diff --git a/3rdparty/CMakeLists.txt b/3rdparty/CMakeLists.txt index d37334a163..568f805628 100644 --- a/3rdparty/CMakeLists.txt +++ b/3rdparty/CMakeLists.txt @@ -1,216 +1,217 @@ project (krita-and-all-its-deps) # # Build all dependencies for Krita and finally Krita itself. # Parameters: EXTERNALS_DOWNLOAD_DIR place to download all packages # INSTALL_ROOT place to install everything to # MXE_TOOLCHAIN: the toolchain file to cross-compile using MXE # # Example usage: cmake ..\kritadeposx -DEXTERNALS_DOWNLOAD_DIR=/dev2/d -DINSTALL_ROOT=/dev2/i -DWIN64_BUILD=TRUE -DBOOST_LIBRARYDIR=/dev2/i/lib -G "Visual Studio 11 Win64" cmake_minimum_required(VERSION 2.8.6) if(NOT SUBMAKE_JOBS) set(SUBMAKE_JOBS 1) endif(NOT SUBMAKE_JOBS) if (CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR) message(FATAL_ERROR "Compiling in the source directory is not supported. Use for example 'mkdir build; cd build; cmake ..'.") endif (CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR) # Tools must be obtained to work with: include (ExternalProject) # allow specification of a directory with pre-downloaded # requirements if(NOT IS_DIRECTORY ${EXTERNALS_DOWNLOAD_DIR}) message(FATAL_ERROR "No externals download dir set. Use -DEXTERNALS_DOWNLOAD_DIR") endif() if(NOT IS_DIRECTORY ${INSTALL_ROOT}) message(FATAL_ERROR "No install dir set. Use -DINSTALL_ROOT") endif() set(TOP_INST_DIR ${INSTALL_ROOT}) set(EXTPREFIX "${TOP_INST_DIR}") set(CMAKE_PREFIX_PATH "${EXTPREFIX}") if (${CMAKE_GENERATOR} STREQUAL "Visual Studio 14 2015 Win64") SET(GLOBAL_PROFILE -DCMAKE_MODULE_LINKER_FLAGS=/machine:x64 -DCMAKE_EXE_LINKER_FLAGS=/machine:x64 -DCMAKE_SHARED_LINKER_FLAGS=/machine:x64 -DCMAKE_STATIC_LINKER_FLAGS=/machine:x64 ) endif () message( STATUS "CMAKE_GENERATOR: ${CMAKE_GENERATOR}") message( STATUS "CMAKE_CL_64: ${CMAKE_CL_64}") set(GLOBAL_BUILD_TYPE RelWithDebInfo) set(GLOBAL_PROFILE ${GLOBAL_PROFILE} -DBUILD_TESTING=false) if (MINGW) option(QT_ENABLE_DEBUG_INFO "Build Qt with debug info included" OFF) option(QT_ENABLE_DYNAMIC_OPENGL "Build Qt with dynamic ANGLE support '-opengl dynamic -angle' (needs env var 'WindowsSdkDir' set to path of Windows 10 SDK)" ON) if (QT_ENABLE_DYNAMIC_OPENGL) if (DEFINED ENV{WindowsSdkDir}) message(STATUS "WindowsSdkDir is set to '$ENV{WindowsSdkDir}'") else (DEFINED ENV{WindowsSdkDir}) message(FATAL_ERROR "Environment variable 'WindowsSdkDir' not set! Please set it to path of Windows 10 SDK or disable QT_ENABLE_DYNAMIC_OPENGL") endif (DEFINED ENV{WindowsSdkDir}) endif (QT_ENABLE_DYNAMIC_OPENGL) endif (MINGW) set(SECURITY_EXE_LINKER_FLAGS "") set(SECURITY_SHARED_LINKER_FLAGS "") set(SECURITY_MODULE_LINKER_FLAGS "") if (MINGW) option(USE_MINGW_HARDENING_LINKER "Enable DEP (NX), ASLR and high-entropy ASLR linker flags (mingw-w64)" ON) if (USE_MINGW_HARDENING_LINKER) set(SECURITY_EXE_LINKER_FLAGS "-Wl,--dynamicbase -Wl,--nxcompat -Wl,--disable-auto-image-base") set(SECURITY_SHARED_LINKER_FLAGS "-Wl,--dynamicbase -Wl,--nxcompat -Wl,--disable-auto-image-base") set(SECURITY_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(SECURITY_EXE_LINKER_FLAGS "${SECURITY_EXE_LINKER_FLAGS} -Wl,--high-entropy-va -Wl,--image-base,0x140000000") set(SECURITY_SHARED_LINKER_FLAGS "${SECURITY_SHARED_LINKER_FLAGS} -Wl,--high-entropy-va -Wl,--image-base,0x180000000") set(SECURITY_MODULE_LINKER_FLAGS "${SECURITY_MODULE_LINKER_FLAGS} -Wl,--high-entropy-va -Wl,--image-base,0x180000000") set(GLOBAL_PROFILE ${GLOBAL_PROFILE} -DCMAKE_EXE_LINKER_FLAGS=${SECURITY_EXE_LINKER_FLAGS} -DCMAKE_SHARED_LINKER_FLAGS=${SECURITY_SHARED_LINKER_FLAGS} -DCMAKE_MODULE_LINKER_FLAGS=${SECURITY_MODULE_LINKER_FLAGS} ) 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) if (DEFINED EP_PREFIX) set_directory_properties(PROPERTIES EP_PREFIX ${EP_PREFIX}) endif (DEFINED EP_PREFIX) if (MSVC) set(GLOBAL_PROFILE ${GLOBAL_PROFILE} -DCMAKE_EXE_LINKER_FLAGS=/PROFILE -DCMAKE_SHARED_LINKER_FLAGS=/PROFILE) set(PATCH_COMMAND myptch) endif() if (MINGW) set(PATCH_COMMAND myptch) endif() if (MSYS) set(PATCH_COMMAND patch) set(GLOBAL_PROFILE ${GLOBAL_PROFILE} -DCMAKE_TOOLCHAIN_FILE=${MXE_TOOLCHAIN} -DCMAKE_FIND_PREFIX_PATH=${CMAKE_PREFIX_PATH} -DCMAKE_SYSTEM_INCLUDE_PATH=${CMAKE_PREFIX_PATH}/include -DCMAKE_INCLUDE_PATH=${CMAKE_PREFIX_PATH}/include -DCMAKE_LIBRARY_PATH=${CMAKE_PREFIX_PATH}/lib -DZLIB_ROOT=${CMAKE_PREFIX_PATH} ) set(GLOBAL_AUTOMAKE_PROFILE --host=i686-pc-mingw32 ) endif() if (APPLE) set(GLOBAL_PROFILE ${GLOBAL_PROFILE} -DCMAKE_MACOSX_RPATH=ON -DKDE_SKIP_RPATH_SETTINGS=ON -DBUILD_WITH_INSTALL_RPATH=ON -DAPPLE_SUPPRESS_X11_WARNING=ON) set(PATCH_COMMAND patch) endif () if (UNIX AND NOT APPLE) set(LINUX true) set(PATCH_COMMAND patch) endif () 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) option(ENABLE_PYTHON_DEPS "Enable Python deps (sip, pyqt)" ON) if (ENABLE_PYTHON_DEPS) 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) message(STATUS "Python requirements met.") 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!") endif (NOT CAN_USE_PYTHON_LIBS) else (PYTHONLIBS_FOUND AND PYTHONINTERP_FOUND) message(FATAL_ERROR "Python requirements not met. To disable Python deps, set ENABLE_PYTHON_DEPS to OFF.") endif (PYTHONLIBS_FOUND AND PYTHONINTERP_FOUND) endif (ENABLE_PYTHON_DEPS) endif (MINGW) # this list must be dependency-ordered if (ENABLE_PYTHON_DEPS OR NOT MINGW) add_subdirectory( ext_python ) endif (ENABLE_PYTHON_DEPS OR NOT MINGW) if (MSVC) add_subdirectory( ext_patch ) add_subdirectory( ext_png2ico ) endif (MSVC) if (MINGW) add_subdirectory( ext_patch ) add_subdirectory( ext_png2ico ) endif (MINGW) add_subdirectory( ext_iconv ) add_subdirectory( ext_gettext ) add_subdirectory( ext_zlib ) add_subdirectory( ext_boost ) add_subdirectory( ext_jpeg ) add_subdirectory( ext_tiff ) add_subdirectory( ext_png ) add_subdirectory( ext_eigen3 ) add_subdirectory( ext_expat ) # for exiv2 add_subdirectory( ext_exiv2 ) add_subdirectory( ext_ilmbase ) add_subdirectory( ext_lcms2 ) add_subdirectory( ext_openexr ) add_subdirectory( ext_vc ) add_subdirectory( ext_gsl ) add_subdirectory( ext_fftw3 ) add_subdirectory( ext_ocio ) if (MSVC) add_subdirectory( ext_pthreads ) endif (MSVC) add_subdirectory( ext_fontconfig) add_subdirectory( ext_freetype) add_subdirectory( ext_qt ) add_subdirectory( ext_poppler ) add_subdirectory( ext_libraw ) add_subdirectory( ext_frameworks ) if (ENABLE_PYTHON_DEPS OR NOT MINGW) add_subdirectory( ext_sip ) add_subdirectory( ext_pyqt ) endif (ENABLE_PYTHON_DEPS OR NOT MINGW) if (MSVC OR MINGW) add_subdirectory( ext_drmingw ) +# add_subdirectory( ext_ffmpeg ) endif (MSVC OR MINGW) if (NOT APPLE) add_subdirectory( ext_gmic ) endif (NOT APPLE) add_subdirectory(ext_giflib) diff --git a/3rdparty/README.md b/3rdparty/README.md index 7070589e3b..b3600e4c95 100644 --- a/3rdparty/README.md +++ b/3rdparty/README.md @@ -1,259 +1,255 @@ = CMake external projects to build krita's dependencies on Linux, Windows or OSX = If you need to build Krita's dependencies for the following reasons: * you develop on Windows and aren't using Craft * you develop on OSX and aren't using Homebrew, or are on OSX and want to make distributable app bundles * you want to build a generic, distro-agnostic version of Krita for Linux * you develop on Linux, but some dependencies aren't available for your distribution and you know what you're doing, you can use the following guide to build the dependencies that Krita needs. If you develop on Linux and your distribution has all dependencies available, YOU DO NOT NEED THIS GUIDE AND YOU SHOULD STOP READING NOW Otherwise you risk major confusion. == Prerequisites == Note: on all operating systems the entire procedure is done in a terminal window. 1. git: https://git-scm.com/downloads. Make sure git is in your path 2. CMake 3.3.2 or later: https://cmake.org/download/. Make sure cmake is in your path. * CMake 3.9 does not build Krita properly at the moment, please use 3.8 instead. 3. Make sure you have a compiler: * Linux: gcc, minimum version 4.8 * OSX: clang, you need to install xcode for this * Windows: mingw-w64 7.1 (by mingw-builds) - 32-bit (x86) target: https://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win32/Personal%20Builds/mingw-builds/7.1.0/threads-posix/dwarf/ - 64-bit (x64) target: https://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win64/Personal%20Builds/mingw-builds/7.1.0/threads-posix/seh/ Make sure mingw's bin folder is in your path. It might be a good idea to create a batch file which sets the path and start cmd. MSVC is *not* supported at the moment. 4. On Windows, you will also need Python 3.6.2 (technically any versions of 3.6 is fine, but it's not tested): https://www.python.org. Make sure to have that version of python.exe in your path. This version of Python will be used for two things: to configure Qt and to build the Python scripting module. Make sure the version you download is exactly python-3.6.2. Make sure that this version of Python comes first in your path. Do not set PYTHONHOME or PYTHONPATH. Make sure that your Python will have the correct architecture for the version you are trying to build: https://www.python.org/downloads/windows/ 5. On Windows, if you want to compile Qt with ANGLE support, you will need to install Windows 10 SDK and have the environment variable `WindowsSdkDir` set to it (typically `C:\Program Files (x86)\Windows Kits\10`) == Setup your environment == == Prepare your directory layout == 1. Make a toplevel build directory, say $HOME/dev or c:\dev. We'll refer to this directory as BUILDROOT. You can use a variable for this, on WINDOWS %BUILDROOT%, on OSX and Linux $BUILDROOT. You will have to replace BUILDROOT with $BUILDROOT or %BUILDROOT whenever you copy and paste a command, depending on your operating system. 2. Checkout krita in BUILDROOT cd BUILDROOT git clone git://anongit.kde.org/krita.git 3. Create the build directory mkdir BUILDROOT/b 4. Create the downloads directory mkdir BUILDROOT/d 5. Create the install directory mkdir BUILDROOT/i == Prepare the externals build == 1. enter the BUILDROOT/b directory 2. run cmake: * Linux: export PATH=$BUILDROOT/i/bin:$PATH export PYTHONHOME=$BUILDROOT/i (only if you want to build your own python) cmake ../krita/3rdparty \ -DINSTALL_ROOT=$BUILDROOT/i \ -DEXTERNALS_DOWNLOAD_DIR=$BUILDROOT/d \ -DCMAKE_INSTALL_PREFIX=BUILDROOT/i * OSX: export PATH=$BUILDROOT/i/bin:$PATH export PYTHONHOME=$BUILDROOT/i (only if you want to build your own python) cmake ../krita/3rdparty/ \ -DCMAKE_INSTALL_PREFIX=$BUILDROOT/i \ -DEXTERNALS_DOWNLOAD_DIR=$BUILDROOT/d \ -DINSTALL_ROOT=$BUILDROOT/i * Windows 32-bit / 64-bit: Note that the cmake command needs to point to your BUILDROOT like /dev/d, not c:\dev\d. set PATH=%BUILDROOT%\i\bin\;%BUILDROOT%\i\lib;%PATH% set PATH=BUILDROOT\i\bin\;BUILDROOT\i\lib;%PATH% cmake ..\krita\3rdparty -DEXTERNALS_DOWNLOAD_DIR=/dev/d -DINSTALL_ROOT=/dev/i -G "MinGW Makefiles" - If you want to build Qt and some other dependencies with parallel jobs, add `-DSUBMAKE_JOBS=` to the cmake command where is the number of jobs to run (if your PC has 4 CPU cores, you might want to set it to 5). - If you don't have Windows 10 SDK and don't want to build Qt with ANGLE, add `-DQT_ENABLE_DYNAMIC_OPENGL=OFF` to the CMake command line args. 3. build the packages: With a judicious application of DEPENDS statements, it's possible to build it all in one go, but in my experience that fails always, so it's better to build the dependencies independently. On Windows: cmake --build . --config RelWithDebInfo --target ext_patch cmake --build . --config RelWithDebInfo --target ext_png2ico - cmake --build . --config RelWithDebInfo --target ext_gettext -On OSX: +On OSX and Windows: cmake --build . --config RelWithDebInfo --target ext_gettext On all operating systems: cmake --build . --config RelWithDebInfo --target ext_qt cmake --build . --config RelWithDebInfo --target ext_zlib cmake --build . --config RelWithDebInfo --target ext_boost Note about boost: check if the headers are installed into i/include/boost, but not into i/include/boost-1.61/boost cmake --build . --config RelWithDebInfo --target ext_eigen3 cmake --build . --config RelWithDebInfo --target ext_exiv2 cmake --build . --config RelWithDebInfo --target ext_fftw3 - -On all operating systems - cmake --build . --config RelWithDebInfo --target ext_ilmbase cmake --build . --config RelWithDebInfo --target ext_jpeg cmake --build . --config RelWithDebInfo --target ext_lcms2 cmake --build . --config RelWithDebInfo --target ext_ocio cmake --build . --config RelWithDebInfo --target ext_openexr -Note for OSX: - -On OSX, you need to first build openexr; that will fail; then you need to set the rpath for the two utilities correctly, then try to build openexr again. +OSX Note: You need to first build openexr; that will fail; then you need to set the rpath for the two utilities correctly, then try to build openexr again. install_name_tool -add_rpath $BUILD_ROOT/i/lib $BUILD_ROOT/b/ext_openexr/ext_openexr-prefix/src/ext_openexr-build/IlmImf/./b44ExpLogTable install_name_tool -add_rpath $BUILD_ROOT/i/lib $BUILD_ROOT/b/ext_openexr/ext_openexr-prefix/src/ext_openexr-build/IlmImf/./dwaLookups -On All operating systems: + + +On All operating systems cmake --build . --config RelWithDebInfo --target ext_png cmake --build . --config RelWithDebInfo --target ext_tiff cmake --build . --config RelWithDebInfo --target ext_gsl cmake --build . --config RelWithDebInfo --target ext_vc cmake --build . --config RelWithDebInfo --target ext_libraw cmake --build . --config RelWithDebInfo --target ext_giflib -On Linux, if you want to build your own SIP and PyQt instead of the system one: +On Linux (if you want to build your own SIP and PyQt instead of the system one) cmake --build . --config RelWithDebInfo --target ext_sip cmake --build . --config RelWithDebInfo --target ext_pyqt On Windows cmake --build . --config RelWithDebInfo --target ext_freetype cmake --build . --config RelWithDebInfo --target ext_poppler On Linux cmake --build . --config RelWithDebInfo --target ext_kcrash -Everywhere else: - - cmake --build . --config RelWithDebInfo --target ext_kwindowsystem - -On Windows, if you want to include DrMingw for dumping backtrace on crash: +On Windows (if you want to include DrMingw for dumping backtrace on crash) cmake --build . --config RelWithDebInfo --target ext_drmingw -On Windows, if you want to include Python scripting: +On Windows (if you want to include Python scripting) cmake --build . --config RelWithDebInfo --target ext_python cmake --build . --config RelWithDebInfo --target ext_sip cmake --build . --config RelWithDebInfo --target ext_pyqt -On Windows and Linux, if you want to build gmic-qt: +On Windows and Linux (if you want to include gmic-qt) cmake --build . --config RelWithDebInfo --target ext_gmic -Note: poppler should be buildable on Linux as well with a home-built freetype +Linux Note: poppler should be buildable on Linux as well with a home-built freetype and fontconfig, but I don't know how to make fontconfig find freetype, and on Linux, fontconfig is needed for poppler. Poppler is needed for PDF import. -Note 2: if you want to build a release, you need to get the binary gettext - -Note 3: in order to build fontconfig on macOS, you need to have pkg-config installed. +OSX Note: In order to build fontconfig on macOS, you need to have pkg-config installed. You probably need homebrew for that... See http://macappstore.org/pkg-config/ . +archives from: files.kde.org/krita/build/dependencies: + +On Windows, download the approporiate zip file and put in in your i folder -archives from files.kde.org/krita/build/dependencies: + http://files.kde.org/krita/build/dependencies/gettext0.19.8.1-iconv1.14-shared-32.zip + http://files.kde.org/krita/build/dependencies/gettext0.19.8.1-iconv1.14-shared-64.zip + +- Take care, these zips contain a libstdc++-6.dll that you don't want in your path when building. + +On Windows and OSX + cmake --build . --config RelWithDebInfo --target ext_kwindowsystem - http://files.kde.org/krita/build/dependencies/gettext0.19.8.1-iconv1.14-shared-32.zip - http://files.kde.org/krita/build/dependencies/gettext0.19.8.1-iconv1.14-shared-64.zip -Take care, these zips contain a libstdc++-6.dll that you don't want in your path when building. == Build Krita == 1. Make a krita build directory: mkdir BUILDROOT/build 2. Enter the BUILDROOT/build 3. Run On Windows Depending on what you want to use, run this command for MSBuild: cmake ..\krita -G "MinGW Makefiles" -DBoost_DEBUG=OFF -DBOOST_INCLUDEDIR=c:\dev\i\include -DBOOST_DEBUG=ON -DBOOST_ROOT=c:\dev\i -DBOOST_LIBRARYDIR=c:\dev\i\lib -DCMAKE_INSTALL_PREFIX=c:\dev\i -DCMAKE_PREFIX_PATH=c:\dev\i -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_TESTING=OFF -DKDE4_BUILD_TESTS=OFF -DHAVE_MEMORY_LEAK_TRACKER=OFF -Wno-dev -DDEFINE_NO_DEPRECATED=1 Or this to use jom (faster compiling, uses all cores, ships with QtCreator/pre-built Qt binaries): cmake ..\krita -G "MinGW Makefiles" -DBoost_DEBUG=OFF -DBOOST_INCLUDEDIR=c:\dev\i\include -DBOOST_DEBUG=ON -DBOOST_ROOT=c:\dev\i -DBOOST_LIBRARYDIR=c:\dev\i\lib -DCMAKE_INSTALL_PREFIX=c:\dev\i -DCMAKE_PREFIX_PATH=c:\dev\i -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_TESTING=OFF -DKDE4_BUILD_TESTS=OFF -DHAVE_MEMORY_LEAK_TRACKER=OFF -Wno-dev -DDEFINE_NO_DEPRECATED=1 On Linux cmake ../krita -DCMAKE_INSTALL_PREFIX=BUILDROOT/i -DDEFINE_NO_DEPRECATED=1 -DBUILD_TESTING=OFF -DKDE4_BUILD_TESTS=OFF -DCMAKE_BUILD_TYPE=RelWithDebInfo # Troubleshooting: if you built your own SIP and CMake fails to find it, please set # the following environment variable to the SIP installation directory: export PYTHONPATH=$BUILDROOT/i/sip/ # If you also have KIO installed in the system, don't forget to disable it by bassing to cmake: # cmake -DCMAKE_DISABLE_FIND_PACKAGE_KF5KIO=true . On OSX cmake ../krita -DCMAKE_INSTALL_PREFIX=$BUILDROOT/i -DDEFINE_NO_DEPRECATED=1 -DBUILD_TESTING=OFF -DKDE4_BUILD_TESTS=OFF -DBUNDLE_INSTALL_DIR=$BUILDROOT/i/bin -DCMAKE_BUILD_TYPE=RelWithDebInfo 4. Run On Linux and OSX make make install On Windows (replace 4 with the number of jobs to run in parallel) cmake --build . --target install -- -j4 6. Run krita: On Linux BUILDROOT/i/bin/krita On Windows BUILDROOT\i\bin\krita.exe On OSX BUILDROOT/i/bin/krita.app/Contents/MacOS/krita == Packaging a Windows Build == If you want to create a stripped down version of Krita to distribute, after building everything just copy the package_2.cmd file from the "windows" folder inside krita root source folder to BUILDROOT and run it (most likely C:\dev\). That script will copy the necessary files into the specified folder and leave out developer related files. After the script runs there will be two new ZIP files that contain a small portable version of Krita and a separate portable debug version. diff --git a/3rdparty/ext_exiv2/CMakeLists.txt b/3rdparty/ext_exiv2/CMakeLists.txt index 262789f0c5..29c51c7ed6 100644 --- a/3rdparty/ext_exiv2/CMakeLists.txt +++ b/3rdparty/ext_exiv2/CMakeLists.txt @@ -1,16 +1,16 @@ SET(PREFIX_ext_exiv2 "${EXTPREFIX}" ) ExternalProject_Add( ext_exiv2 DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} - URL http://www.exiv2.org/builds/exiv2-0.26-trunk.tar.gz + URL http://files.kde.org/krita/build/dependencies/exiv2-0.26-trunk.tar.gz URL_MD5 5399e3b570d7f9205f0e76d47582da4c PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/tzname.patch COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/patch_mingw.patch INSTALL_DIR ${PREFIX_ext_exiv2} CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${PREFIX_ext_exiv2} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DEXIV2_ENABLE_BUILD_SAMPLES=OFF -DEXIV2_ENABLE_BUILD_PO=OFF -DEXIV2_ENABLE_NLS=OFF -DICONV_INCLUDE_DIR=${PREFIX_ext_exiv2}/include UPDATE_COMMAND "" DEPENDS ext_iconv ext_expat ) diff --git a/3rdparty/ext_ffmpeg/CMakeLists.txt b/3rdparty/ext_ffmpeg/CMakeLists.txt new file mode 100644 index 0000000000..2e60c26203 --- /dev/null +++ b/3rdparty/ext_ffmpeg/CMakeLists.txt @@ -0,0 +1,34 @@ +SET(PREFIX_ext_ffmpeg "${EXTPREFIX}" ) + +if(MSVC OR MINGW) + if("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") + ExternalProject_Add( ext_ffmpeg + DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} + URL https://ffmpeg.zeranoe.com/builds/win64/static/ffmpeg-4.0-win64-static.zip + URL_MD5 bf496481c6991c529e2e94a8e0fa3113 + + INSTALL_DIR ${PREFIX_ext_ffmpeg} + CONFIGURE_COMMAND "" + BUILD_COMMAND ${CMAKE_COMMAND} -E echo deploying ffmpeg3 64-bit binary + INSTALL_COMMAND ${CMAKE_COMMAND} -E copy /bin/ffmpeg.exe ${PREFIX_ext_ffmpeg}/bin/ffmpeg.exe + COMMAND ${CMAKE_COMMAND} -E copy /LICENSE.txt ${PREFIX_ext_ffmpeg}/bin/ffmpeg_LICENSE.txt + COMMAND ${CMAKE_COMMAND} -E copy /README.txt ${PREFIX_ext_ffmpeg}/bin/ffmpeg_README.txt + UPDATE_COMMAND "" + ) + else("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") + ExternalProject_Add( ext_ffmpeg + DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} + URL https://ffmpeg.zeranoe.com/builds/win32/static/ffmpeg-4.0-win32-static.zip + URL_MD5 a969a969e3404fe35100e85a37186e5f + + INSTALL_DIR ${PREFIX_ext_ffmpeg} + CONFIGURE_COMMAND "" + BUILD_COMMAND ${CMAKE_COMMAND} -E echo deploying ffmpeg3 32-bit binary + INSTALL_COMMAND ${CMAKE_COMMAND} -E copy /bin/ffmpeg.exe ${PREFIX_ext_ffmpeg}/bin/ffmpeg.exe + COMMAND ${CMAKE_COMMAND} -E copy /LICENSE.txt ${PREFIX_ext_ffmpeg}/bin/ffmpeg_LICENSE.txt + COMMAND ${CMAKE_COMMAND} -E copy /README.txt ${PREFIX_ext_ffmpeg}/bin/ffmpeg_README.txt + UPDATE_COMMAND "" + ) + endif("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") + +endif() diff --git a/3rdparty/ext_gmic/CMakeLists.txt b/3rdparty/ext_gmic/CMakeLists.txt index d565a8110f..53f58dd1dc 100644 --- a/3rdparty/ext_gmic/CMakeLists.txt +++ b/3rdparty/ext_gmic/CMakeLists.txt @@ -1,37 +1,37 @@ SET(PREFIX_ext_gmic "${EXTPREFIX}" ) # Download the gmic sources ExternalProject_Add( ext_gmic_base DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} - URL https://gmic.eu/files/source/gmic_2.2.0.tar.gz - URL_HASH SHA1=caf72b80da8bd3e31111cff8407b005e7115d2c2 + URL https://gmic.eu/files/source/gmic_2.2.3.tar.gz + URL_HASH SHA1=43f975a462a842be5eeff70d8d372476547163b0 SOURCE_DIR gmic CONFIGURE_COMMAND "" BUILD_COMMAND "" INSTALL_COMMAND "" BUILD_IN_SOURCE 1 ) # Download and build gmic-qt # FIXME: Forcing CMAKE_BUILD_TYPE to Release ExternalProject_Add( ext_gmic_qt DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} - URL https://github.com/c-koi/gmic-qt/archive/v.220.tar.gz - URL_HASH SHA1=2b3731e7c90c79d298758b7702a68c1468035429 + URL https://github.com/c-koi/gmic-qt/archive/v.2.2.3.tar.gz + URL_HASH SHA1=a3c889de09031d34754c9bb3bd1004a2f67713c6 PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/desktop_icon.diff SOURCE_DIR gmic-qt INSTALL_DIR ${PREFIX_ext_gmic} CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${PREFIX_ext_gmic} -DGMIC_QT_HOST=krita -DCMAKE_BUILD_TYPE=Release ${GLOBAL_PROFILE} UPDATE_COMMAND "" DEPENDS ext_gmic_base ) add_custom_target(ext_gmic) add_dependencies(ext_gmic ext_gmic_qt) diff --git a/CMakeLists.txt b/CMakeLists.txt index 242197988a..ee32fa41af 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,752 +1,740 @@ project(krita) message(STATUS "Using CMake version: ${CMAKE_VERSION}") cmake_minimum_required(VERSION 2.8.12 FATAL_ERROR) set(MIN_QT_VERSION 5.6.0) -option(OVERRIDE_QT_VERSION "Use this to make it possible to build with Qt < 5.6.0. There will be bugs." OFF) -if (OVERRIDE_QT_VERSION) - set(MIN_QT_VERSION 5.4.0) -endif() -set(MIN_FRAMEWORKS_VERSION 5.22.0) +set(MIN_FRAMEWORKS_VERSION 5.18.0) if (POLICY CMP0002) cmake_policy(SET CMP0002 OLD) endif() if (POLICY CMP0017) cmake_policy(SET CMP0017 NEW) endif () if (POLICY CMP0022) cmake_policy(SET CMP0022 OLD) endif () if (POLICY CMP0026) cmake_policy(SET CMP0026 OLD) endif() if (POLICY CMP0042) cmake_policy(SET CMP0042 NEW) endif() if (POLICY CMP0046) cmake_policy(SET CMP0046 OLD) endif () if (POLICY CMP0059) cmake_policy(SET CMP0059 OLD) endif() if (POLICY CMP0063) cmake_policy(SET CMP0063 OLD) endif() if (POLICY CMP0054) cmake_policy(SET CMP0054 OLD) endif() if (POLICY CMP0064) cmake_policy(SET CMP0064 OLD) endif() if (APPLE) set(APPLE_SUPPRESS_X11_WARNING TRUE) set(KDE_SKIP_RPATH_SETTINGS TRUE) set(CMAKE_MACOSX_RPATH 1) set(BUILD_WITH_INSTALL_RPATH 1) add_definitions(-mmacosx-version-min=10.11 -Wno-macro-redefined -Wno-deprecated-register) endif() -if (LINUX) - if (CMAKE_COMPILER_IS_GNUCXX AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.9 AND NOT WINDOWS) - add_definitions(-Werror=delete-incomplete) - endif() +if (CMAKE_COMPILER_IS_GNUCXX AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.9 AND NOT WIN32) + add_definitions(-Werror=delete-incomplete) endif() ###################### ####################### ## Constants defines ## ####################### ###################### # define common versions of Krita applications, used to generate kritaversion.h # update these version for every release: set(KRITA_VERSION_STRING "4.1.0-pre-alpha") # Major version: 3 for 3.x, 4 for 4.x, etc. set(KRITA_STABLE_VERSION_MAJOR 4) # Minor version: 0 for 4.0, 1 for 4.1, etc. set(KRITA_STABLE_VERSION_MINOR 1) # Bugfix release version, or 0 for before the first stable release set(KRITA_VERSION_RELEASE 0) # the 4th digit, really only used for the Windows installer: # - [Pre-]Alpha: Starts from 0, increment 1 per release # - Beta: Starts from 50, increment 1 per release # - Stable: Set to 100, bump to 101 if emergency update is needed set(KRITA_VERSION_REVISION 0) set(KRITA_ALPHA 1) # uncomment only for Alpha #set(KRITA_BETA 1) # uncomment only for Beta #set(KRITA_RC 1) # uncomment only for RC set(KRITA_YEAR 2018) # update every year if(NOT DEFINED KRITA_ALPHA AND NOT DEFINED KRITA_BETA AND NOT DEFINED KRITA_RC) set(KRITA_STABLE 1) # do not edit endif() message(STATUS "Krita version: ${KRITA_VERSION_STRING}") # Define the generic version of the Krita libraries here # This makes it easy to advance it when the next Krita release comes. # 14 was the last GENERIC_KRITA_LIB_VERSION_MAJOR of the previous Krita series # (2.x) so we're starting with 15 in 3.x series, 16 in 4.x series if(KRITA_STABLE_VERSION_MAJOR EQUAL 4) math(EXPR GENERIC_KRITA_LIB_VERSION_MAJOR "${KRITA_STABLE_VERSION_MINOR} + 16") else() # let's make sure we won't forget to update the "16" message(FATAL_ERROR "Reminder: please update offset == 16 used to compute GENERIC_KRITA_LIB_VERSION_MAJOR to something bigger") endif() set(GENERIC_KRITA_LIB_VERSION "${GENERIC_KRITA_LIB_VERSION_MAJOR}.0.0") set(GENERIC_KRITA_LIB_SOVERSION "${GENERIC_KRITA_LIB_VERSION_MAJOR}") LIST (APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/modules") LIST (APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/kde_macro") # fetch git revision for the current build set(KRITA_GIT_SHA1_STRING "") set(KRITA_GIT_BRANCH_STRING "") include(GetGitRevisionDescription) get_git_head_hash(GIT_SHA1) get_git_branch(GIT_BRANCH) if(GIT_SHA1) string(SUBSTRING ${GIT_SHA1} 0 7 GIT_SHA1) set(KRITA_GIT_SHA1_STRING ${GIT_SHA1}) if(GIT_BRANCH) set(KRITA_GIT_BRANCH_STRING ${GIT_BRANCH}) else() set(KRITA_GIT_BRANCH_STRING "(detached HEAD)") endif() endif() -if(NOT DEFINED RELEASE_BUILD) - # estimate mode by CMAKE_BUILD_TYPE content if not set on cmdline - string(TOLOWER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_TOLOWER) - set(RELEASE_BUILD_TYPES "release" "relwithdebinfo" "minsizerel") - list(FIND RELEASE_BUILD_TYPES "${CMAKE_BUILD_TYPE_TOLOWER}" INDEX) - if (INDEX EQUAL -1) - set(RELEASE_BUILD FALSE) - else() - set(RELEASE_BUILD TRUE) - endif() -endif() -message(STATUS "Release build: ${RELEASE_BUILD}") - # create test make targets enable_testing() # collect list of broken tests, empty here to start fresh with each cmake run set(KRITA_BROKEN_TESTS "" CACHE INTERNAL "KRITA_BROKEN_TESTS") ############ ############# ## Options ## ############# ############ include(FeatureSummary) if (WIN32) option(USE_DRMINGW "Support the Dr. Mingw crash handler (only on windows)" ON) add_feature_info("Dr. Mingw" USE_DRMINGW "Enable the Dr. Mingw crash handler") if (MINGW) option(USE_MINGW_HARDENING_LINKER "Enable DEP (NX), ASLR and high-entropy ASLR linker flags (mingw-w64)" ON) add_feature_info("Linker Security Flags" USE_MINGW_HARDENING_LINKER "Enable DEP (NX), ASLR and high-entropy ASLR linker flags") if (USE_MINGW_HARDENING_LINKER) set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--dynamicbase -Wl,--nxcompat -Wl,--disable-auto-image-base") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--dynamicbase -Wl,--nxcompat -Wl,--disable-auto-image-base") set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--dynamicbase -Wl,--nxcompat -Wl,--disable-auto-image-base") if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") # Enable high-entropy ASLR for 64-bit # The image base has to be >4GB for HEASLR to be enabled. # The values used here are kind of arbitrary. set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--high-entropy-va -Wl,--image-base,0x140000000") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--high-entropy-va -Wl,--image-base,0x180000000") set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--high-entropy-va -Wl,--image-base,0x180000000") endif ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") else (USE_MINGW_HARDENING_LINKER) message(WARNING "Linker Security Flags not enabled!") endif (USE_MINGW_HARDENING_LINKER) endif (MINGW) endif () option(HIDE_SAFE_ASSERTS "Don't show message box for \"safe\" asserts, just ignore them automatically and dump a message to the terminal." ON) configure_file(config-hide-safe-asserts.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-hide-safe-asserts.h) add_feature_info("Safe Asserts" HIDE_SAFE_ASSERTS "Don't show message box for \"safe\" asserts, just ignore them automatically and dump a message to the terminal.") option(FOUNDATION_BUILD "A Foundation build is a binary release build that can package some extra things like color themes. Linux distributions that build and install Krita into a default system location should not define this option to true." OFF) add_feature_info("Foundation Build" FOUNDATION_BUILD "A Foundation build is a binary release build that can package some extra things like color themes. Linux distributions that build and install Krita into a default system location should not define this option to true.") option(KRITA_ENABLE_BROKEN_TESTS "Enable tests that are marked as broken" OFF) add_feature_info("Enable Broken Tests" KRITA_ENABLE_BROKEN_TESTS "Runs broken test when \"make test\" is invoked (use -DKRITA_ENABLE_BROKEN_TESTS=ON to enable).") option(ENABLE_PYTHON_2 "Enables the compiler to look for Python 2.7 instead of Python 3. Some packaged scripts are not compatible with Python 2 and this should only be used if you really have to use 2.7." OFF) include(MacroJPEG) ######################################################### ## Look for Python3 It is also searched by KF5, ## ## so we should request the correct version in advance ## ######################################################### function(TestCompileLinkPythonLibs OUTPUT_VARNAME) include(CheckCXXSourceCompiles) set(CMAKE_REQUIRED_INCLUDES ${PYTHON_INCLUDE_PATH}) set(CMAKE_REQUIRED_LIBRARIES ${PYTHON_LIBRARIES}) if (MINGW) set(CMAKE_REQUIRED_DEFINITIONS -D_hypot=hypot) endif (MINGW) unset(${OUTPUT_VARNAME} CACHE) CHECK_CXX_SOURCE_COMPILES(" #include int main(int argc, char *argv[]) { Py_InitializeEx(0); }" ${OUTPUT_VARNAME}) endfunction() if(MINGW) if(ENABLE_PYTHON_2) message(FATAL_ERROR "Python 2.7 is not supported on Windows at the moment.") else(ENABLE_PYTHON_2) find_package(PythonInterp 3.6 EXACT) find_package(PythonLibs 3.6 EXACT) endif(ENABLE_PYTHON_2) if (PYTHONLIBS_FOUND AND PYTHONINTERP_FOUND) if(ENABLE_PYTHON_2) find_package(PythonLibrary 2.7) else(ENABLE_PYTHON_2) find_package(PythonLibrary 3.6) endif(ENABLE_PYTHON_2) TestCompileLinkPythonLibs(CAN_USE_PYTHON_LIBS) if (NOT CAN_USE_PYTHON_LIBS) message(FATAL_ERROR "Compiling with Python library failed, please check whether the architecture is correct. Python will be disabled.") endif (NOT CAN_USE_PYTHON_LIBS) endif (PYTHONLIBS_FOUND AND PYTHONINTERP_FOUND) else(MINGW) if(ENABLE_PYTHON_2) find_package(PythonInterp 2.7) find_package(PythonLibrary 2.7) else(ENABLE_PYTHON_2) find_package(PythonInterp 3.0) find_package(PythonLibrary 3.0) endif(ENABLE_PYTHON_2) endif(MINGW) ######################## ######################### ## Look for KDE and Qt ## ######################### ######################## find_package(ECM 5.22 REQUIRED NOMODULE) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR}) include(ECMOptionalAddSubdirectory) include(ECMAddAppIcon) include(ECMSetupVersion) include(ECMMarkNonGuiExecutable) include(ECMGenerateHeaders) include(GenerateExportHeader) include(ECMMarkAsTest) include(ECMInstallIcons) include(CMakePackageConfigHelpers) include(WriteBasicConfigVersionFile) include(CheckFunctionExists) include(KDEInstallDirs) include(KDECMakeSettings) include(KDECompilerSettings) # do not reorder to be alphabetical: this is the order in which the frameworks # depend on each other. find_package(KF5 ${MIN_FRAMEWORKS_VERSION} REQUIRED COMPONENTS Archive Config WidgetsAddons Completion CoreAddons GuiAddons I18n ItemModels ItemViews WindowSystem ) # KConfig deprecated authorizeKAction. In order to be warning free, # compile with the updated function when the dependency is new enough. # Remove this (and the uses of the define) when the minimum KF5 # version is >= 5.24.0. if (${KF5Config_VERSION} VERSION_LESS "5.24.0" ) message("Old KConfig (< 5.24.0) found.") add_definitions(-DKCONFIG_BEFORE_5_24) endif() find_package(Qt5 ${MIN_QT_VERSION} REQUIRED COMPONENTS Core Gui Widgets Xml Network PrintSupport Svg Test Concurrent ) include (MacroAddFileDependencies) include (MacroBoolTo01) include (MacroEnsureOutOfSourceBuild) macro_ensure_out_of_source_build("Compiling Krita inside the source directory is not possible. Please refer to the build instruction https://community.kde.org/Krita#Build_Instructions") # Note: OPTIONAL_COMPONENTS does not seem to be reliable # (as of ECM 5.15.0, CMake 3.2) find_package(Qt5Multimedia ${MIN_QT_VERSION}) set_package_properties(Qt5Multimedia PROPERTIES DESCRIPTION "Qt multimedia integration" URL "http://www.qt.io/" TYPE OPTIONAL PURPOSE "Optionally used to provide sound support for animations") macro_bool_to_01(Qt5Multimedia_FOUND HAVE_QT_MULTIMEDIA) configure_file(config-qtmultimedia.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-qtmultimedia.h ) if (NOT APPLE) find_package(Qt5Quick ${MIN_QT_VERSION}) set_package_properties(Qt5Quick PROPERTIES DESCRIPTION "QtQuick" URL "http://www.qt.io/" TYPE OPTIONAL PURPOSE "Optionally used for the touch gui for Krita") macro_bool_to_01(Qt5Quick_FOUND HAVE_QT_QUICK) find_package(Qt5QuickWidgets ${MIN_QT_VERSION}) set_package_properties(Qt5QuickWidgets PROPERTIES DESCRIPTION "QtQuickWidgets" URL "http://www.qt.io/" TYPE OPTIONAL PURPOSE "Optionally used for the touch gui for Krita") endif() if (NOT WIN32 AND NOT APPLE) find_package(Qt5 ${MIN_QT_VERSION} REQUIRED X11Extras) find_package(Qt5DBus ${MIN_QT_VERSION}) set(HAVE_DBUS ${Qt5DBus_FOUND}) set_package_properties(Qt5DBus PROPERTIES DESCRIPTION "Qt DBUS integration" URL "http://www.qt.io/" TYPE OPTIONAL PURPOSE "Optionally used to provide a dbus api on Linux") find_package(KF5KIO ${MIN_FRAMEWORKS_VERSION}) macro_bool_to_01(KF5KIO_FOUND HAVE_KIO) set_package_properties(KF5KIO PROPERTIES DESCRIPTION "KDE's KIO Framework" URL "http://api.kde.org/frameworks-api/frameworks5-apidocs/kio/html/index.html" TYPE OPTIONAL PURPOSE "Optionally used for recent document handling") find_package(KF5Crash ${MIN_FRAMEWORKS_VERSION}) macro_bool_to_01(KF5Crash_FOUND HAVE_KCRASH) set_package_properties(KF5Crash PROPERTIES DESCRIPTION "KDE's Crash Handler" URL "http://api.kde.org/frameworks-api/frameworks5-apidocs/kcrash/html/index.html" TYPE OPTIONAL PURPOSE "Optionally used to provide crash reporting on Linux") find_package(X11 REQUIRED COMPONENTS Xinput) set(HAVE_X11 TRUE) add_definitions(-DHAVE_X11) find_package(XCB COMPONENTS XCB ATOM) set(HAVE_XCB ${XCB_FOUND}) else() set(HAVE_DBUS FALSE) set(HAVE_X11 FALSE) set(HAVE_XCB FALSE) endif() add_definitions( -DQT_USE_QSTRINGBUILDER -DQT_STRICT_ITERATORS -DQT_NO_SIGNALS_SLOTS_KEYWORDS -DQT_USE_FAST_OPERATOR_PLUS -DQT_USE_FAST_CONCATENATION -DQT_NO_URL_CAST_FROM_STRING ) if (${Qt5_VERSION} VERSION_GREATER "5.8.0" ) add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50900) elseif(${Qt5_VERSION} VERSION_GREATER "5.7.0" ) add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50800) elseif(${Qt5_VERSION} VERSION_GREATER "5.6.0" ) add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50700) else() add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50600) endif() add_definitions(-DTRANSLATION_DOMAIN=\"krita\") # # The reason for this mode is that the Debug mode disable inlining # if(CMAKE_COMPILER_IS_GNUCXX) set(CMAKE_CXX_FLAGS_KRITADEVS "-O3 -g" CACHE STRING "" FORCE) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fext-numeric-literals") endif() if(UNIX) set(CMAKE_REQUIRED_LIBRARIES "${CMAKE_REQUIRED_LIBRARIES};m") endif() if(WIN32) if(MSVC) # C4522: 'class' : multiple assignment operators specified set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -wd4522") endif() endif() # KDECompilerSettings adds the `--export-all-symbols` linker flag. # We don't really need it. if(MINGW) string(REPLACE "-Wl,--export-all-symbols" "" CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS}") string(REPLACE "-Wl,--export-all-symbols" "" CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS}") endif(MINGW) # enable exceptions globally kde_enable_exceptions() set(KRITA_DEFAULT_TEST_DATA_DIR ${CMAKE_SOURCE_DIR}/sdk/tests/data/) macro(macro_add_unittest_definitions) add_definitions(-DFILES_DATA_DIR="${CMAKE_CURRENT_SOURCE_DIR}/data/") add_definitions(-DFILES_OUTPUT_DIR="${CMAKE_CURRENT_BINARY_DIR}") add_definitions(-DFILES_DEFAULT_DATA_DIR="${KRITA_DEFAULT_TEST_DATA_DIR}") add_definitions(-DSYSTEM_RESOURCES_DATA_DIR="${CMAKE_SOURCE_DIR}/krita/data/") endmacro() # overcome some platform incompatibilities if(WIN32) include_directories(${CMAKE_CURRENT_SOURCE_DIR}/winquirks) add_definitions(-D_USE_MATH_DEFINES) add_definitions(-DNOMINMAX) set(WIN32_PLATFORM_NET_LIBS ws2_32.lib netapi32.lib) endif() # set custom krita plugin installdir set(KRITA_PLUGIN_INSTALL_DIR ${LIB_INSTALL_DIR}/kritaplugins) ########################### ############################ ## Required dependencies ## ############################ ########################### find_package(PNG REQUIRED) if (APPLE) # this is not added correctly on OSX -- see http://forum.kde.org/viewtopic.php?f=139&t=101867&p=221242#p221242 include_directories(SYSTEM ${PNG_INCLUDE_DIR}) endif() add_definitions(-DBOOST_ALL_NO_LIB) find_package(Boost 1.55 REQUIRED COMPONENTS system) include_directories(SYSTEM ${Boost_INCLUDE_DIRS}) ## ## Test for GNU Scientific Library ## find_package(GSL) set_package_properties(GSL PROPERTIES URL "http://www.gnu.org/software/gsl" TYPE RECOMMENDED PURPOSE "Required by Krita's Transform tool.") macro_bool_to_01(GSL_FOUND HAVE_GSL) configure_file(config-gsl.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-gsl.h ) ########################### ############################ ## Optional dependencies ## ############################ ########################### find_package(ZLIB) set_package_properties(ZLIB PROPERTIES DESCRIPTION "Compression library" URL "http://www.zlib.net/" TYPE OPTIONAL PURPOSE "Optionally used by the G'Mic and the PSD plugins") macro_bool_to_01(ZLIB_FOUND HAVE_ZLIB) find_package(OpenEXR) set_package_properties(OpenEXR PROPERTIES DESCRIPTION "High dynamic-range (HDR) image file format" URL "http://www.openexr.com" TYPE OPTIONAL PURPOSE "Required by the Krita OpenEXR filter") macro_bool_to_01(OPENEXR_FOUND HAVE_OPENEXR) set(LINK_OPENEXR_LIB) if(OPENEXR_FOUND) include_directories(SYSTEM ${OPENEXR_INCLUDE_DIR}) set(LINK_OPENEXR_LIB ${OPENEXR_LIBRARIES}) add_definitions(${OPENEXR_DEFINITIONS}) endif() find_package(TIFF) set_package_properties(TIFF PROPERTIES DESCRIPTION "TIFF Library and Utilities" URL "http://www.remotesensing.org/libtiff" TYPE OPTIONAL PURPOSE "Required by the Krita TIFF filter") find_package(JPEG) set_package_properties(JPEG PROPERTIES DESCRIPTION "Free library for JPEG image compression. Note: libjpeg8 is NOT supported." URL "http://www.libjpeg-turbo.org" TYPE OPTIONAL PURPOSE "Required by the Krita JPEG filter") find_package(GIF) set_package_properties(GIF PROPERTIES DESCRIPTION "Library for loading and saving gif files." URL "http://giflib.sourceforge.net/" TYPE OPTIONAL PURPOSE "Required by the Krita GIF filter") +find_package(HEIF "1.2.0") +set_package_properties(HEIF PROPERTIES + DESCRIPTION "Library for loading and saving heif files." + URL "https://github.com/strukturag/libheif" + TYPE OPTIONAL + PURPOSE "Required by the Krita HEIF filter") + set(LIBRAW_MIN_VERSION "0.16") find_package(LibRaw ${LIBRAW_MIN_VERSION}) set_package_properties(LibRaw PROPERTIES DESCRIPTION "Library to decode RAW images" URL "http://www.libraw.org" TYPE OPTIONAL PURPOSE "Required to build the raw import plugin") find_package(FFTW3) set_package_properties(FFTW3 PROPERTIES DESCRIPTION "A fast, free C FFT library" URL "http://www.fftw.org/" TYPE OPTIONAL PURPOSE "Required by the Krita for fast convolution operators and some G'Mic features") macro_bool_to_01(FFTW3_FOUND HAVE_FFTW3) find_package(OCIO) set_package_properties(OCIO PROPERTIES DESCRIPTION "The OpenColorIO Library" URL "http://www.opencolorio.org" TYPE OPTIONAL PURPOSE "Required by the Krita LUT docker") macro_bool_to_01(OCIO_FOUND HAVE_OCIO) set_package_properties(PythonLibrary PROPERTIES DESCRIPTION "Python Library" URL "http://www.python.org" TYPE OPTIONAL PURPOSE "Required by the Krita PyQt plugin") macro_bool_to_01(PYTHONLIBS_FOUND HAVE_PYTHONLIBS) find_package(SIP "4.18.0") set_package_properties(SIP PROPERTIES DESCRIPTION "Support for generating SIP Python bindings" URL "https://www.riverbankcomputing.com/software/sip/download" TYPE OPTIONAL PURPOSE "Required by the Krita PyQt plugin") macro_bool_to_01(SIP_FOUND HAVE_SIP) find_package(PyQt5 "5.6.0") set_package_properties(PyQt5 PROPERTIES DESCRIPTION "Python bindings for Qt5." URL "https://www.riverbankcomputing.com/software/pyqt/download5" TYPE OPTIONAL PURPOSE "Required by the Krita PyQt plugin") macro_bool_to_01(PYQT5_FOUND HAVE_PYQT5) ## ## Look for OpenGL ## # TODO: see if there is a better check for QtGui being built with opengl support (and thus the QOpenGL* classes) if(Qt5Gui_OPENGL_IMPLEMENTATION) message(STATUS "Found QtGui OpenGL support") else() message(FATAL_ERROR "Did NOT find QtGui OpenGL support. Check your Qt configuration. You cannot build Krita without Qt OpenGL support.") endif() ## ## Test for eigen3 ## find_package(Eigen3 3.0 REQUIRED) set_package_properties(Eigen3 PROPERTIES DESCRIPTION "C++ template library for linear algebra" URL "http://eigen.tuxfamily.org" TYPE REQUIRED) ## ## Test for exiv2 ## find_package(Exiv2 0.16 REQUIRED) set_package_properties(Exiv2 PROPERTIES DESCRIPTION "Image metadata library and tools" URL "http://www.exiv2.org" PURPOSE "Required by Krita") ## ## Test for lcms ## find_package(LCMS2 2.4 REQUIRED) set_package_properties(LCMS2 PROPERTIES DESCRIPTION "LittleCMS Color management engine" URL "http://www.littlecms.com" TYPE REQUIRED PURPOSE "Will be used for color management and is necessary for Krita") if(LCMS2_FOUND) if(NOT ${LCMS2_VERSION} VERSION_LESS 2040 ) set(HAVE_LCMS24 TRUE) endif() set(HAVE_REQUIRED_LCMS_VERSION TRUE) set(HAVE_LCMS2 TRUE) endif() ## ## Test for Vc ## set(OLD_CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ) set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/modules ) set(HAVE_VC FALSE) if (NOT ${CMAKE_SYSTEM_PROCESSOR} MATCHES "arm") if(NOT MSVC) find_package(Vc 1.1.0) set_package_properties(Vc PROPERTIES DESCRIPTION "Portable, zero-overhead SIMD library for C++" URL "https://github.com/VcDevel/Vc" TYPE OPTIONAL PURPOSE "Required by the Krita for vectorization") macro_bool_to_01(Vc_FOUND HAVE_VC) endif() endif() configure_file(config-vc.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-vc.h ) if(HAVE_VC) message(STATUS "Vc found!") set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/vc") include (VcMacros) if(Vc_COMPILER_IS_CLANG) set(ADDITIONAL_VC_FLAGS "-Wabi -ffp-contract=fast") if(NOT WIN32) set(ADDITIONAL_VC_FLAGS "${ADDITIONAL_VC_FLAGS} -fPIC") endif() elseif (NOT MSVC) set(ADDITIONAL_VC_FLAGS "-Wabi -fabi-version=0 -ffp-contract=fast") if(NOT WIN32) set(ADDITIONAL_VC_FLAGS "${ADDITIONAL_VC_FLAGS} -fPIC") endif() endif() #Handle Vc master if(Vc_COMPILER_IS_GCC OR Vc_COMPILER_IS_CLANG) AddCompilerFlag("-std=c++11" _ok) if(NOT _ok) AddCompilerFlag("-std=c++0x" _ok) endif() endif() macro(ko_compile_for_all_implementations_no_scalar _objs _src) vc_compile_for_all_implementations(${_objs} ${_src} FLAGS ${ADDITIONAL_VC_FLAGS} ONLY SSE2 SSSE3 SSE4_1 AVX AVX2+FMA+BMI2) endmacro() macro(ko_compile_for_all_implementations _objs _src) vc_compile_for_all_implementations(${_objs} ${_src} FLAGS ${ADDITIONAL_VC_FLAGS} ONLY Scalar SSE2 SSSE3 SSE4_1 AVX AVX2+FMA+BMI2) endmacro() endif() set(CMAKE_MODULE_PATH ${OLD_CMAKE_MODULE_PATH} ) add_definitions(${QT_DEFINITIONS} ${QT_QTDBUS_DEFINITIONS}) ## ## Test endianness ## include (TestBigEndian) test_big_endian(CMAKE_WORDS_BIGENDIAN) ## ## Test for qt-poppler ## find_package(Poppler COMPONENTS Qt5) set_package_properties(Poppler PROPERTIES DESCRIPTION "A PDF rendering library" URL "http://poppler.freedesktop.org" TYPE OPTIONAL PURPOSE "Required by the Krita PDF filter.") ############################ ############################# ## Add Krita helper macros ## ############################# ############################ include(MacroKritaAddBenchmark) #################### ##################### ## Define includes ## ##################### #################### # for config.h and includes (if any?) include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_SOURCE_DIR}/interfaces ) add_subdirectory(libs) add_subdirectory(plugins) add_subdirectory(benchmarks) add_subdirectory(krita) configure_file(KoConfig.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/KoConfig.h ) configure_file(config_convolution.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config_convolution.h) configure_file(config-ocio.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-ocio.h ) check_function_exists(powf HAVE_POWF) configure_file(config-powf.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-powf.h) if(WIN32) - include(${CMAKE_CURRENT_LIST_DIR}/packaging/windows/ConfigureInstallerNsis.cmake) + include(${CMAKE_CURRENT_LIST_DIR}/packaging/windows/installer/ConfigureInstallerNsis.cmake) endif() message("\nBroken tests:") foreach(tst ${KRITA_BROKEN_TESTS}) message(" * ${tst}") endforeach() feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/po OR EXISTS ${CMAKE_CURRENT_BINARY_DIR}/po ) find_package(KF5I18n CONFIG REQUIRED) ki18n_install(po) endif() diff --git a/cmake/modules/FindFFTW3.cmake b/cmake/modules/FindFFTW3.cmake index 7578ef1887..8d30a1f6da 100644 --- a/cmake/modules/FindFFTW3.cmake +++ b/cmake/modules/FindFFTW3.cmake @@ -1,59 +1,59 @@ # - Try to find the Fftw3 Library # Once done this will define # # FFTW3_FOUND - system has fftw3 # FFTW3_INCLUDE_DIRS - the fftw3 include directories # FFTW3_LIBRARIES - the libraries needed to use fftw3 # Redistribution and use is allowed according to the terms of the BSD license. # For details see the accompanying COPYING-CMAKE-SCRIPTS file. # if (NOT WIN32) -include(LibFindMacros) -libfind_pkg_check_modules(FFTW3_PKGCONF fftw3>=3.2) - -find_path(FFTW3_INCLUDE_DIR - NAMES fftw3.h - HINTS ${FFTW3_PKGCONF_INCLUDE_DIRS} ${FFTW3_PKGCONF_INCLUDEDIR} - PATH_SUFFIXES fftw3 -) - -find_library(FFTW3_LIBRARY - NAMES fftw3 - HINTS ${FFTW3_PKGCONF_LIBRARY_DIRS} ${FFTW3_PKGCONF_LIBDIR} -) - -set(FFTW3_PROCESS_LIBS FFTW3_LIBRARY) -set(FFTW3_PROCESS_INCLUDES FFTW3_INCLUDE_DIR) -libfind_process(FFTW3) - -if(FFTW3_FOUND) - message(STATUS "FFTW Found Version: " ${FFTW_VERSION}) -endif() + include(LibFindMacros) + libfind_pkg_check_modules(FFTW3_PKGCONF fftw3>=3.2) + + find_path(FFTW3_INCLUDE_DIR + NAMES fftw3.h + HINTS ${FFTW3_PKGCONF_INCLUDE_DIRS} ${FFTW3_PKGCONF_INCLUDEDIR} + PATH_SUFFIXES fftw3 + ) + + find_library(FFTW3_LIBRARY + NAMES fftw3 + HINTS ${FFTW3_PKGCONF_LIBRARY_DIRS} ${FFTW3_PKGCONF_LIBDIR} + ) + + set(FFTW3_PROCESS_LIBS FFTW3_LIBRARY) + set(FFTW3_PROCESS_INCLUDES FFTW3_INCLUDE_DIR) + libfind_process(FFTW3) + + if(FFTW3_FOUND) + message(STATUS "FFTW Found Version: " ${FFTW_VERSION}) + endif() else() -# TODO: Maybe use fftw3/FFTW3Config.cmake? + # TODO: Maybe use fftw3/FFTW3Config.cmake? -find_path(FFTW3_INCLUDE_DIR - NAMES fftw3.h -) + find_path(FFTW3_INCLUDE_DIR + NAMES fftw3.h + ) -find_library( - FFTW3_LIBRARY - NAMES libfftw3 libfftw3-3 libfftw3f-3 libfftw3l-3 - DOC "Libraries to link against for FFT Support") + find_library( + FFTW3_LIBRARY + NAMES libfftw3 libfftw3-3 libfftw3f-3 libfftw3l-3 + DOC "Libraries to link against for FFT Support") -if (FFTW3_LIBRARY) - set(FFTW3_LIBRARY_DIR ${FFTW3_LIBRARY}) -endif() + if (FFTW3_LIBRARY) + set(FFTW3_LIBRARY_DIR ${FFTW3_LIBRARY}) + endif() -set (FFTW3_LIBRARIES ${FFTW3_LIBRARY}) + set (FFTW3_LIBRARIES ${FFTW3_LIBRARY}) -if(FFTW3_INCLUDE_DIR AND FFTW3_LIBRARY_DIR) - set (FFTW3_FOUND true) - message(STATUS "Correctly found FFTW3") -else() - message(STATUS "Could not find FFTW3") -endif() + if(FFTW3_INCLUDE_DIR AND FFTW3_LIBRARY_DIR) + set (FFTW3_FOUND true) + message(STATUS "Correctly found FFTW3") + else() + message(STATUS "Could not find FFTW3") + endif() endif() diff --git a/cmake/modules/FindHEIF.cmake b/cmake/modules/FindHEIF.cmake new file mode 100644 index 0000000000..2f7fa9befa --- /dev/null +++ b/cmake/modules/FindHEIF.cmake @@ -0,0 +1,50 @@ +# - Try to find the libheif library +# Once done this will define +# +# HEIF_FOUND - system has heif +# HEIF_INCLUDE_DIRS - the heif include directories +# HEIF_LIBRARIES - the libraries needed to use heif +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. +# +if (NOT WIN32) + include(LibFindMacros) + libfind_pkg_check_modules(HEIF_PKGCONF libheif) + + find_path(HEIF_INCLUDE_DIR + NAMES libheif/heif.h + HINTS ${HEIF_PKGCONF_INCLUDE_DIRS} ${HEIF_PKGCONF_INCLUDEDIR} + PATH_SUFFIXES heif + ) + + find_library(HEIF_LIBRARY + NAMES heif + HINTS ${HEIF_PKGCONF_LIBRARY_DIRS} ${HEIF_PKGCONF_LIBDIR} + ) + + set(HEIF_PROCESS_LIBS HEIF_LIBRARY) + set(HEIF_PROCESS_INCLUDES HEIF_INCLUDE_DIR) + libfind_process(HEIF) + +else() + find_path(HEIF_INCLUDE_DIR + NAMES heif.h + ) + + find_library ( + HEIF_LIBRARY + NAMES libheif libheif + DOC "Libraries to link against for HEIF Support" + ) + + if (HEIF_LIBRARY) + set(HEIF_LIBRARY_DIR ${HEIF_LIBRARY}) + endif() + + set (HEIF_LIBRARIES ${HEIF_LIBRARY}) + + if(HEIF_INCLUDE_DIR AND HEIF_LIBRARY_DIR) + set (HEIF_FOUND true) + endif() +endif() + diff --git a/cmake/modules/FindLibWpd.cmake b/cmake/modules/FindLibWpd.cmake deleted file mode 100644 index ed9bc26067..0000000000 --- a/cmake/modules/FindLibWpd.cmake +++ /dev/null @@ -1,26 +0,0 @@ -# - Try to find the libwpd (WordPerfect library) -# Once done this will define -# -# LIBWPD_FOUND - system has LIBWPD -# LIBWPD_INCLUDE_DIRS - the LIBWPD include directory -# LIBWPD_LIBRARIES - Link these to use LIBWPD -# LIBWPD_DEFINITIONS - Compiler switches required for using LIBWPD -# - -include(LibFindMacros) -libfind_pkg_check_modules(WPD_PKGCONF libwpd-0.10) - -find_path(WPD_INCLUDE_DIR - NAMES libwpd/libwpd.h - HINTS ${WPD_PKGCONF_INCLUDE_DIRS} ${WPD_PKGCONF_INCLUDEDIR} - PATH_SUFFIXES libwpd-0.10 -) - -find_library(WPD_LIBRARY - NAMES wpd libwpd wpd-0.10 libwpd-0.10 - HINTS ${WPD_PKGCONF_LIBRARY_DIRS} ${WPD_PKGCONF_LIBDIR} -) - -set(LIBWPD_PROCESS_LIBS WPD_LIBRARY) -set(LIBWPD_PROCESS_INCLUDES WPD_INCLUDE_DIR) -libfind_process(LIBWPD) diff --git a/cmake/modules/FindLibWpg.cmake b/cmake/modules/FindLibWpg.cmake deleted file mode 100644 index 16c2a1cde8..0000000000 --- a/cmake/modules/FindLibWpg.cmake +++ /dev/null @@ -1,29 +0,0 @@ -# - Try to find LibWpg -# Once done this will define -# -# LIBWPG_FOUND - libwpg is available -# LIBWPG_INCLUDE_DIRS - include directory, e.g. /usr/include -# LIBWPG_LIBRARIES - the libraries needed to use LibWpg -# LIBWPG_DEFINITIONS - Compiler switches required for using LibWpg -# -# Copyright (C) 2007 Ariya Hidayat -# Redistribution and use is allowed according to the terms of the BSD license. - -include(LibFindMacros) -libfind_package(LIBWPG LibWpd) -libfind_pkg_check_modules(LIBWPG_PKGCONF libwpg-0.3) - -find_path(LIBWPG_INCLUDE_DIR - NAMES libwpg/libwpg.h - HINTS ${LIBWPG_PKGCONF_INCLUDE_DIRS} ${LIBWPG_PKGCONF_INCLUDEDIR} - PATH_SUFFIXES libwpg-0.3 -) - -find_library(LIBWPG_LIBRARY - NAMES wpg wpg-0.3 - HINTS ${LIBWPG_PKGCONF_LIBRARY_DIRS} ${LIBWPG_PKGCONF_LIBDIR} -) - -set(LIBWPG_PROCESS_LIBS LIBWPG_LIBRARY LIBWPD_LIBRARIES) -set(LIBWPG_PROCESS_INCLUDES LIBWPG_INCLUDE_DIR LIBWPD_INCLUDE_DIRS) -libfind_process(LIBWPG) diff --git a/cmake/modules/FindLibWps.cmake b/cmake/modules/FindLibWps.cmake deleted file mode 100644 index f8c82256fd..0000000000 --- a/cmake/modules/FindLibWps.cmake +++ /dev/null @@ -1,28 +0,0 @@ -# - Try to find LibWps -# Once done this will define -# -# LIBWPS_FOUND - libwps is available -# LIBWPS_INCLUDE_DIRS - include directory, e.g. /usr/include -# LIBWPS_LIBRARIES - the libraries needed to use LibWps -# -# Copyright (C) 2013 Yue Liu -# Redistribution and use is allowed according to the terms of the BSD license. - -include(LibFindMacros) -libfind_package(LIBWPS LibWpd) -libfind_pkg_check_modules(LIBWPS_PKGCONF libwps-0.3) - -find_path(LIBWPS_INCLUDE_DIR - NAMES libwps/libwps.h - HINTS ${LIBWPS_PKGCONF_INCLUDE_DIRS} ${LIBWPS_PKGCONF_INCLUDEDIR} - PATH_SUFFIXES libwps-0.3 -) - -find_library(LIBWPS_LIBRARY - NAMES wps wps-0.3 - HINTS ${LIBWPS_PKGCONF_LIBRARY_DIRS} ${LIBWPS_PKGCONF_LIBDIR} -) - -set(LIBWPS_PROCESS_LIBS LIBWPS_LIBRARY LIBWPD_LIBRARIES) -set(LIBWPS_PROCESS_INCLUDES LIBWPS_INCLUDE_DIR LIBWPD_INCLUDE_DIRS) -libfind_process(LIBWPS) diff --git a/krita/data/aboutdata/aboutdata.qrc b/krita/data/aboutdata/aboutdata.qrc index 9eb9f37255..9dd1b78735 100644 --- a/krita/data/aboutdata/aboutdata.qrc +++ b/krita/data/aboutdata/aboutdata.qrc @@ -1,9 +1,10 @@ backers.txt credits.txt developers.txt LICENSE + libraries.txt diff --git a/krita/data/aboutdata/libraries.txt b/krita/data/aboutdata/libraries.txt new file mode 100644 index 0000000000..09a21f7a7c --- /dev/null +++ b/krita/data/aboutdata/libraries.txt @@ -0,0 +1,32 @@ +Boost,https://www.boost.org,Boost Software License - Version 1.0 +DrMingw,https://github.com/jrfonseca/drmingw,LGPLv2.1 +Eigen3,http://eigen.tuxfamily.org,MPL2 +Exiv2,http://www.exiv2.org,GPLv2 +Expat,https://github.com/libexpat/libexpat,MIT +#ffmpeg,https://www.ffmpeg.org,LGPL2.1+ +fftw3,http://www.fftw.org/,GPLv2+ +fontconfig,https://www.freedesktop.org/wiki/Software/fontconfig/,BSD +freetype,https://www.freetype.org/,FTL or GPLv2 +gettext,https://www.gnu.org/software/gettext/,GPLv3 +giflib,http://giflib.sourceforge.net/,BSD +gmic,http://gmic.eu/,CeCILLv2.1 +gmic-qt,http://gmic.eu/,GPLv3 +GNU Scientific Library,http://www.gnu.org/software/gsl,GPLv3 +libheif,https://github.com/strukturag/libheif,LGPLv3 +iconv,https://www.gnu.org/software/libiconv/,LGPLv2 or GPLv3 +ilmbase,http://www.openexr.com,Modified BSD +libjpeg-turbo,http://www.libjpeg-turbo.org,BSD +littlecms2,http://www.littlecms.com,MIT +libraw,http://www.libraw.org,LGPLv2.1 or CDDL 1.0 +OpenColorIO,http://www.opencolorio.org,BSD +OpenEXR,http://www.openexr.com,Modified BSD +libpng,http://www.libpng.org,libpng license +poppler,http://poppler.freedesktop.org,GPLv2 and GPLv3 +PyQt,https://www.riverbankcomputing.com/software/pyqt/download5,GPLv3 +Python,http://www.python.org,Python Software Foundation License v2 +Qt,https://www.qt.io,GPLv2 + GPLv3 + LGPLv2.1 + LGPLv3 +SIP,https://www.riverbankcomputing.com/software/sip/download,GPLv3 +libiff,http://www.remotesensing.org/libtiff,BSD +Vc,https://github.com/VcDevel/Vc,BSD +zlib,http://www.zlib.net/,BSD +KDE Frameworks 5,https://www.kde.org,LGPLv+ diff --git a/krita/data/actions/ArtisticTextTool.action b/krita/data/actions/ArtisticTextTool.action deleted file mode 100644 index f372072860..0000000000 --- a/krita/data/actions/ArtisticTextTool.action +++ /dev/null @@ -1,106 +0,0 @@ - - - - Artistic Text Tool - - Subscript - - Subscript - format-text-subscript - - - true - Subscript - - - Detach Path - - Detach Path - artistictext-detach-path - - - false - Detach Path - - - Italic text - - Italic text - format-text-italic - - - true - Italic text - - - Anchor at Middle - - Anchor at Middle - format-justify-center - - - true - Anchor at Middle - - - Anchor at End - - Anchor at End - format-justify-right - - - true - Anchor at End - - - Convert to Path - - Convert to Path - pathshape - - - false - Convert to Path - - - Anchor at Start - - Anchor at Start - format-justify-left - - - true - Anchor at Start - - - Bold text - - Bold text - format-text-bold - - - true - Bold text - - - Deselect - Ctrl+Shift+A - Deselect - - - - false - Dese&lect - - - Superscript - - Superscript - format-text-superscript - - - true - Superscript - - - diff --git a/krita/data/actions/CMakeLists.txt b/krita/data/actions/CMakeLists.txt index 73f290a9cd..18914973b4 100644 --- a/krita/data/actions/CMakeLists.txt +++ b/krita/data/actions/CMakeLists.txt @@ -1,8 +1,6 @@ install( FILES - ArtisticTextTool.action InteractionTool.action PathTool.action - TextTool.action ConnectionTool.action MoveTool.action DESTINATION ${DATA_INSTALL_DIR}/krita/actions) diff --git a/krita/data/actions/TextTool.action b/krita/data/actions/TextTool.action deleted file mode 100644 index 16f721677a..0000000000 --- a/krita/data/actions/TextTool.action +++ /dev/null @@ -1,416 +0,0 @@ - - - - Text Tool - - Align Right - Ctrl+Alt+R - Align Right - format-justify-right - - - false - Align Right - - - Decrease Indent - - Decrease Indent - - - - false - Decrease Indent - - - Paragraph - Ctrl+Alt+P - Change paragraph margins, text flow, borders, bullets, numbering etc. - - <p>Change paragraph margins, text flow, borders, bullets, numbering etc.</p><p>Select text in multiple paragraphs to change the formatting of all selected paragraphs.</p><p>If no text is selected, the paragraph where the cursor is located will be changed.</p> - - false - Paragraph... - - - Text Color - - Text Color... - format-text-color - - - false - Text Color - - - Special Character - Alt+Shift+C - Insert one or more symbols or characters not found on the keyboard - character-set - Insert one or more symbols or characters not found on the keyboard. - - false - Special Character... - - - Shrink To Fit - - Shrink To Fit - zoom-fit-best - - - false - Shrink To Fit - - - Styles Debug - Ctrl+Alt+Shift+S - Styles Debug - - - - false - Styles Debug - - - Increase Indent - - Increase Indent - - - - false - Increase Indent - - - Underline - Ctrl+U - Underline - format-text-underline - - - false - Underline - - - Grow To Fit Width - - Grow To Fit Width - zoom-fit-best - - - false - Grow To Fit Width - - - Subscript - Ctrl+Shift+B - Subscript - format-text-subscript - - - false - Subscript - - - Insert Non-Breaking Hyphen - Ctrl+Shift+- - Insert Non-Breaking Hyphen - - - - false - Insert Non-Breaking Hyphen - - - Align Left - - Align Left - format-justify-left - - - false - Align Left - - - Insert Comment - Ctrl+Shift+C - Insert Comment - - - - false - Insert Comment - - - Strikethrough - - Strikethrough - format-text-strikethrough - - - false - Strikethrough - - - Configure current section - - Configure current section - configure - - - false - Configure current section - - - Change text direction - Ctrl+Shift+D - Change writing direction - format-text-direction-rtl - - - false - Change text direction - - - Insert Non-Breaking Space - Ctrl+Space - Insert Non-Breaking Space - - - - false - Insert Non-Breaking Space - - - Insert Soft Hyphen - - Insert Soft Hyphen - - - - false - Insert Soft Hyphen - - - Variable - - Variable - - - - false - Variable - - - Bullet list - - Bullet list - format-list-unordered - - - false - Bullet list - - - Insert Index - Ctrl+T - Insert Index - - - - false - Insert Index - - - Align Center - Ctrl+Alt+C - Align Center - format-justify-center - - - false - Align Center - - - Background - - Background Color... - format-fill-color - - - false - Background - - - Decrease Font Size - Ctrl+< - Decrease Font Size - - - - false - Decrease Font Size - - - Insert new section - - Insert new section - insert-text - - - false - Insert new section - - - Insert Paragraph between sections - - Insert Paragraph between sections - split - - - - - - - Font Size - - Font Size - - - - false - Font Size - - - Italic - Ctrl+I - Italic - format-text-italic - - - false - Italic - - - Bold - Ctrl+B - Bold - format-text-bold - - - false - Bold - - - Increase Font Size - Ctrl+> - Increase Font Size - - - - false - Increase Font Size - - - Superscript - Ctrl+Shift+P - Superscript - format-text-superscript - - - false - Superscript - - - Select All - Ctrl+A - Select All - edit-select-all - - - false - Select &All - - - Numbered list - - Numbered list - format-list-ordered - - - false - Numbered list - - - Style Manager - Ctrl+Alt+S - Change attributes of styles - - <p>Change font and paragraph attributes of styles.</p><p>Multiple styles can be changed using the dialog box.</p> - - false - Style Manager... - - - Align Block - Ctrl+Alt+R - Align Block - format-justify-fill - - - false - Align Block - - - Grow To Fit Height - - Grow To Fit Height - zoom-fit-best - - - false - Grow To Fit Height - - - Paragraph Debug - Ctrl+Alt+Shift+P - Paragraph Debug - - - - false - Paragraph Debug - - - Font Family - - Font Family - - - - false - Font Family - - - Font - Ctrl+Alt+F - Change character size, font, boldface, italics etc. - - Change the attributes of the currently selected characters. - - false - Font... - - - Repaint - - Repaint - view-refresh - - - false - Repaint - - - diff --git a/krita/krita.action b/krita/krita.action index 701eca9471..288fd81458 100644 --- a/krita/krita.action +++ b/krita/krita.action @@ -1,3501 +1,3456 @@ General Open Resources Folder Opens a file browser at the location Krita saves resources such as brushes to. Opens a file browser at the location Krita saves resources such as brushes to. Open Resources Folder 0 0 false Cleanup removed files... Cleanup removed files Cleanup removed files 0 0 false C&ascade Cascade Cascade 10 0 false &Tile Tile Tile 10 0 false Create Resource Bundle... Create Resource Bundle Create Resource Bundle 0 0 false Show File Toolbar Show File Toolbar Show File Toolbar false Show color selector Show color selector Show color selector Shift+I false Show MyPaint shade selector Show MyPaint shade selector Show MyPaint shade selector Shift+M false Show minimal shade selector Show minimal shade selector Show minimal shade selector Shift+N false Show color history Show color history Show color history H false Show common colors Show common colors Show common colors U false Show Tool Options Show Tool Options Show Tool Options \ false Show Brush Editor Show Brush Editor Show Brush Editor F5 false Show Brush Presets Show Brush Presets Show Brush Presets F6 false Toggle Tablet Debugger Toggle Tablet Debugger Toggle Tablet Debugger 0 0 Ctrl+Shift+T false Show system information for bug reports. Show system information for bug reports. Show system information for bug reports. false Rename Composition... Rename Composition Rename Composition 0 0 false Update Composition Update Composition Update Composition 0 0 false Use multiple of 2 for pixel scale Use multiple of 2 for pixel scale Use multiple of 2 for pixel scale Use multiple of 2 for pixel scale 1 0 true + + + &Invert Selection + + Invert current selection + Invert Selection + 10000000000 + 100 + Ctrl+Shift+I + false + + + Painting lightness-increase Make brush color lighter Make brush color lighter Make brush color lighter 0 0 L false lightness-decrease Make brush color darker Make brush color darker Make brush color darker 0 0 K false Make brush color more saturated Make brush color more saturated Make brush color more saturated false Make brush color more desaturated Make brush color more desaturated Make brush color more desaturated false Shift brush color hue clockwise Shift brush color hue clockwise Shift brush color hue clockwise false Shift brush color hue counter-clockwise Shift brush color hue counter-clockwise Shift brush color hue counter-clockwise false Make brush color more red Make brush color more red Make brush color more red false Make brush color more green Make brush color more green Make brush color more green false Make brush color more blue Make brush color more blue Make brush color more blue false Make brush color more yellow Make brush color more yellow Make brush color more yellow false opacity-increase Increase opacity Increase opacity Increase opacity 0 0 O false opacity-decrease Decrease opacity Decrease opacity Decrease opacity 0 0 I false draw-eraser Set eraser mode Set eraser mode Set eraser mode 10000 0 E true view-refresh Reload Original Preset Reload Original Preset Reload Original Preset 10000 false transparency-unlocked Preserve Alpha Preserve Alpha Preserve Alpha 10000 true transform_icons_penPressure Use Pen Pressure Use Pen Pressure Use Pen Pressure 10000 true symmetry-horizontal Horizontal Mirror Tool Horizontal Mirror Tool Horizontal Mirror Tool 0 true symmetry-vertical Vertical Mirror Tool Vertical Mirror Tool Vertical Mirror Tool 0 true Hide Mirror X Line Hide Mirror X Line Hide Mirror X Line 10000 true Hide Mirror Y Line Hide Mirror Y Line Hide Mirror Y Line 10000 true Lock Lock X Line Lock X Line 10000 true Lock Y Line Lock Y Line Lock Y Line 10000 true Move to Canvas Center Move to Canvas Center X Move to Canvas Center X 10000 false Move to Canvas Center Y Move to Canvas Center Y Move to Canvas Center Y 10000 false - - - &Invert Selection - - Invert current selection - Invert Selection - 10000000000 - 100 - Ctrl+Shift+I - false - - &Toggle Selection Display Mode Toggle Selection Display Mode Toggle Selection Display Mode 0 0 false Next Favourite Preset Next Favourite Preset Next Favourite Preset , false Previous Favourite Preset Previous Favourite Preset Previous Favourite Preset . false preset-switcher Switch to Previous Preset Switch to Previous Preset Switch to Previous Preset / false Hide Brushes and Stuff Toolbar Hide Brushes and Stuff Toolbar Hide Brushes and Stuff Toolbar true Reset Foreground and Background Color Reset Foreground and Background Color Reset Foreground and Background Color D false Swap Foreground and Background Color Swap Foreground and Background Color Swap Foreground and Background Color X false smoothing-weighted Brush Smoothing: Weighted Brush Smoothing: Weighted Brush Smoothing: Weighted false smoothing-no Brush Smoothing: Disabled Brush Smoothing: Disabled Brush Smoothing: Disabled false smoothing-stabilizer Brush Smoothing: Stabilizer Brush Smoothing: Stabilizer Brush Smoothing: Stabilizer false brushsize-decrease Decrease Brush Size Decrease Brush Size Decrease Brush Size 0 0 [ false smoothing-basic Brush Smoothing: Basic Brush Smoothing: Basic Brush Smoothing: Basic false brushsize-increase Increase Brush Size Increase Brush Size Increase Brush Size 0 0 ] false Toggle Assistant Toggle Assistant ToggleAssistant Ctrl+Shift+L true Undo Polygon Selection Points Undo Polygon Selection Points Undo Polygon Selection Points Shift+Z false Fill with Foreground Color (Opacity) Fill with Foreground Color (Opacity) Fill with Foreground Color (Opacity) 10000 1 Ctrl+Shift+Backspace false Fill with Background Color (Opacity) Fill with Background Color (Opacity) Fill with Background Color (Opacity) 10000 1 Ctrl+Backspace false Fill with Pattern (Opacity) Fill with Pattern (Opacity) Fill with Pattern (Opacity) 10000 1 false Convert &to Shape Convert to Shape Convert to Shape 10000000000 0 false &Select Opaque Select Opaque Select Opaque 100000 100 false &Show Global Selection Mask Shows global selection as a usual selection mask in <interface>Layers</interface> docker Show Global Selection Mask 100000 100 true Filters color-to-alpha &Color to Alpha... Color to Alpha Color to Alpha 10000 0 false &Top Edge Detection Top Edge Detection Top Edge Detection 10000 0 false &Index Colors... Index Colors Index Colors 10000 0 false Emboss Horizontal &Only Emboss Horizontal Only Emboss Horizontal Only 10000 0 false D&odge Dodge Dodge 10000 0 false &Sharpen Sharpen Sharpen 10000 0 false B&urn Burn Burn 10000 0 false &Mean Removal Mean Removal Mean Removal 10000 0 false &Gaussian Blur... Gaussian Blur Gaussian Blur 10000 0 false Emboss &in All Directions Emboss in All Directions Emboss in All Directions 10000 0 false &Small Tiles... Small Tiles Small Tiles 10000 0 false &Levels... Levels Levels 10000 0 Ctrl+L false &Sobel... Sobel Sobel 10000 0 false &Wave... Wave Wave 10000 0 false &Motion Blur... Motion Blur Motion Blur 10000 0 false &Color Adjustment curves... Color Adjustment curves Color Adjustment curves 10000 0 Ctrl+M false Pi&xelize... Pixelize Pixelize 10000 0 false Emboss (&Laplacian) Emboss (Laplacian) Emboss (Laplacian) 10000 0 false &Left Edge Detection Left Edge Detection Left Edge Detection 10000 0 false &Blur... Blur Blur 10000 0 false &Raindrops... Raindrops Raindrops 10000 0 false &Bottom Edge Detection Bottom Edge Detection Bottom Edge Detection 10000 0 false &Random Noise... Random Noise Random Noise 10000 0 false &Brightness/Contrast curve... Brightness/Contrast curve Brightness/Contrast curve 10000 0 false - Colo&r Balance.. + Colo&r Balance... - Color Balance.. - Color Balance.. + Color Balance + Color Balance 10000 0 Ctrl+B false &Phong Bumpmap... Phong Bumpmap Phong Bumpmap 10000 0 false &Desaturate Desaturate Desaturate 10000 0 Ctrl+Shift+U false Color &Transfer... Color Transfer Color Transfer 10000 0 false Emboss &Vertical Only Emboss Vertical Only Emboss Vertical Only 10000 0 false &Lens Blur... Lens Blur Lens Blur 10000 0 false M&inimize Channel Minimize Channel Minimize Channel 10000 0 false M&aximize Channel Maximize Channel Maximize Channel 10000 0 false &Oilpaint... Oilpaint Oilpaint 10000 0 false &Right Edge Detection Right Edge Detection Right Edge Detection 10000 0 false &Auto Contrast Auto Contrast Auto Contrast 10000 0 false &Round Corners... Round Corners Round Corners 10000 0 false &Unsharp Mask... Unsharp Mask Unsharp Mask 10000 0 false &Emboss with Variable Depth... Emboss with Variable Depth Emboss with Variable Depth 10000 0 false Emboss &Horizontal && Vertical Emboss Horizontal & Vertical Emboss Horizontal & Vertical 10000 0 false Random &Pick... Random Pick Random Pick 10000 0 false &Gaussian Noise Reduction... Gaussian Noise Reduction Gaussian Noise Reduction 10000 0 false &Posterize... Posterize Posterize 10000 0 false &Wavelet Noise Reducer... Wavelet Noise Reducer Wavelet Noise Reducer 10000 0 false &HSV Adjustment... HSV Adjustment HSV Adjustment 10000 0 Ctrl+U false Tool Shortcuts Dynamic Brush Tool Dynamic Brush Tool Dynamic Brush Tool false Crop Tool Crop the image to an area Crop the image to an area C false Polygon Tool Polygon Tool. Shift-mouseclick ends the polygon. Polygon Tool. Shift-mouseclick ends the polygon. false Rectangle Tool Rectangle Tool Rectangle Tool false Multibrush Tool Multibrush Tool Multibrush Tool Q false Lazy Brush Tool Lazy Brush Tool Lazy Brush Tool Smart Patch Tool Smart Patch Tool Smart Patch Tool Pan Tool Pan Tool Pan Tool Select Shapes Tool Select Shapes Tool Select Shapes Tool false Color Picker Select a color from the image or current layer Select a color from the image or current layer P false - - - Text Editing Tool - - Text editing - Text editing - - false - - Outline Selection Tool Outline Selection Tool Outline Selection Tool false - - - Artistic Text Tool - - Artistic text editing - Artistic text editing - - false - - Bezier Curve Selection Tool Select a Bezier Curve Selection Tool false Similar Color Selection Tool Select a Similar Color Selection Tool false Fill Tool Fill a contiguous area of color with a color, or fill a selection. Fill a contiguous area of color with a color, or fill a selection. F false Line Tool Line Tool Line Tool false Freehand Path Tool Freehand Path Tool Freehand Path Tool false Bezier Curve Tool - Bezier Curve Tool. Shift-mouseclick ends the curve. - Bezier Curve Tool. Shift-mouseclick ends the curve. + Bezier Curve Tool. Shift-mouseclick or double-click ends the curve. + Bezier Curve Tool. Shift-mouseclick or double-click ends the curve. false Ellipse Tool Ellipse Tool Ellipse Tool false Freehand Brush Tool Freehand Brush Tool Freehand Brush Tool B false Create object Create object Create object false Elliptical Selection Tool Elliptical Selection Tool Elliptical Selection Tool J false Contiguous Selection Tool Contiguous Selection Tool Contiguous Selection Tool false Pattern editing Pattern editing Pattern editing false Review Review Review false Draw a gradient. Draw a gradient. Draw a gradient. G false Polygonal Selection Tool Polygonal Selection Tool Polygonal Selection Tool false Measurement Tool Measure the distance between two points Measure the distance between two points false Rectangular Selection Tool Rectangular Selection Tool Rectangular Selection Tool Ctrl+R false Move Tool Move a layer Move a layer T false Vector Image Tool Vector Image (EMF/WMF/SVM/SVG) tool Vector Image (EMF/WMF/SVM/SVG) tool false Calligraphy Calligraphy Calligraphy false Path editing Path editing Path editing false Zoom Tool Zoom Tool Zoom Tool false Polyline Tool Polyline Tool. Shift-mouseclick ends the polyline. Polyline Tool. Shift-mouseclick ends the polyline. false Transform Tool Transform a layer or a selection Transform a layer or a selection Ctrl+T false Assistant Tool Assistant Tool Assistant Tool false Text tool Text tool Text tool false Gradient Editing Tool Gradient editing Gradient editing false Blending Modes Select Normal Blending Mode Select Normal Blending Mode Select Normal Blending Mode 0 0 Alt+Shift+N false Select Dissolve Blending Mode Select Dissolve Blending Mode Select Dissolve Blending Mode 0 0 Alt+Shift+I false Select Behind Blending Mode Select Behind Blending Mode Select Behind Blending Mode 0 0 Alt+Shift+Q false Select Clear Blending Mode Select Clear Blending Mode Select Clear Blending Mode 0 0 Alt+Shift+R false Select Darken Blending Mode Select Darken Blending Mode Select Darken Blending Mode 0 0 Alt+Shift+K false Select Multiply Blending Mode Select Multiply Blending Mode Select Multiply Blending Mode 0 0 Alt+Shift+M false Select Color Burn Blending Mode Select Color Burn Blending Mode Select Color Burn Blending Mode 0 0 Alt+Shift+B false Select Linear Burn Blending Mode Select Linear Burn Blending Mode Select Linear Burn Blending Mode 0 0 Alt+Shift+A false Select Lighten Blending Mode Select Lighten Blending Mode Select Lighten Blending Mode 0 0 Alt+Shift+G false Select Screen Blending Mode Select Screen Blending Mode Select Screen Blending Mode 0 0 Alt+Shift+S false Select Color Dodge Blending Mode Select Color Dodge Blending Mode Select Color Dodge Blending Mode 0 0 Alt+Shift+D false Select Linear Dodge Blending Mode Select Linear Dodge Blending Mode Select Linear Dodge Blending Mode 0 0 Alt+Shift+W false Select Overlay Blending Mode Select Overlay Blending Mode Select Overlay Blending Mode 0 0 Alt+Shift+O false Select Hard Overlay Blending Mode Select Hard Overlay Blending Mode Select Hard Overlay Blending Mode 0 0 Alt+Shift+P false Select Soft Light Blending Mode Select Soft Light Blending Mode Select Soft Light Blending Mode 0 0 Alt+Shift+F false Select Hard Light Blending Mode Select Hard Light Blending Mode Select Hard Light Blending Mode 0 0 Alt+Shift+H false Select Vivid Light Blending Mode Select Vivid Light Blending Mode Select Vivid Light Blending Mode 0 0 Alt+Shift+V false Select Linear Light Blending Mode Select Linear Light Blending Mode Select Linear Light Blending Mode 0 0 Alt+Shift+J false Select Pin Light Blending Mode Select Pin Light Blending Mode Select Pin Light Blending Mode 0 0 Alt+Shift+Z false Select Hard Mix Blending Mode Select Hard Mix Blending Mode Select Hard Mix Blending Mode 0 0 Alt+Shift+L false Select Difference Blending Mode Select Difference Blending Mode Select Difference Blending Mode 0 0 Alt+Shift+E false Select Exclusion Blending Mode Select Exclusion Blending Mode Select Exclusion Blending Mode 0 0 Alt+Shift+X false Select Hue Blending Mode Select Hue Blending Mode Select Hue Blending Mode 0 0 Alt+Shift+U false Select Saturation Blending Mode Select Saturation Blending Mode Select Saturation Blending Mode 0 0 Alt+Shift+T false Select Color Blending Mode Select Color Blending Mode Select Color Blending Mode 0 0 Alt+Shift+C false Select Luminosity Blending Mode Select Luminosity Blending Mode Select Luminosity Blending Mode 0 0 Alt+Shift+Y false Animation Previous frame Move to previous frame Move to previous frame 1 0 false Next frame Move to next frame Move to next frame 1 0 false Play / pause animation Play / pause animation Play / pause animation 1 0 false addblankframe Create Blank Frame Add blank frame Add blank frame 100000 0 false addduplicateframe Create Duplicate Frame Add duplicate frame Add duplicate frame 100000 0 false Toggle onion skin Toggle onion skin Toggle onion skin 100000 0 false Previous Keyframe false Next Keyframe false First Frame false Last Frame false Auto Frame Mode true true Show in Timeline true - - - Insert Keyframe Right - - Insert keyframes to the right of selection moving the tail of animation to the right - - 100000 - 0 - - false - - - - + Insert Keyframe Left - Insert keyframes to the left of selection moving the tail of animation to the right + Insert keyframes to the left of selection, moving the tail of animation to the right. 100000 0 false - + - Insert N Keyframes Right + Insert Keyframe Right - Insert several keyframes to the right of selection moving the tail of animation to the right + Insert keyframes to the right of selection, moving the tail of animation to the right. 100000 0 false - + - Insert N Keyframes Left + Insert Multiple Keyframes - Insert several keyframes to the left of selection moving the tail of animation to the right + Insert several keyframes based on user parameters. 100000 0 false Remove Frame and Pull Remove keyframes moving the tail of animation to the left 100000 0 false deletekeyframe Remove Keyframe Remove keyframes without moving anything around 100000 0 false - - - Insert Column Right - - Insert column to the right of selection moving the tail of animation to the right - - 100000 - 0 - - false - - - - + Insert Column Left - Insert column to the left of selection moving the tail of animation to the right + Insert column to the left of selection, moving the tail of animation to the right 100000 0 false - + - Insert N Columns Right + Insert Column Right - Insert several columns to the right of selection moving the tail of animation to the right + Insert column to the right of selection, moving the tail of animation to the right 100000 0 false - + - Insert N Columns Left + Insert Multiple Columns - Insert several columns to the left of selection moving the tail of animation to the right + Insert several columns based on user parameters. 100000 0 false Remove Column and Pull Remove columns moving the tail of animation to the left 100000 0 false Remove Column Remove columns without moving anything around 100000 0 false Insert Hold Frame Insert a hold frame after every keyframe 100000 0 false - + - Insert N Hold Frames + Insert Multiple Hold Frames Insert N hold frames after every keyframe 100000 0 false Remove Hold Frame Remove a hold frame after every keyframe 100000 0 false - + - Remove N Hold Frames + Remove Multiple Hold Frames Remove N hold frames after every keyframe 100000 0 false Insert Hold Column Insert a hold column into the frame at the current position 100000 0 false - + - Insert N Hold Columns + Insert Multiple Hold Columns Insert N hold columns into the frame at the current position 100000 0 false Remove Hold Column Remove a hold column from the frame at the current position 100000 0 false - + - Remove N Hold Columns + Remove Multiple Hold Columns Remove N hold columns from the frame at the current position 100000 0 false Mirror Frames Mirror frames' position 100000 0 false Mirror Columns Mirror columns' position 100000 0 false Copy to Clipboard Copy frames to clipboard 100000 0 false Cut to Clipboard Cut frames to clipboard 100000 0 false Paste from Clipboard Paste frames from clipboard 100000 0 false Copy Columns to Clipboard Copy columns to clipboard 100000 0 false Cut Columns to Clipboard Cut columns to clipboard 100000 0 false Paste Columns from Clipboard Paste columns from clipboard 100000 0 false Set Start Time 100000 0 false Set End Time 100000 0 false Update Playback Range 100000 0 false Layers Activate next layer Activate next layer Activate next layer 1000 0 PgUp false Activate previous layer Activate previous layer Activate previous layer 1000 0 PgDown false Activate previously selected layer Activate previously selected layer Activate previously selected layer 1000 0 ; false groupLayer &Group Layer Group Layer Group Layer 1000 0 false cloneLayer &Clone Layer Clone Layer Clone Layer 1000 0 false vectorLayer &Vector Layer Vector Layer Vector Layer 1000 0 false filterLayer &Filter Layer... Filter Layer Filter Layer 1000 0 false fillLayer &Fill Layer... Fill Layer Fill Layer 1000 0 false fileLayer &File Layer... File Layer File Layer 1000 0 false transparencyMask &Transparency Mask Transparency Mask Transparency Mask 100000 0 false filterMask &Filter Mask... Filter Mask Filter Mask 100000 0 false filterMask &Colorize Mask Colorize Mask Colorize Mask 100000 0 false transformMask &Transform Mask... Transform Mask Transform Mask 100000 0 false selectionMask &Local Selection Local Selection Local Selection 100000 0 false view-filter &Isolate Layer Isolate Layer Isolate Layer 1000 0 true layer-locked &Toggle layer lock Toggle layer lock Toggle layer lock 1000 0 false visible Toggle layer &visibility Toggle layer visibility Toggle layer visibility 1000 0 false transparency-locked Toggle layer &alpha Toggle layer alpha Toggle layer alpha 1000 0 false transparency-enabled Toggle layer alpha &inheritance Toggle layer alpha inheritance Toggle layer alpha inheritance 1000 0 false paintLayer &Paint Layer Paint Layer Paint Layer 1000 0 Insert false &New Layer From Visible New layer from visible New layer from visible 1000 0 false duplicatelayer &Duplicate Layer or Mask Duplicate Layer or Mask Duplicate Layer or Mask 1000 0 Ctrl+J false &Cut Selection to New Layer Cut Selection to New Layer Cut Selection to New Layer 100000000 1 Ctrl+Shift+J false Copy &Selection to New Layer Copy Selection to New Layer Copy Selection to New Layer 100000000 0 Ctrl+Alt+J false Copy Layer Copy layer to clipboard Copy layer to clipboard 1000 0 false Cut Layer Cut layer to clipboard Cut layer to clipboard 1000 0 false Paste Layer Paste layer from clipboard Paste layer from clipboard 1000 0 false Quick Group Create a group layer containing selected layers Quick Group - 100000 + 1000 0 Ctrl+G false Quick Ungroup Remove grouping of the layers or remove one layer out of the group Quick Ungroup 100000 0 Ctrl+Alt+G false Quick Clipping Group Group selected layers and add a layer with clipped alpha channel Quick Clipping Group 100000 0 Ctrl+Shift+G false All Layers Select all layers Select all layers 10000 0 false Visible Layers Select all visible layers Select all visible layers 10000 0 false Locked Layers Select all locked layers Select all locked layers 10000 0 false Invisible Layers Select all invisible layers Select all invisible layers 10000 0 false Unlocked Layers Select all unlocked layers Select all unlocked layers 10000 0 false document-save &Save Layer/Mask... Save Layer/Mask Save Layer/Mask 1000 0 false document-save Save Vector Layer as SVG... Save Vector Layer as SVG Save Vector Layer as SVG 1000 0 false document-save Save &Group Layers... Save Group Layers Save Group Layers 100000 0 false Convert group to &animated layer Convert child layers into animation frames Convert child layers into animation frames 100000 0 false fileLayer to &File Layer Saves out the layers into a new image and then references that image. Convert to File Layer 100000 0 false I&mport Layer... Import Layer Import Layer 100000 0 false paintLayer &as Paint Layer... as Paint Layer as Paint Layer 1000 0 false transparencyMask as &Transparency Mask... as Transparency Mask as Transparency Mask 1000 0 false filterMask as &Filter Mask... as Filter Mask as Filter Mask 1000 0 false selectionMask as &Selection Mask... as Selection Mask as Selection Mask 1000 0 false paintLayer to &Paint Layer to Paint Layer to Paint Layer 1000 0 false transparencyMask to &Transparency Mask to Transparency Mask to Transparency Mask 1000 0 false filterMask to &Filter Mask... to Filter Mask to Filter Mask 1000 0 false selectionMask to &Selection Mask to Selection Mask to Selection Mask 1000 0 false transparencyMask &Alpha into Mask Alpha into Mask Alpha into Mask 100000 10 false transparency-enabled &Write as Alpha Write as Alpha Write as Alpha 1000000 1 false document-save &Save Merged... Save Merged Save Merged 1000000 0 false split-layer Split Layer... Split Layer Split Layer 1000 0 false Wavelet Decompose ... Wavelet Decompose Wavelet Decompose 1000 1 false symmetry-horizontal Mirror Layer Hori&zontally Mirror Layer Horizontally Mirror Layer Horizontally 1000 1 false symmetry-vertical Mirror Layer &Vertically Mirror Layer Vertically Mirror Layer Vertically 1000 1 false &Rotate Layer... Rotate Layer Rotate Layer 1000 1 false object-rotate-right Rotate &Layer 90° to the Right Rotate Layer 90° to the Right Rotate Layer 90° to the Right 1000 1 false object-rotate-left Rotate Layer &90° to the Left Rotate Layer 90° to the Left Rotate Layer 90° to the Left 1000 1 false Rotate Layer &180° Rotate Layer 180° Rotate Layer 180° 1000 1 false Scale &Layer to new Size... Scale Layer to new Size Scale Layer to new Size 100000 1 false &Shear Layer... Shear Layer Shear Layer 1000 1 false &Offset Layer... Offset Layer Offset Layer 100000 1 false Clones &Array... Clones Array Clones Array 100000 0 false &Edit metadata... Edit metadata Edit metadata 100000 1 false &Histogram... Histogram Histogram 100000 0 false &Convert Layer Color Space... Convert Layer Color Space Convert Layer Color Space 100000 1 false merge-layer-below &Merge with Layer Below Merge with Layer Below Merge with Layer Below 100000 0 Ctrl+E false &Flatten Layer Flatten Layer Flatten Layer 100000 0 false Ras&terize Layer Rasterize Layer Rasterize Layer 10000000 1 false Flatten ima&ge Flatten image Flatten image 100000 0 Ctrl+Shift+E false La&yer Style... Layer Style Layer Style 100000 1 false Move into previous group Move into previous group Move into previous group 0 0 false Move into next group Move into next group Move into next group 0 0 false Rename current layer Rename current layer Rename current layer 100000 0 F2 false deletelayer &Remove Layer Remove Layer Remove Layer 1000 1 Shift+Delete false arrowupblr Move Layer or Mask Up Move Layer or Mask Up Ctrl+PgUp false arrowdown Move Layer or Mask Down Move Layer or Mask Down Ctrl+PgDown false properties &Properties... Properties Properties 1000 1 F3 false diff --git a/krita/krita4.xmlgui b/krita/krita4.xmlgui index f55ff7f18b..907ebfe326 100644 --- a/krita/krita4.xmlgui +++ b/krita/krita4.xmlgui @@ -1,389 +1,386 @@ &File &Edit Fill Special &View &Canvas &Snap To &Image &Rotate &Layer New &Import/Export Import &Convert &Select &Group &Transform &Rotate S&plit S&plit Alpha &Select Filte&r &Tools - Scripting - - Recording + Scripts + + Macros - - - Macros Setti&ngs &Help File Brushes and Stuff diff --git a/krita/kritamenu.action b/krita/kritamenu.action index 5c5bcb41a1..151a10df76 100644 --- a/krita/kritamenu.action +++ b/krita/kritamenu.action @@ -1,1822 +1,1822 @@ File document-new &New Create new document New 0 0 Ctrl+N false document-open &Open... Open an existing document Open 0 0 Ctrl+O false document-open-recent Open &Recent Open a document which was recently opened Open Recent 1 0 false document-save &Save Save Save 1 0 Ctrl+S false document-save-as Save &As... Save document under a new name Save As 1 0 Ctrl+Shift+S false Sessions... Open session manager Sessions 0 0 false document-import Open ex&isting Document as Untitled Document... Open existing Document as Untitled Document Open existing Document as Untitled Document 0 0 false document-export E&xport... Export Export 1 0 false application-pdf &Export as PDF... Export as PDF Export as PDF 1 0 false Import animation frames... Import animation frames Import animation frames 1 0 false &Render Animation... Render Animation to GIF, Image Sequence or Video Render Animation 1000 0 false &Render Image Sequence Again Render Animation to Image Sequence Again Render Animation 1000 0 false Save Incremental &Version Save Incremental Version Save Incremental Version 1 0 Ctrl+Alt+S false Save Incremental &Backup Save Incremental Backup Save Incremental Backup 1 0 F4 false &Create Template From Image... Create Template From Image Create Template From Image 1 0 false Create Copy &From Current Image Create Copy From Current Image Create Copy From Current Image 1 0 false document-print &Print... Print document Print 1 0 Ctrl+P false document-print-preview Print Previe&w Show a print preview of document Print Preview 1 0 false configure &Document Information Document Information Document Information 1 0 false &Close All Close All Close All 1 0 Ctrl+Shift+W false C&lose Close Close 1 0 false &Quit Quit application Quit 0 0 Ctrl+Q false Edit edit-undo Undo Undo last action Undo 1 0 Ctrl+Z false edit-redo Redo Redo last undone action Redo 1 0 Ctrl+Shift+Z false edit-cut Cu&t Cut selection to clipboard Cut 0 0 Ctrl+X false edit-copy &Copy Copy selection to clipboard Copy 0 0 Ctrl+C false C&opy (sharp) Copy (sharp) Copy (sharp) 100000000 0 false Cut (&sharp) Cut (sharp) Cut (sharp) 100000000 0 false Copy &merged Copy merged Copy merged 100000000 0 Ctrl+Shift+C false edit-paste &Paste Paste clipboard content Paste 0 0 Ctrl+V false Paste at Cursor Paste at cursor Paste at cursor 0 0 Ctrl+Alt+V false Paste into &New Image Paste into New Image Paste into New Image 0 0 Ctrl+Shift+N false edit-clear C&lear Clear Clear 1 0 Del false &Fill with Foreground Color Fill with Foreground Color Fill with Foreground Color 10000 1 Shift+Backspace false Fill &with Background Color Fill with Background Color Fill with Background Color 10000 1 Backspace false F&ill with Pattern Fill with Pattern Fill with Pattern 10000 1 false Fill Special Fill with Foreground Color (Opacity) Fill with Foreground Color (Opacity) Fill with Foreground Color (Opacity) 10000 1 Ctrl+Shift+Backspace false Fill with Background Color (Opacity) Fill with Background Color (Opacity) Fill with Background Color (Opacity) 10000 1 Ctrl+Backspace false Fill with Pattern (Opacity) Fill with Pattern (Opacity) Fill with Pattern (Opacity) 10000 1 false Stro&ke selected shapes Stroke selected shapes Stroke selected shapes 1000000000 0 false Stroke Selec&tion... Stroke selection Stroke selection 10000000000 0 false Delete keyframe Delete keyframe Delete keyframe 100000 0 false Window window-new &New Window New Window New Window 0 0 false N&ext Next Next 10 0 false Previous Previous Previous false View &Show Canvas Only Show just the canvas or the whole window Show Canvas Only 0 0 Tab true view-fullscreen F&ull Screen Mode Display the window in full screen Full Screen Mode 0 0 Ctrl+Shift+F true &Wrap Around Mode Wrap Around Mode Wrap Around Mode 1 0 W true &Instant Preview Mode Instant Preview Mode Instant Preview Mode 1 0 Shift+L true Soft Proofing Turns on Soft Proofing Turns on Soft Proofing Ctrl+Y true Out of Gamut Warnings Turns on warnings for colors out of proofed gamut, needs soft proofing to be turned on. Turns on warnings for colors out of proofed gamut, needs soft proofing to be turned on. Ctrl+Shift+Y true mirror-view Mirror View Mirror View Mirror View M false zoom-original &Reset zoom Reset zoom Reset zoom 1 0 Ctrl+0 false zoom-in Zoom &In Zoom In 0 0 Ctrl++ false zoom-out Zoom &Out Zoom Out 0 0 Ctrl+- false rotate-canvas-right Rotate &Canvas Right Rotate Canvas Right Rotate Canvas Right 1 0 Ctrl+] false rotate-canvas-left Rotate Canvas &Left Rotate Canvas Left Rotate Canvas Left 1 0 Ctrl+[ false rotation-reset Reset Canvas Rotation Reset Canvas Rotation Reset Canvas Rotation 1 0 false Show &Rulers The rulers show the horizontal and vertical positions of the mouse on the image and can be used to position your mouse at the right place on the canvas. <p>Uncheck this to hide the rulers.</p> Show Rulers Show Rulers 1 0 true Rulers Track Pointer The rulers will track current mouse position and show it on screen. It can cause suptle performance slowdown Rulers Track Pointer Rulers Track Pointer 1 0 true Show Guides Show or hide guides Show Guides 1 0 true Lock Guides Lock or unlock guides Lock Guides 1 0 true Snap to Guides Snap cursor to guides position Snap to Guides 1 0 true Show Status &Bar Show or hide the status bar Show Status Bar 0 0 true Show Pixel Grid Show Pixel Grid Show Pixel Grid 1000 0 true view-grid Show &Grid Show Grid Show Grid 1000 0 Ctrl+Shift+' true Snap To Grid Snap To Grid Snap To Grid 1000 Ctrl+Shift+; true Show Snap Options Popup Show Snap Options Popup Show Snap Options Popup 1000 Shift+s false Snap Orthogonal Snap Orthogonal Snap Orthogonal 1000 true Snap Node Snap Node Snap Node 1000 true Snap Extension Snap Extension Snap Extension 1000 true Snap Intersection Snap Intersection Snap Intersection 1000 true Snap Bounding Box Snap Bounding Box Snap Bounding Box 1000 true Snap Image Bounds Snap Image Bounds Snap Image Bounds 1000 true Snap Image Center Snap Image Center Snap Image Center 1000 true S&how Painting Assistants Show Painting Assistants Show Painting Assistants 1000 0 true Show &Assistant Previews Show Assistant Previews Show Assistant Previews 1000 0 true S&how Reference Images Show Reference Images Show Reference Images 1000 0 true Image document-properties &Properties... Properties Properties 1000 0 false format-stroke-color &Image Background Color and Transparency... Change the background color of the image Image Background Color and Transparency 1000 0 false &Convert Image Color Space... Convert Image Color Space Convert Image Color Space 1000 0 false trim-to-image &Trim to Image Size Trim to Image Size Trim to Image Size 1 0 false Trim to Current &Layer Trim to Current Layer Trim to Current Layer 100000 0 false Trim to S&election Trim to Selection Trim to Selection 100000000 0 false &Rotate Image... Rotate Image Rotate Image 1000 0 false object-rotate-right Rotate &Image 90° to the Right Rotate Image 90° to the Right Rotate Image 90° to the Right 1000 0 false object-rotate-left Rotate Image &90° to the Left Rotate Image 90° to the Left Rotate Image 90° to the Left 1000 0 false Rotate Image &180° Rotate Image 180° Rotate Image 180° 1000 0 false &Shear Image... Shear Image Shear Image 1000 0 false symmetry-horizontal &Mirror Image Horizontally Mirror Image Horizontally Mirror Image Horizontally 1000 0 false symmetry-vertical Mirror Image &Vertically Mirror Image Vertically Mirror Image Vertically 1000 0 false Scale Image To &New Size... Scale Image To New Size Scale Image To New Size 1000 0 Ctrl+Alt+I false &Offset Image... Offset Image Offset Image 1000 0 false R&esize Canvas... Resize Canvas Resize Canvas 1000 0 Ctrl+Alt+C false Im&age Split Image Split Image Split 1000 0 false Separate Ima&ge... Separate Image Separate Image 1000 0 false Select edit-select-all Select &All Select All Select All 0 0 Ctrl+A false edit-select-all &Deselect Deselect Deselect 1100000000 0 Ctrl+Shift+A false &Reselect Reselect Reselect 0 0 Ctrl+Shift+D false - + - &Invert + &Invert Selection - Invert - Invert + Invert Selection + Invert Selection 10000 0 Ctrl+I false &Convert to Vector Selection Convert to Vector Selection Convert to Vector Selection 10000000000 0 false Convert Shapes to &Vector Selection Convert Shapes to Vector Selection Convert Shapes to Vector Selection 1000000000 0 false &Feather Selection... Feather Selection Feather Selection 10000000000 100 Shift+F6 false Dis&play Selection Display Selection Display Selection 1000 0 Ctrl+H true Sca&le... Scale Scale 100000000 100 false S&elect from Color Range... Select from Color Range Select from Color Range 10000 100 false Select &Opaque Select Opaque Select Opaque 10000 100 false &Grow Selection... Grow Selection Grow Selection 10000000000 100 false S&hrink Selection... Shrink Selection Shrink Selection 10000000000 100 false &Border Selection... Border Selection Border Selection 10000000000 100 false S&mooth Smooth Smooth 10000000000 100 false Filter &Apply Filter Again Apply Filter Again Apply Filter Again 0 0 Ctrl+F false Adjust Adjust Adjust false Artistic Artistic Artistic false Blur Blur Blur false Colors Colors Colors false Edge Detection Edge Detection Edge Detection false Enhance Enhance Enhance false Emboss Emboss Emboss false Map Map Map false Other Other Other false gmic Start G'MIC-Qt Start G'Mic-Qt Start G'Mic-Qt false gmic Re-apply the last G'MIC filter Apply the last G'Mic-Qt action again Apply the last G'Mic-Qt action again false Tools media-record &Start recording macro Start recording macro Start recording macro 1000 0 false media-playback-stop - Stop &recording actions + Stop &recording macro - Stop recording actions - Stop recording actions + Stop recording macro + Stop recording macro 1000 0 false media-playback-start &Open and play... Open and play Open and play 0 0 false document-edit Open &and edit... Open and edit Open and edit 0 0 false Settings configure &Configure Krita... Configure Krita Configure Krita 0 0 false &Manage Resources... Manage Resources Manage Resources 0 0 false preferences-desktop-locale Switch Application &Language... Switch Application Language Switch Application Language false &Show Dockers Show Dockers Show Dockers 0 0 true configure Configure Tool&bars... Configure Toolbars Configure Toolbars 0 0 false Dockers Dockers Dockers false &Themes Themes Themes false im-user Active Author Profile Active Author Profile Active Author Profile configure-shortcuts Configure S&hortcuts... Configure Shortcuts Configure Shortcuts 0 0 false &Window Window Window false Help help-contents Krita &Handbook Krita Handbook Krita Handbook F1 false tools-report-bug &Report Bug... Report Bug Report Bug false calligrakrita &About Krita About Krita About Krita false kde About &KDE About KDE About KDE false Brushes and Stuff &Gradients Gradients Gradients false &Patterns Patterns Patterns false &Color Color Color false &Painter's Tools Painter's Tools Painter's Tools false Brush composite Brush composite Brush composite false Brush option slider 1 Brush option slider 1 Brush option slider 1 false Brush option slider 2 Brush option slider 2 Brush option slider 2 false Brush option slider 3 Brush option slider 3 Brush option slider 3 false Mirror Mirror Mirror false Layouts Select layout false Workspaces Workspaces Workspaces false diff --git a/krita/main.cc b/krita/main.cc index 7f5ef3306d..764bdb3af7 100644 --- a/krita/main.cc +++ b/krita/main.cc @@ -1,389 +1,403 @@ /* * Copyright (c) 1999 Matthias Elter * Copyright (c) 2002 Patrick Julien * Copyright (c) 2015 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "data/splash/splash_screen.xpm" #include "data/splash/splash_holidays.xpm" #include "data/splash/splash_screen_x2.xpm" #include "data/splash/splash_holidays_x2.xpm" #include "KisDocument.h" #include "kis_splash_screen.h" #include "KisPart.h" #include "KisApplicationArguments.h" #include #include "input/KisQtWidgetsTweaker.h" #if defined Q_OS_WIN #include #include #include #include #include #include #include #elif defined HAVE_X11 #include #endif #if defined HAVE_KCRASH #include #elif defined USE_DRMINGW namespace { void tryInitDrMingw() { wchar_t path[MAX_PATH]; QString pathStr = QCoreApplication::applicationDirPath().replace(L'/', L'\\') + QStringLiteral("\\exchndl.dll"); if (pathStr.size() > MAX_PATH - 1) { return; } int pathLen = pathStr.toWCharArray(path); path[pathLen] = L'\0'; // toWCharArray doesn't add NULL terminator HMODULE hMod = LoadLibraryW(path); if (!hMod) { return; } // No need to call ExcHndlInit since the crash handler is installed on DllMain auto myExcHndlSetLogFileNameA = reinterpret_cast(GetProcAddress(hMod, "ExcHndlSetLogFileNameA")); if (!myExcHndlSetLogFileNameA) { return; } // Set the log file path to %LocalAppData%\kritacrash.log QString logFile = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation).replace(L'/', L'\\') + QStringLiteral("\\kritacrash.log"); myExcHndlSetLogFileNameA(logFile.toLocal8Bit()); } typedef enum ORIENTATION_PREFERENCE { ORIENTATION_PREFERENCE_NONE = 0x0, ORIENTATION_PREFERENCE_LANDSCAPE = 0x1, ORIENTATION_PREFERENCE_PORTRAIT = 0x2, ORIENTATION_PREFERENCE_LANDSCAPE_FLIPPED = 0x4, ORIENTATION_PREFERENCE_PORTRAIT_FLIPPED = 0x8 } ORIENTATION_PREFERENCE; typedef BOOL WINAPI (*pSetDisplayAutoRotationPreferences_t)( ORIENTATION_PREFERENCE orientation ); void resetRotation() { QLibrary user32Lib("user32"); if (!user32Lib.load()) { qWarning() << "Failed to load user32.dll! This really should not happen."; return; } pSetDisplayAutoRotationPreferences_t pSetDisplayAutoRotationPreferences = reinterpret_cast(user32Lib.resolve("SetDisplayAutoRotationPreferences")); if (!pSetDisplayAutoRotationPreferences) { qDebug() << "Failed to load function SetDisplayAutoRotationPreferences"; return; } bool result = pSetDisplayAutoRotationPreferences(ORIENTATION_PREFERENCE_NONE); qDebug() << "SetDisplayAutoRotationPreferences(ORIENTATION_PREFERENCE_NONE) returned" << result; } } // namespace #endif extern "C" int main(int argc, char **argv) { // The global initialization of the random generator qsrand(time(0)); bool runningInKDE = !qgetenv("KDE_FULL_SESSION").isEmpty(); #if defined HAVE_X11 qputenv("QT_QPA_PLATFORM", "xcb"); #endif KisLoggingManager::initialize(); // A per-user unique string, without /, because QLocalServer cannot use names with a / in it QString key = "Krita3" + QStandardPaths::writableLocation(QStandardPaths::HomeLocation).replace("/", "_"); key = key.replace(":", "_").replace("\\","_"); QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts, true); QCoreApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings, true); QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps, true); #if QT_VERSION >= 0x050900 QCoreApplication::setAttribute(Qt::AA_DisableShaderDiskCache, true); #endif const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); bool singleApplication = true; bool enableOpenGLDebug = false; bool openGLDebugSynchronous = false; { QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); singleApplication = kritarc.value("EnableSingleApplication", true).toBool(); #if QT_VERSION >= 0x050600 if (kritarc.value("EnableHiDPI", false).toBool()) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); } if (!qgetenv("KRITA_HIDPI").isEmpty()) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); } #endif if (!qgetenv("KRITA_OPENGL_DEBUG").isEmpty()) { enableOpenGLDebug = true; } else { enableOpenGLDebug = kritarc.value("EnableOpenGLDebug", false).toBool(); } if (enableOpenGLDebug && (qgetenv("KRITA_OPENGL_DEBUG") == "sync" || kritarc.value("OpenGLDebugSynchronous", false).toBool())) { openGLDebugSynchronous = true; } KisOpenGL::setDefaultFormat(enableOpenGLDebug, openGLDebugSynchronous); #ifdef Q_OS_WIN QString preferredOpenGLRenderer = kritarc.value("OpenGLRenderer", "auto").toString(); // Force ANGLE to use Direct3D11. D3D9 doesn't support OpenGL ES 3 and WARP // might get weird crashes atm. qputenv("QT_ANGLE_PLATFORM", "d3d11"); // Probe QPA auto OpenGL detection char *fakeArgv[2] = { argv[0], nullptr }; // Prevents QCoreApplication from modifying the real argc/argv KisOpenGL::probeWindowsQpaOpenGL(1, fakeArgv, preferredOpenGLRenderer); // HACK: https://bugs.kde.org/show_bug.cgi?id=390651 resetRotation(); #endif } QString root; QString language; { // Create a temporary application to get the root QCoreApplication app(argc, argv); Q_UNUSED(app); root = KoResourcePaths::getApplicationRoot(); QSettings languageoverride(configPath + QStringLiteral("/klanguageoverridesrc"), QSettings::IniFormat); languageoverride.beginGroup(QStringLiteral("Language")); language = languageoverride.value(qAppName(), "").toString(); } #ifdef Q_OS_LINUX { QByteArray originalXdgDataDirs = qgetenv("XDG_DATA_DIRS"); if (originalXdgDataDirs.isEmpty()) { // We don't want to completely override the default originalXdgDataDirs = "/usr/local/share/:/usr/share/"; } qputenv("XDG_DATA_DIRS", QFile::encodeName(root + "share") + ":" + originalXdgDataDirs); } #else qputenv("XDG_DATA_DIRS", QFile::encodeName(root + "share")); #endif qDebug() << "Setting XDG_DATA_DIRS" << qgetenv("XDG_DATA_DIRS"); // Now that the paths are set, set the language. First check the override from the language // selection dialog. qDebug() << "Override language:" << language; if (!language.isEmpty()) { KLocalizedString::setLanguages(language.split(":")); // And override Qt's locale, too - qputenv("LANG", language.split(":").first().toUtf8()); + qputenv("LANG", language.split(":").first().toLocal8Bit()); QLocale locale(language.split(":").first()); QLocale::setDefault(locale); } #ifndef Q_OS_LINUX else { // And if there isn't one, check the one set by the system. QLocale locale = QLocale::system(); if (locale.name() != QStringLiteral("en")) { - qDebug() << "Setting Krita's language to:" << locale; - qputenv("LANG", locale.name().toLatin1()); - KLocalizedString::setLanguages(QStringList() << locale.name()); + QStringList uiLanguages = locale.uiLanguages(); + for (QString &uiLanguage : uiLanguages) { + uiLanguage.replace(QChar('-'), QChar('_')); + } + for (int i = 0; i < uiLanguages.size(); i++) { + QString uiLanguage = uiLanguages[i]; + // Strip the country code + int idx = uiLanguage.indexOf(QChar('_')); + if (idx != -1) { + uiLanguage = uiLanguage.left(idx); + uiLanguages.insert(i + 1, uiLanguage); + i++; + } + } + qDebug() << "Setting Krita's language to:" << uiLanguages; + qputenv("LANG", uiLanguages.first().toLocal8Bit()); + KLocalizedString::setLanguages(uiLanguages); } } #endif // first create the application so we can create a pixmap KisApplication app(key, argc, argv); KLocalizedString::setApplicationDomain("krita"); qDebug() << "Available translations" << KLocalizedString::availableApplicationTranslations(); qDebug() << "Available domain translations" << KLocalizedString::availableDomainTranslations("krita"); qDebug() << "Qt UI languages" << QLocale::system().uiLanguages() << qgetenv("LANG"); #ifdef Q_OS_WIN QDir appdir(KoResourcePaths::getApplicationRoot()); QString path = qgetenv("PATH"); qputenv("PATH", QFile::encodeName(appdir.absolutePath() + "/bin" + ";" + appdir.absolutePath() + "/lib" + ";" + appdir.absolutePath() + "/Frameworks" + ";" + appdir.absolutePath() + ";" + path)); qDebug() << "PATH" << qgetenv("PATH"); #endif if (qApp->applicationDirPath().contains(KRITA_BUILD_DIR)) { qFatal("FATAL: You're trying to run krita from the build location. You can only run Krita from the installation location."); } #if defined HAVE_KCRASH KCrash::initialize(); #elif defined USE_DRMINGW tryInitDrMingw(); #endif // If we should clear the config, it has to be done as soon as possible after // KisApplication has been created. Otherwise the config file may have been read // and stored in a KConfig object we have no control over. app.askClearConfig(); KisApplicationArguments args(app); if (singleApplication && app.isRunning()) { // only pass arguments to main instance if they are not for batch processing // any batch processing would be done in this separate instance - const bool batchRun = (args.print() || args.exportAs() || args.exportAsPdf()); + const bool batchRun = args.exportAs(); if (!batchRun) { QByteArray ba = args.serialize(); if (app.sendMessage(ba)) { return 0; } } } if (!runningInKDE) { // Icons in menus are ugly and distracting app.setAttribute(Qt::AA_DontShowIconsInMenus); } #if defined HAVE_X11 app.installNativeEventFilter(KisXi2EventFilter::instance()); #endif app.installEventFilter(KisQtWidgetsTweaker::instance()); if (!args.noSplash()) { // then create the pixmap from an xpm: we cannot get the // location of our datadir before we've started our components, // so use an xpm. QDate currentDate = QDate::currentDate(); QWidget *splash = 0; if (currentDate > QDate(currentDate.year(), 12, 4) || currentDate < QDate(currentDate.year(), 1, 9)) { splash = new KisSplashScreen(app.applicationVersion(), QPixmap(splash_holidays_xpm), QPixmap(splash_holidays_x2_xpm)); } else { splash = new KisSplashScreen(app.applicationVersion(), QPixmap(splash_screen_xpm), QPixmap(splash_screen_x2_xpm)); } app.setSplashScreen(splash); } #if defined Q_OS_WIN { KisConfig cfg; if (cfg.useWin8PointerInput() && !KisTabletSupportWin8::isAvailable()) { cfg.setUseWin8PointerInput(false); } if (!cfg.useWin8PointerInput()) { bool hasWinTab = KisTabletSupportWin::init(); if (!hasWinTab) { if (KisTabletSupportWin8::isPenDeviceAvailable()) { // Use WinInk automatically cfg.setUseWin8PointerInput(true); } else if (!cfg.readEntry("WarnedAboutMissingWinTab", false)) { if (KisTabletSupportWin8::isAvailable()) { QMessageBox::information(nullptr, i18n("Krita Tablet Support"), i18n("Cannot load WinTab driver and no Windows Ink pen devices are found. If you have a drawing tablet, please make sure the tablet driver is properly installed."), QMessageBox::Ok, QMessageBox::Ok); } else { QMessageBox::information(nullptr, i18n("Krita Tablet Support"), i18n("Cannot load WinTab driver. If you have a drawing tablet, please make sure the tablet driver is properly installed."), QMessageBox::Ok, QMessageBox::Ok); } cfg.writeEntry("WarnedAboutMissingWinTab", true); } } } if (cfg.useWin8PointerInput()) { KisTabletSupportWin8 *penFilter = new KisTabletSupportWin8(); if (penFilter->init()) { // penFilter.registerPointerDeviceNotifications(); app.installNativeEventFilter(penFilter); qDebug() << "Using Win8 Pointer Input for tablet support"; } else { qDebug() << "No Win8 Pointer Input available"; delete penFilter; } } } #endif if (!app.start(args)) { return 1; } #if QT_VERSION >= 0x050700 app.setAttribute(Qt::AA_CompressHighFrequencyEvents, false); #endif // Set up remote arguments. QObject::connect(&app, SIGNAL(messageReceived(QByteArray,QObject*)), &app, SLOT(remoteArguments(QByteArray,QObject*))); QObject::connect(&app, SIGNAL(fileOpenRequest(QString)), &app, SLOT(fileOpenRequested(QString))); int state = app.exec(); { QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); kritarc.setValue("canvasState", "OPENGL_SUCCESS"); } return state; } diff --git a/krita/org.kde.krita.appdata.xml b/krita/org.kde.krita.appdata.xml index 586afe06b2..6a30f93d17 100644 --- a/krita/org.kde.krita.appdata.xml +++ b/krita/org.kde.krita.appdata.xml @@ -1,207 +1,243 @@ - org.kde.krita.desktop + org.kde.krita + org.kde.krita.desktop CC0-1.0 + GPL-3.0-only + Krita Foundation + Fundació Krita + Fundació Krita + Krita Foundation + Fundação do Krita + Krita Foundation + Krita-stiftelsen + Фундація Krita + xxKrita Foundationxx + foundation@krita.org Krita كريتا Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita xxKritaxx Krita Krita Digital Painting, Creative Freedom رسم رقميّ، حريّة إبداعيّة Pintura dixital, llibertá creativa Digitalno crtanje, kreativna sloboda Dibuix digital, Llibertat creativa Dibuix digital, Llibertat creativa Digitální malování, svoboda tvorby Digital tegning, kunstnerisk frihed Digitales Malen, kreative Freiheit Ψηφιακή ζωγραφική, δημιουργική ελευθερία Digital Painting, Creative Freedom Pintura digital, libertad creativa Digitaalne joonistamine, loominguline vabadus Digitaalimaalaus, luova vapaus Peinture numérique, liberté créatrice Debuxo dixital, liberdade creativa Pictura digital, Libertate creative Pelukisan Digital, Kebebasan Berkreatif Pittura digitale, libertà creativa Digital Painting, Creative Freedom Cyfrowe malowanie, Wolność Twórcza Pintura Digital, Liberdade Criativa Pintura digital, liberdade criativa Цифровое рисование. Творческая свобода Digitálne maľovanie, kreatívna sloboda Digital målning, kreativ frihet Sayısal Boyama, Yaratıcı Özgürlük Цифрове малювання, творча свобода xxDigital Painting, Creative Freedomxx 自由挥洒数字绘画的无限创意 數位繪畫,創作自由

Krita is the full-featured digital art studio.

Krita ye l'estudiu completu d'arte dixital.

Krita je potpuni digitalni umjetnički studio.

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

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

Krita ist ein digitales Designstudio mit umfangreichen Funktionen.

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

Krita is the full-featured digital art studio.

Krita es un estudio de arte digital completo

Krita on rohkete võimalustega digitaalkunstistuudio.

Krita on täyspiirteinen digitaiteen ateljee.

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

Krita é un estudio completo de arte dixital.

Krita es le studio de arte digital complete.

Krita adalah studio seni digital yang penuh dengan fitur.

Krita è uno studio d'arte digitale completo.

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

Krita is de digitale kunststudio vol mogelijkheden.

Krita jest pełnowymiarowym, cyfrowym studiem artystycznym

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

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

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

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

Krita är den fullfjädrade digitala konststudion.

Krita, tam özellikli dijital sanat stüdyosudur.

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

xxKrita is the full-featured digital art studio.xx

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

https://www.krita.org/ https://krita.org/about/faq/ https://krita.org/support-us/donations/ https://docs.krita.org/Category:Tutorials https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_001.png https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_002.png https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_003.png https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_004.png https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_005.png https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_006.png - foundation@krita.org + + none + none + none + none + none + none + none + none + none + none + none + none + none + none + none + none + none + none + none + none + + + Graphics + KDE krita
diff --git a/libs/brush/tests/CMakeLists.txt b/libs/brush/tests/CMakeLists.txt index 7ae6ecbb8a..df88d5d23e 100644 --- a/libs/brush/tests/CMakeLists.txt +++ b/libs/brush/tests/CMakeLists.txt @@ -1,31 +1,30 @@ ########### next target ############### set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) -include_directories( - ${CMAKE_SOURCE_DIR}/libs/image/metadata - ${CMAKE_SOURCE_DIR}/sdk/tests +include_directories( + ${CMAKE_SOURCE_DIR}/libs/image/metadata + ${CMAKE_SOURCE_DIR}/sdk/tests ) include_directories(SYSTEM ${EIGEN3_INCLUDE_DIR} ) if(HAVE_VC) include_directories(SYSTEM ${Vc_INCLUDE_DIR} ) endif() macro_add_unittest_definitions() include(ECMAddTests) ecm_add_tests( kis_auto_brush_test.cpp kis_auto_brush_factory_test.cpp kis_gbr_brush_test.cpp kis_boundary_test.cpp kis_imagepipe_brush_test.cpp NAME_PREFIX "krita-libbrush-" LINK_LIBRARIES kritaimage kritalibbrush Qt5::Test ) - diff --git a/libs/flake/KoCanvasController.cpp b/libs/flake/KoCanvasController.cpp index 8a1b0f25fc..32a8058d88 100644 --- a/libs/flake/KoCanvasController.cpp +++ b/libs/flake/KoCanvasController.cpp @@ -1,160 +1,132 @@ /* This file is part of the KDE project * * Copyright (C) 2010 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoCanvasController.h" #include "KoToolManager.h" #include #include class Q_DECL_HIDDEN KoCanvasController::Private { public: Private() - : canvasMode(Centered) - , margin(0) + : margin(0) , preferredCenterFractionX(0.5) , preferredCenterFractionY(0.5) , actionCollection(0) { } - CanvasMode canvasMode; int margin; QSize documentSize; QPoint documentOffset; qreal preferredCenterFractionX; qreal preferredCenterFractionY; KActionCollection* actionCollection; }; KoCanvasController::KoCanvasController(KActionCollection* actionCollection) : d(new Private()) { proxyObject = new KoCanvasControllerProxyObject(this); d->actionCollection = actionCollection; } KoCanvasController::~KoCanvasController() { KoToolManager::instance()->removeCanvasController(this); delete d; delete proxyObject; } -void KoCanvasController::setCanvasMode(CanvasMode mode) -{ - d->canvasMode = mode; - switch (mode) { - case AlignTop: - d->preferredCenterFractionX = 0; - d->preferredCenterFractionY = 0.5; - break; - case Infinite: - case Centered: - d->preferredCenterFractionX = 0.5; - d->preferredCenterFractionY = 0.5; - break; - case Spreadsheet: - d->preferredCenterFractionX = 0; - d->preferredCenterFractionY = 0; - break; - }; -} - void KoCanvasController::setMargin(int margin) { d->margin = margin; } int KoCanvasController::margin() const { return d->margin; } - -KoCanvasController::CanvasMode KoCanvasController::canvasMode() const -{ - return d->canvasMode; -} - KoCanvasBase* KoCanvasController::canvas() const { return 0; } void KoCanvasController::setDocumentSize(const QSize &sz) { d->documentSize = sz; } QSize KoCanvasController::documentSize() const { return d->documentSize; } void KoCanvasController::setPreferredCenterFractionX(qreal x) { d->preferredCenterFractionX = x; } qreal KoCanvasController::preferredCenterFractionX() const { return d->preferredCenterFractionX; } void KoCanvasController::setPreferredCenterFractionY(qreal y) { d->preferredCenterFractionY = y; } qreal KoCanvasController::preferredCenterFractionY() const { return d->preferredCenterFractionY; } void KoCanvasController::setDocumentOffset(QPoint &offset) { d->documentOffset = offset; } QPoint KoCanvasController::documentOffset() const { return d->documentOffset; } KoCanvasControllerProxyObject::KoCanvasControllerProxyObject(KoCanvasController *controller, QObject *parent) : QObject(parent) , m_canvasController(controller) { } void KoCanvasControllerProxyObject::updateDocumentSize(const QSize &newSize, bool recalculateCenter) { m_canvasController->updateDocumentSize(newSize, recalculateCenter); } KActionCollection* KoCanvasController::actionCollection() const { return d->actionCollection; } diff --git a/libs/flake/KoCanvasController.h b/libs/flake/KoCanvasController.h index b4950ef178..b7dcd2f56d 100644 --- a/libs/flake/KoCanvasController.h +++ b/libs/flake/KoCanvasController.h @@ -1,503 +1,477 @@ /* This file is part of the KDE project * Copyright (C) 2006, 2008 Thomas Zander * Copyright (C) 2007-2010 Boudewijn Rempt * Copyright (C) 2007-2008 C. Boemann * Copyright (C) 2006-2007 Jan Hambrecht * Copyright (C) 2009 Thorsten Zachmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOCANVASCONTROLLER_H #define KOCANVASCONTROLLER_H #include "kritaflake_export.h" #include #include #include #include #include class KActionCollection; class QRect; class QRectF; class KoShape; class KoCanvasBase; class KoCanvasControllerProxyObject; /** * KoCanvasController is the base class for wrappers around your canvas * that provides scrolling and zooming for your canvas. * * Flake does not provide a canvas, the application will have to * implement a canvas themselves. You canvas can be QWidget-based * or something we haven't invented yet -- as long the class that holds the canvas * imlements KoCanvasController, tools, scrolling and zooming will work. * * A KoCanvasController implementation acts as a decorator around the canvas widget * and provides a way to scroll the canvas, allows the canvas to be centered * in the viewArea and manages tool activation. * *

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

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

The canvas widget is at most as big as the viewport of the scroll * area, and when the view on the document is near its edges, smaller. * In your canvas widget code, you can find the right place in your * document in view coordinates (pixels) by adding the documentOffset */ class KRITAFLAKE_EXPORT KoCanvasController { public: - /// An enum to alter the positioning and size of the canvas inside the canvas controller - enum CanvasMode { - AlignTop, ///< canvas is top aligned if smaller than the viewport - Centered, ///< canvas is centered if smaller than the viewport - Infinite, ///< canvas is never smaller than the viewport - Spreadsheet ///< same as Infinite, but supports right-to-left layouts - }; // proxy QObject: use this to connect to slots and signals. QPointer proxyObject; /** * Constructor. * @param actionCollection the action collection for this canvas */ explicit KoCanvasController(KActionCollection* actionCollection); virtual ~KoCanvasController(); public: /** * Returns the current margin that is used to pad the canvas with. * This value is read from the KConfig property "canvasmargin" */ virtual int margin() const; /** * Set the new margin to pad the canvas with. */ virtual void setMargin(int margin); - /** - * Sets the how the canvas behaves if the zoomed document becomes smaller than the viewport. - * @param mode the new canvas mode, CanvasMode::Centered is the default value - */ - virtual void setCanvasMode(KoCanvasController::CanvasMode mode); - - /// Returns the current canvas mode - virtual KoCanvasController::CanvasMode canvasMode() const; - /** * compatibility with QAbstractScrollArea */ virtual void scrollContentsBy(int dx, int dy) = 0; /** * @return the size of the viewport */ virtual QSize viewportSize() const = 0; - /** - * Set the shadow option -- by default the canvas controller draws - * a black shadow around the canvas widget, which you may or may - * not want. - * - * @param drawShadow if true, the shadow is drawn, if false, not - */ - virtual void setDrawShadow(bool drawShadow) = 0; - /** * Set the new canvas to be shown as a child * Calling this will emit canvasRemoved() if there was a canvas before, and will emit * canvasSet() with the new canvas. * @param canvas the new canvas. The KoCanvasBase::canvas() will be called to retrieve the * actual widget which will then be added as child of this one. */ virtual void setCanvas(KoCanvasBase *canvas) = 0; /** * Return the currently set canvas. The default implementation will return Null * @return the currently set canvas */ virtual KoCanvasBase *canvas() const; /** * return the amount of pixels vertically visible of the child canvas. * @return the amount of pixels vertically visible of the child canvas. */ virtual int visibleHeight() const = 0; /** * return the amount of pixels horizontally visible of the child canvas. * @return the amount of pixels horizontally visible of the child canvas. */ virtual int visibleWidth() const = 0; /** * return the amount of pixels that are not visible on the left side of the canvas. * The leftmost pixel that is shown is returned. */ virtual int canvasOffsetX() const = 0; /** * return the amount of pixels that are not visible on the top side of the canvas. * The topmost pixel that is shown is returned. */ virtual int canvasOffsetY() const = 0; /** * @brief Scrolls the content of the canvas so that the given rect is visible. * * The rect is to be specified in view coordinates (pixels). The scrollbar positions * are changed so that the centerpoint of the rectangle is centered if possible. * * @param rect the rectangle to make visible * @param smooth if true the viewport translation will make be just enough to ensure visibility, no more. * @see KoViewConverter::documentToView() */ virtual void ensureVisible(const QRectF &rect, bool smooth = false) = 0; /** * @brief Scrolls the content of the canvas so that the given shape is visible. * * This is just a wrapper function of the above function. * * @param shape the shape to make visible */ virtual void ensureVisible(KoShape *shape) = 0; /** * @brief zooms in around the center. * * The center must be specified in view coordinates (pixels). The scrollbar positions * are changed so that the center becomes center if possible. * * @param center the position to zoom in on */ virtual void zoomIn(const QPoint ¢er) = 0; /** * @brief zooms out around the center. * * The center must be specified in view coordinates (pixels). The scrollbar positions * are changed so that the center becomes center if possible. * * @param center the position to zoom out around */ virtual void zoomOut(const QPoint ¢er) = 0; /** * @brief zooms around the center. * * The center must be specified in view coordinates (pixels). The scrollbar positions * are changed so that the center becomes center if possible. * * @param center the position to zoom around * @param zoom the zoom to apply */ virtual void zoomBy(const QPoint ¢er, qreal zoom) = 0; /** * @brief zoom so that rect is exactly visible (as close as possible) * * The rect must be specified in view coordinates (pixels). The scrollbar positions * are changed so that the center of the rect becomes center if possible. * * @param rect the rect in view coordinates (pixels) that should fit the view afterwards */ virtual void zoomTo(const QRect &rect) = 0; /** * @brief repositions the scrollbars so previous center is once again center * * The previous center is cached from when the user uses the scrollbars or zoomTo * are called. zoomTo is mostly used when a zoom tool of sorts have marked an area * to zoom in on * * The success of this method is limited by the size of thing. But we try our best. */ virtual void recenterPreferred() = 0; /** * Sets the preferred center point in view coordinates (pixels). * @param viewPoint the new preferred center */ virtual void setPreferredCenter(const QPointF &viewPoint) = 0; /// Returns the currently set preferred center point in view coordinates (pixels) virtual QPointF preferredCenter() const = 0; /** * Move the canvas over the x and y distance of the parameter distance * @param distance the distance in view coordinates (pixels). A positive distance means moving the canvas up/left. */ virtual void pan(const QPoint &distance) = 0; /** * Move the canvas up. This behaves the same as \sa pan() with a positive y coordinate. */ virtual void panUp() = 0; /** * Move the canvas down. This behaves the same as \sa pan() with a negative y coordinate. */ virtual void panDown() = 0; /** * Move the canvas to the left. This behaves the same as \sa pan() with a positive x coordinate. */ virtual void panLeft() = 0; /** * Move the canvas to the right. This behaves the same as \sa pan() with a negative x coordinate. */ virtual void panRight() = 0; /** * Get the position of the scrollbar */ virtual QPoint scrollBarValue() const = 0; /** * Set the position of the scrollbar * @param value the new values of the scroll bars */ virtual void setScrollBarValue(const QPoint &value) = 0; /** * Called when the size of your document in view coordinates (pixels) changes, for instance when zooming. * * @param newSize the new size, in view coordinates (pixels), of the document. * @param recalculateCenter if true the offset in the document we center on after calling * recenterPreferred() will be recalculated for the new document size so the visual offset stays the same. */ virtual void updateDocumentSize(const QSize &sz, bool recalculateCenter) = 0; /** * Set mouse wheel to zoom behaviour * @param zoom if true wheel will zoom instead of scroll, control modifier will scroll */ virtual void setZoomWithWheel(bool zoom) = 0; /** * Set scroll area to be bigger than actual document. * It allows the user to move the corner of the document * to e.g. the center of the screen * * @param factor the coefficient, defining how much we can scroll out, * measured in parts of the widget size. Null value means vast * scrolling is disabled. */ virtual void setVastScrolling(qreal factor) = 0; /** * Returns the action collection for the canvas * @returns action collection for this canvas, can be 0 */ virtual KActionCollection* actionCollection() const; QPoint documentOffset() const; /** * @return the current position of the cursor fetched from QCursor::pos() and * converted into document coordinates */ virtual QPointF currentCursorPosition() const = 0; protected: void setDocumentSize(const QSize &sz); QSize documentSize() const; void setPreferredCenterFractionX(qreal); qreal preferredCenterFractionX() const; void setPreferredCenterFractionY(qreal); qreal preferredCenterFractionY() const; void setDocumentOffset( QPoint &offset); private: class Private; Private * const d; }; /** * Workaround class for the problem that Qt does not allow two QObject base classes. * KoCanvasController can be implemented by for instance QWidgets, so it cannot be * a QObject directly. The interface of this class should be considered public interface * for KoCanvasController. */ class KRITAFLAKE_EXPORT KoCanvasControllerProxyObject : public QObject { Q_OBJECT Q_DISABLE_COPY(KoCanvasControllerProxyObject) public: explicit KoCanvasControllerProxyObject(KoCanvasController *canvasController, QObject *parent = 0); public: // Convenience methods to invoke the signals from subclasses void emitCanvasRemoved(KoCanvasController *canvasController) { emit canvasRemoved(canvasController); } void emitCanvasSet(KoCanvasController *canvasController) { emit canvasSet(canvasController); } void emitCanvasOffsetXChanged(int offset) { emit canvasOffsetXChanged(offset); } void emitCanvasOffsetYChanged(int offset) { emit canvasOffsetYChanged(offset); } void emitCanvasMousePositionChanged(const QPoint &position) { emit canvasMousePositionChanged(position); } void emitDocumentMousePositionChanged(const QPointF &position) { emit documentMousePositionChanged(position); } void emitSizeChanged(const QSize &size) { emit sizeChanged(size); } void emitMoveDocumentOffset(const QPoint &point) { emit moveDocumentOffset(point); } void emitZoomRelative(const qreal factor, const QPointF &stillPoint) { emit zoomRelative(factor, stillPoint); } // Convenience method to retrieve the canvas controller for who needs to use QPointer KoCanvasController *canvasController() const { return m_canvasController; } Q_SIGNALS: /** * Emitted when a previously added canvas is about to be removed. * @param canvasController this object */ void canvasRemoved(KoCanvasController *canvasController); /** * Emitted when a canvas is set on this widget * @param canvasController this object */ void canvasSet(KoCanvasController *canvasController); /** * Emitted when canvasOffsetX() changes * @param offset the new canvas offset */ void canvasOffsetXChanged(int offset); /** * Emitted when canvasOffsetY() changes * @param offset the new canvas offset */ void canvasOffsetYChanged(int offset); /** * Emitted when the cursor is moved over the canvas widget. * @param position the position in view coordinates (pixels). */ void canvasMousePositionChanged(const QPoint &position); /** * Emitted when the cursor is moved over the canvas widget. * @param position the position in document coordinates. * * Use \ref canvasMousePositionChanged to get the position * in view coordinates. */ void documentMousePositionChanged(const QPointF &position); /** * Emitted when the entire controller size changes * @param size the size in widget pixels. */ void sizeChanged(const QSize &size); /** * Emitted whenever the document is scrolled. * * @param point the new top-left point from which the document should * be drawn. */ void moveDocumentOffset(const QPoint &point); /** * Emitted when zoomRelativeToPoint have calculated a factor by which * the zoom should change and the point which should stand still * on screen. * Someone needs to connect to this and take action * * @param factor by how much the zoom needs to change. * @param stillPoint the point which will not change its position * in widget during the zooming. It is measured in * view coordinate system *before* zoom. */ void zoomRelative(const qreal factor, const QPointF &stillPoint); public Q_SLOTS: /** * Call this slot whenever the size of your document in view coordinates (pixels) * changes, for instance when zooming. * @param newSize the new size, in view coordinates (pixels), of the document. * @param recalculateCenter if true the offset in the document we center on after calling * recenterPreferred() will be recalculated for the new document size so the visual offset stays the same. */ void updateDocumentSize(const QSize &newSize, bool recalculateCenter = true); private: KoCanvasController *m_canvasController; }; class KRITAFLAKE_EXPORT KoDummyCanvasController : public KoCanvasController { public: explicit KoDummyCanvasController(KActionCollection* actionCollection) : KoCanvasController(actionCollection) {} ~KoDummyCanvasController() override {} void scrollContentsBy(int /*dx*/, int /*dy*/) override {} QSize viewportSize() const override { return QSize(); } - void setDrawShadow(bool /*drawShadow*/) override {} void setCanvas(KoCanvasBase *canvas) override {Q_UNUSED(canvas)} KoCanvasBase *canvas() const override {return 0;} int visibleHeight() const override {return 0;} int visibleWidth() const override {return 0;} int canvasOffsetX() const override {return 0;} int canvasOffsetY() const override {return 0;} void ensureVisible(const QRectF &/*rect*/, bool /*smooth */ = false) override {} void ensureVisible(KoShape *shape) override {Q_UNUSED(shape)} void zoomIn(const QPoint &/*center*/) override {} void zoomOut(const QPoint &/*center*/) override {} void zoomBy(const QPoint &/*center*/, qreal /*zoom*/) override {} void zoomTo(const QRect &/*rect*/) override {} void recenterPreferred() override {} void setPreferredCenter(const QPointF &/*viewPoint*/) override {} QPointF preferredCenter() const override {return QPointF();} void pan(const QPoint &/*distance*/) override {} void panUp() override {} void panDown() override {} void panLeft() override {} void panRight() override {} QPoint scrollBarValue() const override {return QPoint();} void setScrollBarValue(const QPoint &/*value*/) override {} void updateDocumentSize(const QSize &/*sz*/, bool /*recalculateCenter*/) override {} void setZoomWithWheel(bool /*zoom*/) override {} void setVastScrolling(qreal /*factor*/) override {} QPointF currentCursorPosition() const override { return QPointF(); } }; #endif diff --git a/libs/flake/KoCanvasControllerWidget.cpp b/libs/flake/KoCanvasControllerWidget.cpp index dea47c94fb..97f2053d67 100644 --- a/libs/flake/KoCanvasControllerWidget.cpp +++ b/libs/flake/KoCanvasControllerWidget.cpp @@ -1,621 +1,612 @@ /* This file is part of the KDE project * * Copyright (C) 2006, 2008-2009 Thomas Zander * Copyright (C) 2006 Peter Simonsson * Copyright (C) 2006, 2009 Thorsten Zachmann * Copyright (C) 2007-2010 Boudewijn Rempt * Copyright (C) 2007 C. Boemann * Copyright (C) 2006-2008 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoCanvasControllerWidget.h" #include "KoCanvasControllerWidget_p.h" #include "KoCanvasControllerWidgetViewport_p.h" #include "KoShape.h" #include "KoViewConverter.h" #include "KoCanvasBase.h" #include "KoCanvasObserverBase.h" #include "KoCanvasSupervisor.h" #include "KoToolManager_p.h" #include #include #include #include #include #include #include #include #include #include void KoCanvasControllerWidget::Private::setDocumentOffset() { // The margins scroll the canvas widget inside the viewport, not // the document. The documentOffset is meant to be the value that // the canvas must add to the update rect in its paint event, to // compensate. QPoint pt(q->horizontalScrollBar()->value(), q->verticalScrollBar()->value()); q->proxyObject->emitMoveDocumentOffset(pt); QWidget *canvasWidget = canvas->canvasWidget(); if (canvasWidget) { // If it isn't an OpenGL canvas if (qobject_cast(canvasWidget) == 0) { QPoint diff = q->documentOffset() - pt; - if (q->canvasMode() == Spreadsheet && canvasWidget->layoutDirection() == Qt::RightToLeft) { - canvasWidget->scroll(-diff.x(), diff.y()); - } else { - canvasWidget->scroll(diff.x(), diff.y()); - } + canvasWidget->scroll(diff.x(), diff.y()); } } q->setDocumentOffset(pt); } void KoCanvasControllerWidget::Private::resetScrollBars() { // The scrollbar value always points at the top-left corner of the // bit of image we paint. int docH = q->documentSize().height() + q->margin(); int docW = q->documentSize().width() + q->margin(); int drawH = viewportWidget->height(); int drawW = viewportWidget->width(); QScrollBar *hScroll = q->horizontalScrollBar(); QScrollBar *vScroll = q->verticalScrollBar(); int horizontalReserve = vastScrollingFactor * drawW; int verticalReserve = vastScrollingFactor * drawH; int xMin = -horizontalReserve; int yMin = -verticalReserve; int xMax = docW - drawW + horizontalReserve; int yMax = docH - drawH + verticalReserve; hScroll->setRange(xMin, xMax); vScroll->setRange(yMin, yMax); int fontheight = QFontMetrics(q->font()).height(); vScroll->setPageStep(drawH); vScroll->setSingleStep(fontheight); hScroll->setPageStep(drawW); hScroll->setSingleStep(fontheight); } void KoCanvasControllerWidget::Private::emitPointerPositionChangedSignals(QEvent *event) { if (!canvas) return; if (!canvas->viewConverter()) return; QPoint pointerPos; QMouseEvent *mouseEvent = dynamic_cast(event); if (mouseEvent) { pointerPos = mouseEvent->pos(); } else { QTabletEvent *tabletEvent = dynamic_cast(event); if (tabletEvent) { pointerPos = tabletEvent->pos(); } } QPoint pixelPos = (pointerPos - canvas->documentOrigin()) + q->documentOffset(); QPointF documentPos = canvas->viewConverter()->viewToDocument(pixelPos); q->proxyObject->emitDocumentMousePositionChanged(documentPos); q->proxyObject->emitCanvasMousePositionChanged(pointerPos); } #include void KoCanvasControllerWidget::Private::activate() { QWidget *parent = q; while (parent->parentWidget()) { parent = parent->parentWidget(); } KoCanvasSupervisor *observerProvider = dynamic_cast(parent); if (!observerProvider) { return; } KoCanvasBase *canvas = q->canvas(); Q_FOREACH (KoCanvasObserverBase *docker, observerProvider->canvasObservers()) { KoCanvasObserverBase *observer = dynamic_cast(docker); if (observer) { observer->setObservedCanvas(canvas); } } } void KoCanvasControllerWidget::Private::unsetCanvas() { QWidget *parent = q; while (parent->parentWidget()) { parent = parent->parentWidget(); } KoCanvasSupervisor *observerProvider = dynamic_cast(parent); if (!observerProvider) { return; } Q_FOREACH (KoCanvasObserverBase *docker, observerProvider->canvasObservers()) { KoCanvasObserverBase *observer = dynamic_cast(docker); if (observer) { if (observer->observedCanvas() == q->canvas()) { observer->unsetObservedCanvas(); } } } } //////////// KoCanvasControllerWidget::KoCanvasControllerWidget(KActionCollection * actionCollection, QWidget *parent) : QAbstractScrollArea(parent) , KoCanvasController(actionCollection) , d(new Private(this)) { // We need to set this as QDeclarativeView sets them a bit different from QAbstractScrollArea setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); // And then our own Viewport d->viewportWidget = new Viewport(this); setViewport(d->viewportWidget); d->viewportWidget->setFocusPolicy(Qt::NoFocus); setFocusPolicy(Qt::NoFocus); setFrameStyle(0); //setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); setAutoFillBackground(false); /* Fixes: apps starting at zero zoom. Details: Since the document is set on the mainwindow before loading commences the inial show/layout can choose to set the document to be very small, even to be zero pixels tall. Setting a sane minimum size on the widget means we no longer get rounding errors in zooming and we no longer end up with zero-zoom. Note: KoPage apps should probably startup with a sane document size; for Krita that's impossible */ setMinimumSize(QSize(50, 50)); setMouseTracking(true); connect(horizontalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(updateCanvasOffsetX())); connect(verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(updateCanvasOffsetY())); connect(d->viewportWidget, SIGNAL(sizeChanged()), this, SLOT(updateCanvasOffsetX())); connect(proxyObject, SIGNAL(moveDocumentOffset(const QPoint&)), d->viewportWidget, SLOT(documentOffsetMoved(const QPoint&))); } KoCanvasControllerWidget::~KoCanvasControllerWidget() { delete d; } void KoCanvasControllerWidget::activate() { d->activate(); } void KoCanvasControllerWidget::scrollContentsBy(int dx, int dy) { Q_UNUSED(dx); Q_UNUSED(dy); d->setDocumentOffset(); } QSize KoCanvasControllerWidget::viewportSize() const { return viewport()->size(); } -void KoCanvasControllerWidget::setDrawShadow(bool drawShadow) -{ - d->viewportWidget->setDrawShadow(drawShadow); -} - void KoCanvasControllerWidget::resizeEvent(QResizeEvent *resizeEvent) { proxyObject->emitSizeChanged(resizeEvent->size()); // XXX: When resizing, keep the area we're looking at now in the // center of the resized view. d->resetScrollBars(); d->setDocumentOffset(); } void KoCanvasControllerWidget::setCanvas(KoCanvasBase *canvas) { if (d->canvas) { d->unsetCanvas(); proxyObject->emitCanvasRemoved(this); d->canvas->setCanvasController(0); d->canvas->canvasWidget()->removeEventFilter(this); } d->canvas = canvas; if (d->canvas) { d->canvas->setCanvasController(this); changeCanvasWidget(d->canvas->canvasWidget()); proxyObject->emitCanvasSet(this); QTimer::singleShot(0, this, SLOT(activate())); setPreferredCenterFractionX(0); setPreferredCenterFractionY(0); } } KoCanvasBase* KoCanvasControllerWidget::canvas() const { if (d->canvas.isNull()) return 0; return d->canvas; } void KoCanvasControllerWidget::changeCanvasWidget(QWidget *widget) { if (d->viewportWidget->canvas()) { widget->setCursor(d->viewportWidget->canvas()->cursor()); d->viewportWidget->canvas()->removeEventFilter(this); } d->viewportWidget->setCanvas(widget); setFocusProxy(d->canvas->canvasWidget()); } int KoCanvasControllerWidget::visibleHeight() const { if (d->canvas == 0) return 0; QWidget *canvasWidget = canvas()->canvasWidget(); int height1; if (canvasWidget == 0) height1 = viewport()->height(); else height1 = qMin(viewport()->height(), canvasWidget->height()); int height2 = height(); return qMin(height1, height2); } int KoCanvasControllerWidget::visibleWidth() const { if (d->canvas == 0) return 0; QWidget *canvasWidget = canvas()->canvasWidget(); int width1; if (canvasWidget == 0) width1 = viewport()->width(); else width1 = qMin(viewport()->width(), canvasWidget->width()); int width2 = width(); return qMin(width1, width2); } int KoCanvasControllerWidget::canvasOffsetX() const { int offset = -horizontalScrollBar()->value(); if (d->canvas) { offset += d->canvas->canvasWidget()->x() + frameWidth(); } return offset; } int KoCanvasControllerWidget::canvasOffsetY() const { int offset = -verticalScrollBar()->value(); if (d->canvas) { offset += d->canvas->canvasWidget()->y() + frameWidth(); } return offset; } void KoCanvasControllerWidget::updateCanvasOffsetX() { proxyObject->emitCanvasOffsetXChanged(canvasOffsetX()); if (d->ignoreScrollSignals) return; setPreferredCenterFractionX((horizontalScrollBar()->value() + viewport()->width() / 2.0) / documentSize().width()); } void KoCanvasControllerWidget::updateCanvasOffsetY() { proxyObject->emitCanvasOffsetYChanged(canvasOffsetY()); if (d->ignoreScrollSignals) return; setPreferredCenterFractionY((verticalScrollBar()->value() + verticalScrollBar()->pageStep() / 2.0) / documentSize().height()); } void KoCanvasControllerWidget::ensureVisible(KoShape *shape) { Q_ASSERT(shape); ensureVisible(d->canvas->viewConverter()->documentToView(shape->boundingRect())); } void KoCanvasControllerWidget::ensureVisible(const QRectF &rect, bool smooth) { QRect currentVisible(-canvasOffsetX(), -canvasOffsetY(), visibleWidth(), visibleHeight()); QRect viewRect = rect.toRect(); viewRect.translate(d->canvas->documentOrigin()); if (!viewRect.isValid() || currentVisible.contains(viewRect)) return; // its visible. Nothing to do. // if we move, we move a little more so the amount of times we have to move is less. int jumpWidth = smooth ? 0 : currentVisible.width() / 5; int jumpHeight = smooth ? 0 : currentVisible.height() / 5; if (!smooth && viewRect.width() + jumpWidth > currentVisible.width()) jumpWidth = 0; if (!smooth && viewRect.height() + jumpHeight > currentVisible.height()) jumpHeight = 0; int horizontalMove = 0; if (currentVisible.width() <= viewRect.width()) // center view horizontalMove = viewRect.center().x() - currentVisible.center().x(); else if (currentVisible.x() > viewRect.x()) // move left horizontalMove = viewRect.x() - currentVisible.x() - jumpWidth; else if (currentVisible.right() < viewRect.right()) // move right horizontalMove = viewRect.right() - qMax(0, currentVisible.right() - jumpWidth); int verticalMove = 0; if (currentVisible.height() <= viewRect.height()) // center view verticalMove = viewRect.center().y() - currentVisible.center().y(); if (currentVisible.y() > viewRect.y()) // move up verticalMove = viewRect.y() - currentVisible.y() - jumpHeight; else if (currentVisible.bottom() < viewRect.bottom()) // move down verticalMove = viewRect.bottom() - qMax(0, currentVisible.bottom() - jumpHeight); pan(QPoint(horizontalMove, verticalMove)); } void KoCanvasControllerWidget::recenterPreferred() { const bool oldIgnoreScrollSignals = d->ignoreScrollSignals; d->ignoreScrollSignals = true; QPointF center = preferredCenter(); // convert into a viewport based point center.rx() += d->canvas->canvasWidget()->x() + frameWidth(); center.ry() += d->canvas->canvasWidget()->y() + frameWidth(); // scroll to a new center point QPointF topLeft = center - 0.5 * QPointF(viewport()->width(), viewport()->height()); setScrollBarValue(topLeft.toPoint()); d->ignoreScrollSignals = oldIgnoreScrollSignals; } void KoCanvasControllerWidget::zoomIn(const QPoint ¢er) { zoomBy(center, sqrt(2.0)); } void KoCanvasControllerWidget::zoomOut(const QPoint ¢er) { zoomBy(center, sqrt(0.5)); } void KoCanvasControllerWidget::zoomBy(const QPoint ¢er, qreal zoom) { setPreferredCenterFractionX(1.0 * center.x() / documentSize().width()); setPreferredCenterFractionY(1.0 * center.y() / documentSize().height()); const bool oldIgnoreScrollSignals = d->ignoreScrollSignals; d->ignoreScrollSignals = true; proxyObject->emitZoomRelative(zoom, preferredCenter()); d->ignoreScrollSignals = oldIgnoreScrollSignals; } void KoCanvasControllerWidget::zoomTo(const QRect &viewRect) { qreal scale; if (1.0 * viewport()->width() / viewRect.width() > 1.0 * viewport()->height() / viewRect.height()) scale = 1.0 * viewport()->height() / viewRect.height(); else scale = 1.0 * viewport()->width() / viewRect.width(); zoomBy(viewRect.center(), scale); } void KoCanvasControllerWidget::updateDocumentSize(const QSize &sz, bool recalculateCenter) { // Don't update if the document-size didn't changed to prevent infinite loops and unneeded updates. if (KoCanvasController::documentSize() == sz) return; if (!recalculateCenter) { // assume the distance from the top stays equal and recalculate the center. setPreferredCenterFractionX(documentSize().width() * preferredCenterFractionX() / sz.width()); setPreferredCenterFractionY(documentSize().height() * preferredCenterFractionY() / sz.height()); } const bool oldIgnoreScrollSignals = d->ignoreScrollSignals; d->ignoreScrollSignals = true; KoCanvasController::setDocumentSize(sz); d->viewportWidget->setDocumentSize(sz); d->resetScrollBars(); // Always emit the new offset. updateCanvasOffsetX(); updateCanvasOffsetY(); d->ignoreScrollSignals = oldIgnoreScrollSignals; } void KoCanvasControllerWidget::setZoomWithWheel(bool zoom) { d->zoomWithWheel = zoom; } void KoCanvasControllerWidget::setVastScrolling(qreal factor) { d->vastScrollingFactor = factor; } QPointF KoCanvasControllerWidget::currentCursorPosition() const { QWidget *canvasWidget = d->canvas->canvasWidget(); const KoViewConverter *converter = d->canvas->viewConverter(); return converter->viewToDocument(canvasWidget->mapFromGlobal(QCursor::pos()) + d->canvas->canvasController()->documentOffset() - canvasWidget->pos()); } void KoCanvasControllerWidget::pan(const QPoint &distance) { QPoint sourcePoint = scrollBarValue(); setScrollBarValue(sourcePoint + distance); } void KoCanvasControllerWidget::panUp() { pan(QPoint(0, verticalScrollBar()->singleStep())); } void KoCanvasControllerWidget::panDown() { pan(QPoint(0, -verticalScrollBar()->singleStep())); } void KoCanvasControllerWidget::panLeft() { pan(QPoint(horizontalScrollBar()->singleStep(), 0)); } void KoCanvasControllerWidget::panRight() { pan(QPoint(-horizontalScrollBar()->singleStep(), 0)); } void KoCanvasControllerWidget::setPreferredCenter(const QPointF &viewPoint) { setPreferredCenterFractionX(viewPoint.x() / documentSize().width()); setPreferredCenterFractionY(viewPoint.y() / documentSize().height()); recenterPreferred(); } QPointF KoCanvasControllerWidget::preferredCenter() const { QPointF center; center.setX(preferredCenterFractionX() * documentSize().width()); center.setY(preferredCenterFractionY() * documentSize().height()); return center; } void KoCanvasControllerWidget::paintEvent(QPaintEvent *event) { QPainter gc(viewport()); d->viewportWidget->handlePaintEvent(gc, event); } void KoCanvasControllerWidget::dragEnterEvent(QDragEnterEvent *event) { d->viewportWidget->handleDragEnterEvent(event); } void KoCanvasControllerWidget::dropEvent(QDropEvent *event) { d->viewportWidget->handleDropEvent(event); } void KoCanvasControllerWidget::dragMoveEvent(QDragMoveEvent *event) { d->viewportWidget->handleDragMoveEvent(event); } void KoCanvasControllerWidget::dragLeaveEvent(QDragLeaveEvent *event) { d->viewportWidget->handleDragLeaveEvent(event); } void KoCanvasControllerWidget::wheelEvent(QWheelEvent *event) { if (d->zoomWithWheel != ((event->modifiers() & Qt::ControlModifier) == Qt::ControlModifier)) { const qreal zoomCoeff = event->delta() > 0 ? sqrt(2.0) : sqrt(0.5); zoomRelativeToPoint(event->pos(), zoomCoeff); event->accept(); } else QAbstractScrollArea::wheelEvent(event); } void KoCanvasControllerWidget::zoomRelativeToPoint(const QPoint &widgetPoint, qreal zoomCoeff) { const QPoint offset = scrollBarValue(); const QPoint mousePos(widgetPoint + offset); const bool oldIgnoreScrollSignals = d->ignoreScrollSignals; d->ignoreScrollSignals = true; proxyObject->emitZoomRelative(zoomCoeff, mousePos); d->ignoreScrollSignals = oldIgnoreScrollSignals; } bool KoCanvasControllerWidget::focusNextPrevChild(bool) { // we always return false meaning the canvas takes keyboard focus, but never gives it away. return false; } void KoCanvasControllerWidget::setMargin(int margin) { KoCanvasController::setMargin(margin); Q_ASSERT(d->viewportWidget); d->viewportWidget->setMargin(margin); } QPoint KoCanvasControllerWidget::scrollBarValue() const { QScrollBar * hBar = horizontalScrollBar(); QScrollBar * vBar = verticalScrollBar(); return QPoint(hBar->value(), vBar->value()); } void KoCanvasControllerWidget::setScrollBarValue(const QPoint &value) { QScrollBar * hBar = horizontalScrollBar(); QScrollBar * vBar = verticalScrollBar(); hBar->setValue(value.x()); vBar->setValue(value.y()); } KoCanvasControllerWidget::Private *KoCanvasControllerWidget::priv() { return d; } //have to include this because of Q_PRIVATE_SLOT #include "moc_KoCanvasControllerWidget.cpp" diff --git a/libs/flake/KoCanvasControllerWidget.h b/libs/flake/KoCanvasControllerWidget.h index 142150db01..0ffb9dd91d 100644 --- a/libs/flake/KoCanvasControllerWidget.h +++ b/libs/flake/KoCanvasControllerWidget.h @@ -1,189 +1,187 @@ /* This file is part of the KDE project * Copyright (C) 2006, 2008 Thomas Zander * Copyright (C) 2007-2010 Boudewijn Rempt * Copyright (C) 2007-2008 C. Boemann * Copyright (C) 2006-2007 Jan Hambrecht * Copyright (C) 2009 Thorsten Zachmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOCANVASCONTROLLERWIDGET_H #define KOCANVASCONTROLLERWIDGET_H #include "kritaflake_export.h" #include #include #include "KoCanvasController.h" class KoShape; class KoCanvasBase; /** * KoCanvasController implementation for QWidget based canvases */ class KRITAFLAKE_EXPORT KoCanvasControllerWidget : public QAbstractScrollArea, public KoCanvasController { Q_OBJECT public: /** * Constructor. * @param parent the parent this widget will belong to */ explicit KoCanvasControllerWidget(KActionCollection * actionCollection, QWidget *parent = 0); ~KoCanvasControllerWidget() override; /** * Reimplemented from QAbstractScrollArea. */ void scrollContentsBy(int dx, int dy) override; QSize viewportSize() const override; /// Reimplemented from KoCanvasController /** * Activate this canvascontroller */ virtual void activate(); - void setDrawShadow(bool drawShadow) override; - void setCanvas(KoCanvasBase *canvas) override; KoCanvasBase *canvas() const override; /** * Change the actual canvas widget used by the current canvas. This allows the canvas widget * to be changed while keeping the current KoCanvasBase canvas and its associated resources as * they are. This might be used, for example, to switch from a QWidget to a QOpenGLWidget canvas. * @param widget the new canvas widget. */ virtual void changeCanvasWidget(QWidget *widget); int visibleHeight() const override; int visibleWidth() const override; int canvasOffsetX() const override; int canvasOffsetY() const override; void ensureVisible(const QRectF &rect, bool smooth = false) override; void ensureVisible(KoShape *shape) override; /** * will cause the toolOptionWidgetsChanged to be emitted and all * listeners to be updated to the new widget. * * FIXME: This doesn't belong her and it does an * inherits("KoView") so it too much tied to komain * * @param widgets the map of widgets */ void setToolOptionWidgets(const QList > &widgets); void zoomIn(const QPoint ¢er) override; void zoomOut(const QPoint ¢er) override; void zoomBy(const QPoint ¢er, qreal zoom) override; void zoomTo(const QRect &rect) override; /** * Zoom document keeping point \p widgetPoint unchanged * \param widgetPoint sticky point in widget pixels */ virtual void zoomRelativeToPoint(const QPoint &widgetPoint, qreal zoomCoeff); void recenterPreferred() override; void setPreferredCenter(const QPointF &viewPoint) override; /// Returns the currently set preferred center point in view coordinates (pixels) QPointF preferredCenter() const override; void pan(const QPoint &distance) override; virtual void panUp() override; virtual void panDown() override; virtual void panLeft() override; virtual void panRight() override; void setMargin(int margin) override; QPoint scrollBarValue() const override; /** * Used by KisCanvasController to correct the scrollbars position * after the rotation. */ void setScrollBarValue(const QPoint &value) override; void updateDocumentSize(const QSize &sz, bool recalculateCenter = true) override; /** * Set mouse wheel to zoom behaviour * @param zoom if true wheel will zoom instead of scroll, control modifier will scroll */ void setZoomWithWheel(bool zoom) override; void setVastScrolling(qreal factor) override; QPointF currentCursorPosition() const override; /** * \internal */ class Private; KoCanvasControllerWidget::Private *priv(); private Q_SLOTS: /// Called by the horizontal scrollbar when its value changes void updateCanvasOffsetX(); /// Called by the vertical scrollbar when its value changes void updateCanvasOffsetY(); protected: friend class KisZoomAndPanTest; /// reimplemented from QWidget void paintEvent(QPaintEvent *event) override; /// reimplemented from QWidget void resizeEvent(QResizeEvent *resizeEvent) override; /// reimplemented from QWidget void dragEnterEvent(QDragEnterEvent *event) override; /// reimplemented from QWidget void dropEvent(QDropEvent *event) override; /// reimplemented from QWidget void dragMoveEvent(QDragMoveEvent *event) override; /// reimplemented from QWidget void dragLeaveEvent(QDragLeaveEvent *event) override; /// reimplemented from QWidget void wheelEvent(QWheelEvent *event) override; /// reimplemented from QWidget bool focusNextPrevChild(bool next) override; private: Q_PRIVATE_SLOT(d, void activate()) Private * const d; }; #endif diff --git a/libs/flake/KoCanvasControllerWidgetViewport_p.cpp b/libs/flake/KoCanvasControllerWidgetViewport_p.cpp index 1c8e6f3d8d..e64484997f 100644 --- a/libs/flake/KoCanvasControllerWidgetViewport_p.cpp +++ b/libs/flake/KoCanvasControllerWidgetViewport_p.cpp @@ -1,421 +1,332 @@ /* This file is part of the KDE project * * Copyright (C) 2006-2007, 2009 Thomas Zander * Copyright (C) 2006 Thorsten Zachmann * Copyright (C) 2007-2010 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoCanvasControllerWidgetViewport_p.h" #include #include #include #include #include #include #include #include "KoShape.h" #include "KoShape_p.h" #include "KoShapeFactoryBase.h" // for the SHAPE mimetypes #include "KoShapeRegistry.h" #include "KoShapeController.h" #include "KoShapeManager.h" #include "KoSelection.h" #include "KoCanvasBase.h" #include "KoShapeLayer.h" #include "KoShapePaintingContext.h" #include "KoToolProxy.h" #include "KoCanvasControllerWidget.h" #include "KoViewConverter.h" #include "KoSvgPaste.h" // ********** Viewport ********** Viewport::Viewport(KoCanvasControllerWidget *parent) : QWidget(parent) , m_draggedShape(0) - , m_drawShadow(false) , m_canvas(0) , m_documentOffset(QPoint(0, 0)) , m_margin(0) { setAutoFillBackground(true); setAcceptDrops(true); setMouseTracking(true); m_parent = parent; } void Viewport::setCanvas(QWidget *canvas) { if (m_canvas) { m_canvas->hide(); delete m_canvas; } m_canvas = canvas; if (!canvas) return; m_canvas->setParent(this); m_canvas->show(); if (!m_canvas->minimumSize().isNull()) { m_documentSize = m_canvas->minimumSize(); } resetLayout(); } void Viewport::setDocumentSize(const QSize &size) { m_documentSize = size; resetLayout(); } void Viewport::documentOffsetMoved(const QPoint &pt) { m_documentOffset = pt; resetLayout(); } -void Viewport::setDrawShadow(bool drawShadow) -{ - m_drawShadow = drawShadow; -} - void Viewport::handleDragEnterEvent(QDragEnterEvent *event) { // if not a canvas set then ignore this, makes it possible to assume // we have a canvas in all the support methods. if (!(m_parent->canvas() && m_parent->canvas()->canvasWidget())) { event->ignore(); return; } delete m_draggedShape; m_draggedShape = 0; // only allow dropping when active layer is editable KoSelection *selection = m_parent->canvas()->shapeManager()->selection(); KoShapeLayer *activeLayer = selection->activeLayer(); if (activeLayer && (!activeLayer->isShapeEditable() || activeLayer->isGeometryProtected())) { event->ignore(); return; } const QMimeData *data = event->mimeData(); if (data->hasFormat(SHAPETEMPLATE_MIMETYPE) || data->hasFormat(SHAPEID_MIMETYPE) || data->hasFormat("image/svg+xml")) { if (data->hasFormat("image/svg+xml")) { KoCanvasBase *canvas = m_parent->canvas(); QSizeF fragmentSize; QList shapes = KoSvgPaste::fetchShapesFromData(data->data("image/svg+xml"), canvas->shapeController()->documentRectInPixels(), canvas->shapeController()->pixelsPerInch(), &fragmentSize); if (!shapes.isEmpty()) { m_draggedShape = shapes[0]; } } else { QByteArray itemData; bool isTemplate = true; if (data->hasFormat(SHAPETEMPLATE_MIMETYPE)) { itemData = data->data(SHAPETEMPLATE_MIMETYPE); } else if (data->hasFormat(SHAPEID_MIMETYPE)) { isTemplate = false; itemData = data->data(SHAPEID_MIMETYPE); } QDataStream dataStream(&itemData, QIODevice::ReadOnly); QString id; dataStream >> id; QString properties; if (isTemplate) dataStream >> properties; // and finally, there is a point. QPointF offset; dataStream >> offset; // The rest of this method is mostly a copy paste from the KoCreateShapeStrategy // So, lets remove this again when Zagge adds his new class that does this kind of thing. (KoLoadSave) KoShapeFactoryBase *factory = KoShapeRegistry::instance()->value(id); if (! factory) { warnFlake << "Application requested a shape that is not registered '" << id << "', Ignoring"; event->ignore(); return; } if (isTemplate) { KoProperties props; props.load(properties); m_draggedShape = factory->createShape(&props, m_parent->canvas()->shapeController()->resourceManager()); } else { m_draggedShape = factory->createDefaultShape(m_parent->canvas()->shapeController()->resourceManager()); } if (m_draggedShape->shapeId().isEmpty()) { m_draggedShape->setShapeId(factory->id()); } } event->setDropAction(Qt::CopyAction); event->accept(); Q_ASSERT(m_draggedShape); if (!m_draggedShape) return; // calculate maximum existing shape zIndex int pasteZIndex = 0; { QList allShapes = m_parent->canvas()->shapeManager()->topLevelShapes(); if (!allShapes.isEmpty()) { std::sort(allShapes.begin(), allShapes.end(), KoShape::compareShapeZIndex); pasteZIndex = qMin(int(KoShape::maxZIndex), allShapes.last()->zIndex() + 1); } } m_draggedShape->setZIndex(pasteZIndex); m_draggedShape->setAbsolutePosition(correctPosition(event->pos())); m_parent->canvas()->shapeManager()->addShape(m_draggedShape); } else { event->ignore(); } } void Viewport::handleDropEvent(QDropEvent *event) { if (!m_draggedShape) { m_parent->canvas()->toolProxy()->dropEvent(event, correctPosition(event->pos())); return; } repaint(m_draggedShape); m_parent->canvas()->shapeManager()->remove(m_draggedShape); // remove it to not interfere with z-index calc. m_draggedShape->setPosition(QPointF(0, 0)); // always save position. QPointF newPos = correctPosition(event->pos()); m_parent->canvas()->clipToDocument(m_draggedShape, newPos); // ensure the shape is dropped inside the document. m_draggedShape->setAbsolutePosition(newPos); KUndo2Command * cmd = m_parent->canvas()->shapeController()->addShape(m_draggedShape, 0); if (cmd) { m_parent->canvas()->addCommand(cmd); KoSelection *selection = m_parent->canvas()->shapeManager()->selection(); // repaint selection before selecting newly create shape Q_FOREACH (KoShape * shape, selection->selectedShapes()) { shape->update(); } selection->deselectAll(); selection->select(m_draggedShape); } else { delete m_draggedShape; } m_draggedShape = 0; } QPointF Viewport::correctPosition(const QPoint &point) const { QWidget *canvasWidget = m_parent->canvas()->canvasWidget(); Q_ASSERT(canvasWidget); // since we should not allow drag if there is not. QPoint correctedPos(point.x() - canvasWidget->x(), point.y() - canvasWidget->y()); correctedPos += m_documentOffset; return m_parent->canvas()->viewToDocument(correctedPos); } void Viewport::handleDragMoveEvent(QDragMoveEvent *event) { if (!m_draggedShape) { m_parent->canvas()->toolProxy()->dragMoveEvent(event, correctPosition(event->pos())); return; } m_draggedShape->update(); repaint(m_draggedShape); m_draggedShape->setAbsolutePosition(correctPosition(event->pos())); m_draggedShape->update(); repaint(m_draggedShape); } void Viewport::repaint(KoShape *shape) { QRect rect = m_parent->canvas()->viewConverter()->documentToView(shape->boundingRect()).toRect(); QWidget *canvasWidget = m_parent->canvas()->canvasWidget(); Q_ASSERT(canvasWidget); // since we should not allow drag if there is not. rect.moveLeft(rect.left() + canvasWidget->x() - m_documentOffset.x()); rect.moveTop(rect.top() + canvasWidget->y() - m_documentOffset.y()); rect.adjust(-2, -2, 2, 2); // adjust for antialias update(rect); } void Viewport::handleDragLeaveEvent(QDragLeaveEvent *event) { if (m_draggedShape) { repaint(m_draggedShape); m_parent->canvas()->shapeManager()->remove(m_draggedShape); delete m_draggedShape; m_draggedShape = 0; } else { m_parent->canvas()->toolProxy()->dragLeaveEvent(event); } } void Viewport::handlePaintEvent(QPainter &painter, QPaintEvent *event) { Q_UNUSED(event); - // Draw the shadow around the canvas. - if (m_parent->canvas() && m_parent->canvas()->canvasWidget() && m_drawShadow) { - QWidget *canvas = m_parent->canvas()->canvasWidget(); - painter.setPen(QPen(Qt::black, 0)); - QRect rect(canvas->x(), canvas->y(), canvas->width(), canvas->height()); - rect.adjust(-1, -1, 0, 0); - painter.drawRect(rect); - painter.drawLine(rect.right() + 2, rect.top() + 2, rect.right() + 2, rect.bottom() + 2); - painter.drawLine(rect.left() + 2, rect.bottom() + 2, rect.right() + 2, rect.bottom() + 2); - } if (m_draggedShape) { const KoViewConverter *vc = m_parent->canvas()->viewConverter(); painter.save(); QWidget *canvasWidget = m_parent->canvas()->canvasWidget(); Q_ASSERT(canvasWidget); // since we should not allow drag if there is not. painter.translate(canvasWidget->x() - m_documentOffset.x(), canvasWidget->y() - m_documentOffset.y()); QPointF offset = vc->documentToView(m_draggedShape->position()); painter.setOpacity(0.6); painter.translate(offset.x(), offset.y()); painter.setRenderHint(QPainter::Antialiasing); KoShapePaintingContext paintContext; //FIXME m_draggedShape->paint(painter, *vc, paintContext); painter.restore(); } } void Viewport::resetLayout() { // Determine the area we have to show QRect viewRect(m_documentOffset, size()); const int viewH = viewRect.height(); const int viewW = viewRect.width(); - const int docH = m_documentSize.height(); - const int docW = m_documentSize.width(); - - int moveX = 0; - int moveY = 0; - - int resizeW = viewW; - int resizeH = viewH; - - // debugFlake <<"viewH:" << viewH << endl - // << "docH: " << docH << endl - // << "viewW: " << viewW << endl - // << "docW: " << docW << endl; - - if (viewH == docH && viewW == docW) { - // Do nothing - resizeW = docW; - resizeH = docH; - } else if (viewH > docH && viewW > docW) { - // Show entire canvas centered - moveX = (viewW - docW) / 2; - moveY = (viewH - docH) / 2; - resizeW = docW; - resizeH = docH; - } else if (viewW > docW) { - // Center canvas horizontally - moveX = (viewW - docW) / 2; - resizeW = docW; - - int marginTop = m_margin - m_documentOffset.y(); - int marginBottom = viewH - (m_documentSize.height() - m_documentOffset.y()); - - if (marginTop > 0) moveY = marginTop; - if (marginTop > 0) resizeH = viewH - marginTop; - if (marginBottom > 0) resizeH = viewH - marginBottom; - } else if (viewH > docH) { - // Center canvas vertically - moveY = (viewH - docH) / 2; - resizeH = docH; - - int marginLeft = m_margin - m_documentOffset.x(); - int marginRight = viewW - (m_documentSize.width() - m_documentOffset.x()); - - if (marginLeft > 0) moveX = marginLeft; - if (marginLeft > 0) resizeW = viewW - marginLeft; - if (marginRight > 0) resizeW = viewW - marginRight; - } else { - // Take care of the margin around the canvas - int marginTop = m_margin - m_documentOffset.y(); - int marginLeft = m_margin - m_documentOffset.x(); - int marginRight = viewW - (m_documentSize.width() - m_documentOffset.x()); - int marginBottom = viewH - (m_documentSize.height() - m_documentOffset.y()); - - if (marginTop > 0) moveY = marginTop; - if (marginLeft > 0) moveX = marginLeft; - - if (marginTop > 0) resizeH = viewH - marginTop; - if (marginLeft > 0) resizeW = viewW - marginLeft; - if (marginRight > 0) resizeW = viewW - marginRight; - if (marginBottom > 0) resizeH = viewH - marginBottom; - } - if (m_parent->canvasMode() == KoCanvasController::AlignTop) { - // have up to m_margin pixels at top. - moveY = qMin(m_margin, moveY); - } if (m_canvas) { - QRect geom; - if (m_parent->canvasMode() == KoCanvasController::Infinite) - geom = QRect(0, 0, viewW, viewH); - else - geom = QRect(moveX, moveY, resizeW, resizeH); + QRect geom = QRect(0, 0, viewW, viewH); if (m_canvas->geometry() != geom) { m_canvas->setGeometry(geom); m_canvas->update(); } } - if (m_drawShadow) { - update(); - } - emit sizeChanged(); #if 0 debugFlake <<"View port geom:" << geometry(); if (m_canvas) debugFlake <<"Canvas widget geom:" << m_canvas->geometry(); #endif } diff --git a/libs/flake/KoCanvasControllerWidgetViewport_p.h b/libs/flake/KoCanvasControllerWidgetViewport_p.h index c430e39e59..5c56595b07 100644 --- a/libs/flake/KoCanvasControllerWidgetViewport_p.h +++ b/libs/flake/KoCanvasControllerWidgetViewport_p.h @@ -1,91 +1,85 @@ /* This file is part of the KDE project * Copyright (C) 2006-2007 Thomas Zander * Copyright (C) 2007-2010 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOCANVASCONTROLLERWIDGETVIEWPORT_P_H #define KOCANVASCONTROLLERWIDGETVIEWPORT_P_H #include #include #include class KoCanvasControllerWidget; class KoShape; class Viewport : public QWidget { Q_OBJECT public: explicit Viewport(KoCanvasControllerWidget *parent); ~Viewport() override {} void setCanvas(QWidget *canvas); QWidget *canvas() const { return m_canvas; } void setDocumentSize(const QSize &size); - /** - * When true, a shadow is drawn around the canvas widet. - */ - void setDrawShadow(bool drawShadow); - public Q_SLOTS: void documentOffsetMoved(const QPoint &); Q_SIGNALS: void sizeChanged(); public: void handleDragEnterEvent(QDragEnterEvent *event); void handleDropEvent(QDropEvent *event); void handleDragMoveEvent(QDragMoveEvent *event); void handleDragLeaveEvent(QDragLeaveEvent *event); void handlePaintEvent(QPainter &gc, QPaintEvent *event); void setMargin(int margin) { m_margin = margin; resetLayout(); } private: QPointF correctPosition(const QPoint &point) const; void repaint(KoShape *shape); /** Decides whether the containing canvas widget should be as big as the viewport (i.e., no margins are visible) or whether there are margins to be left blank, and then places the canvas widget accordingly. */ void resetLayout(); private: KoCanvasControllerWidget *m_parent; KoShape *m_draggedShape; - bool m_drawShadow; QWidget *m_canvas; QSize m_documentSize; // Size in pixels of the document QPoint m_documentOffset; // Place where the canvas widget should int m_margin; // The viewport margin around the document }; #endif diff --git a/libs/flake/commands/KoSubpathJoinCommand.cpp b/libs/flake/commands/KoSubpathJoinCommand.cpp index 4f15ec4b2c..2d4bfa4e7e 100644 --- a/libs/flake/commands/KoSubpathJoinCommand.cpp +++ b/libs/flake/commands/KoSubpathJoinCommand.cpp @@ -1,173 +1,173 @@ /* This file is part of the KDE project * Copyright (C) 2006 Jan Hambrecht * Copyright (C) 2006,2007 Thorsten Zachmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoSubpathJoinCommand.h" #include #include "kis_assert.h" #include "KoPathMergeUtils.h" KoSubpathJoinCommand::KoSubpathJoinCommand(const KoPathPointData &pointData1, const KoPathPointData &pointData2, KUndo2Command *parent) : KUndo2Command(parent) , m_pointData1(pointData1) , m_pointData2(pointData2) , m_splitIndex(KoPathPointIndex(-1, -1)) , m_oldProperties1(KoPathPoint::Normal) , m_oldProperties2(KoPathPoint::Normal) , m_reverse(0) { KIS_ASSERT(m_pointData1.pathShape == m_pointData2.pathShape); KoPathShape * pathShape = m_pointData1.pathShape; KIS_ASSERT(!pathShape->isClosedSubpath(m_pointData1.pointIndex.first)); KIS_ASSERT(m_pointData1.pointIndex.second == 0 || m_pointData1.pointIndex.second == pathShape->subpathPointCount(m_pointData1.pointIndex.first) - 1); KIS_ASSERT(!pathShape->isClosedSubpath(m_pointData2.pointIndex.first)); KIS_ASSERT(m_pointData2.pointIndex.second == 0 || m_pointData2.pointIndex.second == pathShape->subpathPointCount(m_pointData2.pointIndex.first) - 1); //TODO check that points are not the same if (m_pointData2 < m_pointData1) { std::swap(m_pointData1, m_pointData2); } if (!closeSubpathMode()) { if (m_pointData1.pointIndex.second == 0 && pathShape->subpathPointCount(m_pointData1.pointIndex.first) > 1) m_reverse |= ReverseFirst; if (m_pointData2.pointIndex.second != 0) m_reverse |= ReverseSecond; setText(kundo2_i18n("Join subpaths")); } else { setText(kundo2_i18n("Close subpath")); } KoPathPoint * point1 = pathShape->pointByIndex(m_pointData1.pointIndex); KoPathPoint * point2 = pathShape->pointByIndex(m_pointData2.pointIndex); - m_savedControlPoint1 = KritaUtils::fetchControlPoint(point1, m_reverse && ReverseFirst); - m_savedControlPoint2 = KritaUtils::fetchControlPoint(point2, !(m_reverse && ReverseSecond)); + m_savedControlPoint1 = KritaUtils::fetchControlPoint(point1, m_reverse & ReverseFirst); + m_savedControlPoint2 = KritaUtils::fetchControlPoint(point2, !(m_reverse & ReverseSecond)); m_oldProperties1 = point1->properties(); m_oldProperties2 = point2->properties(); } KoSubpathJoinCommand::~KoSubpathJoinCommand() { } void KoSubpathJoinCommand::redo() { KUndo2Command::redo(); KoPathShape * pathShape = m_pointData1.pathShape; KoPathPoint * point1 = pathShape->pointByIndex(m_pointData1.pointIndex); KoPathPoint * point2 = pathShape->pointByIndex(m_pointData2.pointIndex); KIS_SAFE_ASSERT_RECOVER_RETURN(point1); KIS_SAFE_ASSERT_RECOVER_RETURN(point2); // if the endpoint has a control point create a control point for the new segment to be // at the symmetric position to the exiting one if (closeSubpathMode()) { KritaUtils::makeSymmetric(point1, false); KritaUtils::makeSymmetric(point2, true); } else { KritaUtils::makeSymmetric(point1, !(m_reverse & ReverseFirst)); KritaUtils::makeSymmetric(point2, m_reverse & ReverseSecond); } if (closeSubpathMode()) { pathShape->closeSubpath(m_pointData1.pointIndex); } else { if (m_reverse & ReverseFirst) { pathShape->reverseSubpath(m_pointData1.pointIndex.first); } if (m_reverse & ReverseSecond) { pathShape->reverseSubpath(m_pointData2.pointIndex.first); } pathShape->moveSubpath(m_pointData2.pointIndex.first, m_pointData1.pointIndex.first + 1); m_splitIndex = m_pointData1.pointIndex; m_splitIndex.second = pathShape->subpathPointCount(m_pointData1.pointIndex.first) - 1; pathShape->join(m_pointData1.pointIndex.first); } QList pointIndexes; pointIndexes << pathShape->pathPointIndex(point1); pointIndexes << pathShape->pathPointIndex(point2); pathShape->recommendPointSelectionChange(pointIndexes); pathShape->normalize(); pathShape->update(); } void KoSubpathJoinCommand::undo() { KUndo2Command::undo(); KoPathShape * pathShape = m_pointData1.pathShape; pathShape->update(); if (closeSubpathMode()) { pathShape->openSubpath(m_pointData1.pointIndex); } else { pathShape->breakAfter(m_splitIndex); pathShape->moveSubpath(m_pointData1.pointIndex.first + 1, m_pointData2.pointIndex.first); if (m_reverse & ReverseSecond) { pathShape->reverseSubpath(m_pointData2.pointIndex.first); } if (m_reverse & ReverseFirst) { pathShape->reverseSubpath(m_pointData1.pointIndex.first); } } KoPathPoint * point1 = pathShape->pointByIndex(m_pointData1.pointIndex); KoPathPoint * point2 = pathShape->pointByIndex(m_pointData2.pointIndex); KIS_SAFE_ASSERT_RECOVER_RETURN(point1); KIS_SAFE_ASSERT_RECOVER_RETURN(point2); // restore the old end points if (closeSubpathMode()) { KritaUtils::restoreControlPoint(point1, true, m_savedControlPoint1); KritaUtils::restoreControlPoint(point2, false, m_savedControlPoint2); } else { KritaUtils::restoreControlPoint(point1, m_reverse & ReverseFirst, m_savedControlPoint1); KritaUtils::restoreControlPoint(point2, !(m_reverse & ReverseSecond), m_savedControlPoint2); } point1->setProperties(m_oldProperties1); point2->setProperties(m_oldProperties2); QList pointIndexes; pointIndexes << pathShape->pathPointIndex(point1); pointIndexes << pathShape->pathPointIndex(point2); pathShape->recommendPointSelectionChange(pointIndexes); pathShape->normalize(); pathShape->update(); } bool KoSubpathJoinCommand::closeSubpathMode() const { return m_pointData1.pointIndex.first == m_pointData2.pointIndex.first; } diff --git a/libs/flake/tools/PathToolOptionWidgetBase.ui b/libs/flake/tools/PathToolOptionWidgetBase.ui index d7ec32cfc9..fb992dc888 100644 --- a/libs/flake/tools/PathToolOptionWidgetBase.ui +++ b/libs/flake/tools/PathToolOptionWidgetBase.ui @@ -1,271 +1,271 @@ PathToolOptionWidgetBase 0 0 321 208 0 0 0 0 Qt::Horizontal 1 0 - 0 + 1 0 0 0 0 3 ... ... ... Qt::Horizontal 40 20 ... ... ... ... ... ... ... ... ... ... Qt::Vertical Qt::Vertical Qt::Vertical QSizePolicy::Minimum 20 0 0 1 1 0 0 0 0 0 - Convert To Path + Convert to Path Qt::Horizontal 40 20 Qt::Vertical 20 150 diff --git a/libs/global/kis_global.h b/libs/global/kis_global.h index 84cb472528..37777ba1f5 100644 --- a/libs/global/kis_global.h +++ b/libs/global/kis_global.h @@ -1,273 +1,272 @@ /* * Copyright (c) 2000 Matthias Elter * Copyright (c) 2002 Patrick Julien * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KISGLOBAL_H_ #define KISGLOBAL_H_ #include #include #include "kis_assert.h" #include #include const quint8 quint8_MAX = UCHAR_MAX; const quint16 quint16_MAX = 65535; const qint32 qint32_MAX = (2147483647); const qint32 qint32_MIN = (-2147483647 - 1); const quint8 MAX_SELECTED = UCHAR_MAX; const quint8 MIN_SELECTED = 0; const quint8 SELECTION_THRESHOLD = 1; enum OutlineStyle { OUTLINE_NONE = 0, OUTLINE_CIRCLE, OUTLINE_FULL, OUTLINE_TILT, - OUTLINE_COLOR, N_OUTLINE_STYLE_SIZE }; enum CursorStyle { CURSOR_STYLE_NO_CURSOR = 0, CURSOR_STYLE_TOOLICON, CURSOR_STYLE_POINTER, CURSOR_STYLE_SMALL_ROUND, CURSOR_STYLE_CROSSHAIR, CURSOR_STYLE_TRIANGLE_RIGHTHANDED, CURSOR_STYLE_TRIANGLE_LEFTHANDED, CURSOR_STYLE_BLACK_PIXEL, CURSOR_STYLE_WHITE_PIXEL, N_CURSOR_STYLE_SIZE }; enum OldCursorStyle { OLD_CURSOR_STYLE_TOOLICON = 0, OLD_CURSOR_STYLE_CROSSHAIR = 1, OLD_CURSOR_STYLE_POINTER = 2, OLD_CURSOR_STYLE_OUTLINE = 3, OLD_CURSOR_STYLE_NO_CURSOR = 4, OLD_CURSOR_STYLE_SMALL_ROUND = 5, OLD_CURSOR_STYLE_OUTLINE_CENTER_DOT = 6, OLD_CURSOR_STYLE_OUTLINE_CENTER_CROSS = 7, OLD_CURSOR_STYLE_TRIANGLE_RIGHTHANDED = 8, OLD_CURSOR_STYLE_TRIANGLE_LEFTHANDED = 9, OLD_CURSOR_STYLE_OUTLINE_TRIANGLE_RIGHTHANDED = 10, OLD_CURSOR_STYLE_OUTLINE_TRIANGLE_LEFTHANDED = 11 }; /* * Most wacom pads have 512 levels of pressure; Qt only supports 256, and even * this is downscaled to 127 levels because the line would be too jittery, and * the amount of masks take too much memory otherwise. */ const qint32 PRESSURE_LEVELS = 127; const double PRESSURE_MIN = 0.0; const double PRESSURE_MAX = 1.0; const double PRESSURE_DEFAULT = PRESSURE_MAX; const double PRESSURE_THRESHOLD = 5.0 / 255.0; // copy of lcms.h #define INTENT_PERCEPTUAL 0 #define INTENT_RELATIVE_COLORIMETRIC 1 #define INTENT_SATURATION 2 #define INTENT_ABSOLUTE_COLORIMETRIC 3 #include #include #ifndef M_PI #define M_PI 3.14159265358979323846 #endif // converts \p a to [0, 2 * M_PI) range inline qreal normalizeAngle(qreal a) { if (a < 0.0) { a = 2 * M_PI + fmod(a, 2 * M_PI); } return a >= 2 * M_PI ? fmod(a, 2 * M_PI) : a; } // converts \p a to [0, 360.0) range inline qreal normalizeAngleDegrees(qreal a) { if (a < 0.0) { a = 360.0 + fmod(a, 360.0); } return a >= 360.0 ? fmod(a, 360.0) : a; } inline qreal shortestAngularDistance(qreal a, qreal b) { qreal dist = fmod(qAbs(a - b), 2 * M_PI); if (dist > M_PI) dist = 2 * M_PI - dist; return dist; } inline qreal incrementInDirection(qreal a, qreal inc, qreal direction) { qreal b1 = a + inc; qreal b2 = a - inc; qreal d1 = shortestAngularDistance(b1, direction); qreal d2 = shortestAngularDistance(b2, direction); return d1 < d2 ? b1 : b2; } inline qreal bisectorAngle(qreal a, qreal b) { const qreal diff = shortestAngularDistance(a, b); return incrementInDirection(a, 0.5 * diff, b); } template inline PointType snapToClosestAxis(PointType P) { if (qAbs(P.x()) < qAbs(P.y())) { P.setX(0); } else { P.setY(0); } return P; } template inline T pow2(const T& x) { return x * x; } template inline T kisDegreesToRadians(T degrees) { return degrees * M_PI / 180.0; } template inline T kisRadiansToDegrees(T radians) { return radians * 180.0 / M_PI; } template inline T kisGrowRect(const T &rect, U offset) { return rect.adjusted(-offset, -offset, offset, offset); } inline qreal kisDistance(const QPointF &pt1, const QPointF &pt2) { return std::sqrt(pow2(pt1.x() - pt2.x()) + pow2(pt1.y() - pt2.y())); } inline qreal kisSquareDistance(const QPointF &pt1, const QPointF &pt2) { return pow2(pt1.x() - pt2.x()) + pow2(pt1.y() - pt2.y()); } #include inline qreal kisDistanceToLine(const QPointF &m, const QLineF &line) { const QPointF &p1 = line.p1(); const QPointF &p2 = line.p2(); qreal distance = 0; if (qFuzzyCompare(p1.x(), p2.x())) { distance = qAbs(m.x() - p2.x()); } else if (qFuzzyCompare(p1.y(), p2.y())) { distance = qAbs(m.y() - p2.y()); } else { qreal A = 1; qreal B = - (p1.x() - p2.x()) / (p1.y() - p2.y()); qreal C = - p1.x() - B * p1.y(); distance = qAbs(A * m.x() + B * m.y() + C) / std::sqrt(pow2(A) + pow2(B)); } return distance; } inline QPointF kisProjectOnVector(const QPointF &base, const QPointF &v) { const qreal prod = base.x() * v.x() + base.y() * v.y(); const qreal lengthSq = pow2(base.x()) + pow2(base.y()); qreal coeff = prod / lengthSq; return coeff * base; } #include inline QRect kisEnsureInRect(QRect rc, const QRect &bounds) { if(rc.right() > bounds.right()) { rc.translate(bounds.right() - rc.right(), 0); } if(rc.left() < bounds.left()) { rc.translate(bounds.left() - rc.left(), 0); } if(rc.bottom() > bounds.bottom()) { rc.translate(0, bounds.bottom() - rc.bottom()); } if(rc.top() < bounds.top()) { rc.translate(0, bounds.top() - rc.top()); } return rc; } #include "kis_pointer_utils.h" /** * A special wrapper object that converts Qt-style mutexes and locks * into an object that supports Std's (and Boost's) "Lockable" * concept. Basically, it converts tryLock() into try_lock() to comply * with the syntax. */ template struct StdLockableWrapper { StdLockableWrapper(T *lock) : m_lock(lock) {} void lock() { m_lock->lock(); } bool try_lock() { return m_lock->tryLock(); } void unlock() { m_lock->unlock(); } private: T *m_lock; }; #endif // KISGLOBAL_H_ diff --git a/libs/image/brushengine/kis_paintop_settings.cpp b/libs/image/brushengine/kis_paintop_settings.cpp index ce465f7f8c..dd515f806f 100644 --- a/libs/image/brushengine/kis_paintop_settings.cpp +++ b/libs/image/brushengine/kis_paintop_settings.cpp @@ -1,524 +1,524 @@ /* * Copyright (c) 2007 Boudewijn Rempt * Copyright (c) 2008 Lukáš Tvrdý * Copyright (c) 2014 Mohit Goyal * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include "kis_paint_layer.h" #include "kis_image.h" #include "kis_painter.h" #include "kis_paint_device.h" #include "kis_paintop_registry.h" #include "kis_timing_information.h" #include #include "kis_paintop_config_widget.h" #include #include "kis_paintop_settings_update_proxy.h" #include #include #include #include #include #include "KisPaintopSettingsIds.h" struct Q_DECL_HIDDEN KisPaintOpSettings::Private { Private() : disableDirtyNotifications(false) {} QPointer settingsWidget; QString modelName; KisPaintOpPresetWSP preset; QList uniformProperties; bool disableDirtyNotifications; class DirtyNotificationsLocker { public: DirtyNotificationsLocker(KisPaintOpSettings::Private *d) : m_d(d), m_oldNotificationsState(d->disableDirtyNotifications) { m_d->disableDirtyNotifications = true; } ~DirtyNotificationsLocker() { m_d->disableDirtyNotifications = m_oldNotificationsState; } private: KisPaintOpSettings::Private *m_d; bool m_oldNotificationsState; Q_DISABLE_COPY(DirtyNotificationsLocker) }; KisPaintopSettingsUpdateProxy* updateProxyNoCreate() const { auto presetSP = preset.toStrongRef(); return presetSP ? presetSP->updateProxyNoCreate() : 0; } KisPaintopSettingsUpdateProxy* updateProxyCreate() const { auto presetSP = preset.toStrongRef(); return presetSP ? presetSP->updateProxy() : 0; } }; KisPaintOpSettings::KisPaintOpSettings() : d(new Private) { d->preset = 0; } KisPaintOpSettings::~KisPaintOpSettings() { } KisPaintOpSettings::KisPaintOpSettings(const KisPaintOpSettings &rhs) : KisPropertiesConfiguration(rhs) , d(new Private) { d->settingsWidget = 0; d->preset = rhs.preset(); d->modelName = rhs.modelName(); } void KisPaintOpSettings::setOptionsWidget(KisPaintOpConfigWidget* widget) { d->settingsWidget = widget; } void KisPaintOpSettings::setPreset(KisPaintOpPresetWSP preset) { d->preset = preset; } KisPaintOpPresetWSP KisPaintOpSettings::preset() const { return d->preset; } bool KisPaintOpSettings::mousePressEvent(const KisPaintInformation &paintInformation, Qt::KeyboardModifiers modifiers, KisNodeWSP currentNode) { Q_UNUSED(modifiers); Q_UNUSED(currentNode); setRandomOffset(paintInformation); return true; // ignore the event by default } void KisPaintOpSettings::setRandomOffset(const KisPaintInformation &paintInformation) { if (getBool("Texture/Pattern/Enabled")) { if (getBool("Texture/Pattern/isRandomOffsetX")) { setProperty("Texture/Pattern/OffsetX", paintInformation.randomSource()->generate(0, KisPropertiesConfiguration::getInt("Texture/Pattern/MaximumOffsetX"))); } if (getBool("Texture/Pattern/isRandomOffsetY")) { setProperty("Texture/Pattern/OffsetY", paintInformation.randomSource()->generate(0, KisPropertiesConfiguration::getInt("Texture/Pattern/MaximumOffsetY"))); } } } bool KisPaintOpSettings::hasMaskingSettings() const { return getBool(KisPaintOpUtils::MaskingBrushEnabledTag, false); } KisPaintOpSettingsSP KisPaintOpSettings::createMaskingSettings() const { if (!hasMaskingSettings()) return KisPaintOpSettingsSP(); const KoID pixelBrushId(KisPaintOpUtils::MaskingBrushPaintOpId, QString()); KisPaintOpSettingsSP maskingSettings = KisPaintOpRegistry::instance()->settings(pixelBrushId); this->getPrefixedProperties(KisPaintOpUtils::MaskingBrushPresetPrefix, maskingSettings); const bool useMasterSize = this->getBool(KisPaintOpUtils::MaskingBrushUseMasterSizeTag, true); if (useMasterSize) { const qreal masterSizeCoeff = getDouble(KisPaintOpUtils::MaskingBrushMasterSizeCoeffTag, 1.0); maskingSettings->setPaintOpSize(masterSizeCoeff * paintOpSize()); } return maskingSettings; } QString KisPaintOpSettings::maskingBrushCompositeOp() const { return getString(KisPaintOpUtils::MaskingBrushCompositeOpTag, COMPOSITE_MULT); } KisPaintOpSettingsSP KisPaintOpSettings::clone() const { QString paintopID = getString("paintop"); if (paintopID.isEmpty()) return 0; KisPaintOpSettingsSP settings = KisPaintOpRegistry::instance()->settings(KoID(paintopID, "")); QMapIterator i(getProperties()); while (i.hasNext()) { i.next(); settings->setProperty(i.key(), QVariant(i.value())); } settings->setPreset(this->preset()); return settings; } void KisPaintOpSettings::resetSettings(const QStringList &preserveProperties) { QStringList allKeys = preserveProperties; allKeys << "paintop"; QHash preserved; Q_FOREACH (const QString &key, allKeys) { if (hasProperty(key)) { preserved[key] = getProperty(key); } } clearProperties(); for (auto it = preserved.constBegin(); it != preserved.constEnd(); ++it) { setProperty(it.key(), it.value()); } } void KisPaintOpSettings::activate() { } void KisPaintOpSettings::setPaintOpOpacity(qreal value) { KisLockedPropertiesProxySP proxy( KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(this)); proxy->setProperty("OpacityValue", value); } void KisPaintOpSettings::setPaintOpFlow(qreal value) { KisLockedPropertiesProxySP proxy( KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(this)); proxy->setProperty("FlowValue", value); } void KisPaintOpSettings::setPaintOpCompositeOp(const QString &value) { KisLockedPropertiesProxySP proxy( KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(this)); proxy->setProperty("CompositeOp", value); } qreal KisPaintOpSettings::paintOpOpacity() { KisLockedPropertiesProxySP proxy = KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(this); return proxy->getDouble("OpacityValue", 1.0); } qreal KisPaintOpSettings::paintOpFlow() { KisLockedPropertiesProxySP proxy( KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(this)); return proxy->getDouble("FlowValue", 1.0); } QString KisPaintOpSettings::paintOpCompositeOp() { KisLockedPropertiesProxySP proxy( KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(this)); return proxy->getString("CompositeOp", COMPOSITE_OVER); } void KisPaintOpSettings::setEraserMode(bool value) { KisLockedPropertiesProxySP proxy( KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(this)); proxy->setProperty("EraserMode", value); } bool KisPaintOpSettings::eraserMode() { KisLockedPropertiesProxySP proxy( KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(this)); return proxy->getBool("EraserMode", false); } QString KisPaintOpSettings::effectivePaintOpCompositeOp() { return !eraserMode() ? paintOpCompositeOp() : COMPOSITE_ERASE; } qreal KisPaintOpSettings::savedEraserSize() const { return getDouble("SavedEraserSize", 0.0); } void KisPaintOpSettings::setSavedEraserSize(qreal value) { setProperty("SavedEraserSize", value); setPropertyNotSaved("SavedEraserSize"); } qreal KisPaintOpSettings::savedBrushSize() const { return getDouble("SavedBrushSize", 0.0); } void KisPaintOpSettings::setSavedBrushSize(qreal value) { setProperty("SavedBrushSize", value); setPropertyNotSaved("SavedBrushSize"); } qreal KisPaintOpSettings::savedEraserOpacity() const { return getDouble("SavedEraserOpacity", 0.0); } void KisPaintOpSettings::setSavedEraserOpacity(qreal value) { setProperty("SavedEraserOpacity", value); setPropertyNotSaved("SavedEraserOpacity"); } qreal KisPaintOpSettings::savedBrushOpacity() const { return getDouble("SavedBrushOpacity", 0.0); } void KisPaintOpSettings::setSavedBrushOpacity(qreal value) { setProperty("SavedBrushOpacity", value); setPropertyNotSaved("SavedBrushOpacity"); } QString KisPaintOpSettings::modelName() const { return d->modelName; } void KisPaintOpSettings::setModelName(const QString & modelName) { d->modelName = modelName; } KisPaintOpConfigWidget* KisPaintOpSettings::optionsWidget() const { if (d->settingsWidget.isNull()) return 0; return d->settingsWidget.data(); } bool KisPaintOpSettings::isValid() const { return true; } bool KisPaintOpSettings::isLoadable() { return isValid(); } QString KisPaintOpSettings::indirectPaintingCompositeOp() const { return COMPOSITE_ALPHA_DARKEN; } bool KisPaintOpSettings::isAirbrushing() const { return getBool(AIRBRUSH_ENABLED, false); } qreal KisPaintOpSettings::airbrushInterval() const { qreal rate = getDouble(AIRBRUSH_RATE, 1.0); if (rate == 0.0) { return LONG_TIME; } else { return 1000.0 / rate; } } bool KisPaintOpSettings::useSpacingUpdates() const { return getBool(SPACING_USE_UPDATES, false); } bool KisPaintOpSettings::needsAsynchronousUpdates() const { return false; } -QPainterPath KisPaintOpSettings::brushOutline(const KisPaintInformation &info, OutlineMode mode) +QPainterPath KisPaintOpSettings::brushOutline(const KisPaintInformation &info, const OutlineMode &mode) { QPainterPath path; - if (mode == CursorIsOutline || mode == CursorIsCircleOutline || mode == CursorTiltOutline) { + if (mode.isVisible) { path = ellipseOutline(10, 10, 1.0, 0); - if (mode == CursorTiltOutline) { + if (mode.showTiltDecoration) { path.addPath(makeTiltIndicator(info, QPointF(0.0, 0.0), 0.0, 2.0)); } path.translate(info.pos()); } return path; } QPainterPath KisPaintOpSettings::ellipseOutline(qreal width, qreal height, qreal scale, qreal rotation) { QPainterPath path; QRectF ellipse(0, 0, width * scale, height * scale); ellipse.translate(-ellipse.center()); path.addEllipse(ellipse); QTransform m; m.reset(); m.rotate(rotation); path = m.map(path); return path; } QPainterPath KisPaintOpSettings::makeTiltIndicator(KisPaintInformation const& info, QPointF const& start, qreal maxLength, qreal angle) { if (maxLength == 0.0) maxLength = 50.0; maxLength = qMax(maxLength, 50.0); qreal const length = maxLength * (1 - info.tiltElevation(info, 60.0, 60.0, true)); qreal const baseAngle = 360.0 - fmod(KisPaintInformation::tiltDirection(info, true) * 360.0 + 270.0, 360.0); QLineF guideLine = QLineF::fromPolar(length, baseAngle + angle); guideLine.translate(start); QPainterPath ret; ret.moveTo(guideLine.p1()); ret.lineTo(guideLine.p2()); guideLine.setAngle(baseAngle - angle); ret.lineTo(guideLine.p2()); ret.lineTo(guideLine.p1()); return ret; } void KisPaintOpSettings::setCanvasRotation(qreal angle) { Private::DirtyNotificationsLocker locker(d.data()); setProperty("runtimeCanvasRotation", angle); setPropertyNotSaved("runtimeCanvasRotation"); } void KisPaintOpSettings::setCanvasMirroring(bool xAxisMirrored, bool yAxisMirrored) { Private::DirtyNotificationsLocker locker(d.data()); setProperty("runtimeCanvasMirroredX", xAxisMirrored); setPropertyNotSaved("runtimeCanvasMirroredX"); setProperty("runtimeCanvasMirroredY", yAxisMirrored); setPropertyNotSaved("runtimeCanvasMirroredY"); } void KisPaintOpSettings::setProperty(const QString & name, const QVariant & value) { if (value != KisPropertiesConfiguration::getProperty(name) && !d->disableDirtyNotifications) { KisPaintOpPresetSP presetSP = preset().toStrongRef(); if (presetSP) { presetSP->setPresetDirty(true); } } KisPropertiesConfiguration::setProperty(name, value); onPropertyChanged(); } void KisPaintOpSettings::onPropertyChanged() { KisPaintopSettingsUpdateProxy *proxy = d->updateProxyNoCreate(); if (proxy) { proxy->notifySettingsChanged(); } } bool KisPaintOpSettings::isLodUserAllowed(const KisPropertiesConfigurationSP config) { return config->getBool("lodUserAllowed", true); } void KisPaintOpSettings::setLodUserAllowed(KisPropertiesConfigurationSP config, bool value) { config->setProperty("lodUserAllowed", value); } bool KisPaintOpSettings::lodSizeThresholdSupported() const { return true; } qreal KisPaintOpSettings::lodSizeThreshold() const { return getDouble("lodSizeThreshold", 100.0); } void KisPaintOpSettings::setLodSizeThreshold(qreal value) { setProperty("lodSizeThreshold", value); } #include "kis_standard_uniform_properties_factory.h" QList KisPaintOpSettings::uniformProperties(KisPaintOpSettingsSP settings) { QList props = listWeakToStrong(d->uniformProperties); if (props.isEmpty()) { using namespace KisStandardUniformPropertiesFactory; props.append(createProperty(opacity, settings, d->updateProxyCreate())); props.append(createProperty(size, settings, d->updateProxyCreate())); props.append(createProperty(flow, settings, d->updateProxyCreate())); d->uniformProperties = listStrongToWeak(props); } return props; } diff --git a/libs/image/brushengine/kis_paintop_settings.h b/libs/image/brushengine/kis_paintop_settings.h index 6efd7fe4f8..d820f5bcce 100644 --- a/libs/image/brushengine/kis_paintop_settings.h +++ b/libs/image/brushengine/kis_paintop_settings.h @@ -1,352 +1,351 @@ /* * Copyright (c) 2007 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_PAINTOP_SETTINGS_H_ #define KIS_PAINTOP_SETTINGS_H_ #include "kis_types.h" #include "kritaimage_export.h" #include #include #include "kis_properties_configuration.h" #include #include class KisPaintOpConfigWidget; class KisPaintopSettingsUpdateProxy; /** * Configuration property used to control whether airbrushing is enabled. */ const QString AIRBRUSH_ENABLED = "PaintOpSettings/isAirbrushing"; /** * Configuration property used to control airbrushing rate. The value should be in dabs per second. */ const QString AIRBRUSH_RATE = "PaintOpSettings/rate"; /** * Configuration property used to control whether airbrushing is configured to ignore distance-based * spacing. */ const QString AIRBRUSH_IGNORE_SPACING = "PaintOpSettings/ignoreSpacing"; /** * Configuration property used to control whether the spacing settings can be updated between * painted dabs. */ const QString SPACING_USE_UPDATES = "PaintOpSettings/updateSpacingBetweenDabs"; /** * This class is used to cache the settings for a paintop * between two creations. There is one KisPaintOpSettings per input device (mouse, tablet, * etc...). * * The settings may be stored in a preset or a recorded brush stroke. Note that if your * paintop's settings subclass has data that is not stored as a property, that data is not * saved and restored. * * The object also contains a pointer to its parent KisPaintOpPreset object.This is to control the DirtyPreset * property of KisPaintOpPreset. Whenever the settings are changed/modified from the original -- the preset is * set to dirty. */ class KRITAIMAGE_EXPORT KisPaintOpSettings : public KisPropertiesConfiguration { public: KisPaintOpSettings(); ~KisPaintOpSettings() override; KisPaintOpSettings(const KisPaintOpSettings &rhs); /** * */ void setOptionsWidget(KisPaintOpConfigWidget* widget); /** * This function is called by a tool when the mouse is pressed. It's useful if * the paintop needs mouse interaction for instance in the case of the clone op. * If the tool is supposed to ignore the event, the paint op should return false * and if the tool is supposed to use the event, return true. */ virtual bool mousePressEvent(const KisPaintInformation &paintInformation, Qt::KeyboardModifiers modifiers, KisNodeWSP currentNode); /** * Clone the current settings object. Override this if your settings instance doesn't * store everything as properties. */ virtual KisPaintOpSettingsSP clone() const; /** * Removes all the settings from the object while keeping the paintop id, * which is loaded to the object by the factory */ void resetSettings(const QStringList &preserveProperties = QStringList()); /** * @return the node the paintop is working on. */ KisNodeSP node() const; /** * Call this function when the paint op is selected or the tool is activated */ virtual void activate(); /** * XXX: Remove this after 2.0, when the paint operation (incremental/non incremental) will * be completely handled in the paintop, not in the tool. This is a filthy hack to move * the option to the right place, at least. * @return true if we paint incrementally, false if we paint like Photoshop. By default, paintops * do not support non-incremental. */ virtual bool paintIncremental() { return true; } /** * @return the composite op it to which the indirect painting device * should be initialized to. This is used by clone op to reset * the composite op to COMPOSITE_COPY */ virtual QString indirectPaintingCompositeOp() const; /** * Whether this paintop wants to deposit paint even when not moving, i.e. the tool needs to * activate its timer. If this is true, painting updates need to be generated at regular * intervals even in the absence of input device events, e.g. when the cursor is not moving. * * The default implementation checks the property AIRBRUSH_ENABLED, defaulting to false if the * property is not found. This should be suitable for most paintops. */ virtual bool isAirbrushing() const; /** * Indicates the minimum time interval that might be needed between airbrush dabs, in * milliseconds. A lower value means painting updates need to happen more frequently. This value * should be ignored if isAirbrushing() is false. * * The default implementation uses the property AIRBRUSH_RATE, defaulting to an interval of * one second if the property is not found. This should be suitable for most paintops. */ virtual qreal airbrushInterval() const; /** * Indicates whether this configuration allows spacing information to be updated between painted * dabs during a stroke. */ virtual bool useSpacingUpdates() const; /** * Indicates if the tool should call paintOp->doAsynchronousUpdate() inbetween * paintAt() calls to do the asynchronous rendering */ virtual bool needsAsynchronousUpdates() const; /** - * This enum defines the current mode for painting an outline. + * This structure defines the current mode for painting an outline. */ - enum OutlineMode { - CursorIsOutline = 1, ///< When this mode is set, an outline is painted around the cursor - CursorIsCircleOutline, - CursorNoOutline, - CursorTiltOutline, - CursorColorOutline + struct OutlineMode { + bool isVisible = false; + bool forceCircle = false; + bool showTiltDecoration = false; + bool forceFullSize = false; }; /** * Returns the brush outline in pixel coordinates. Tool is responsible for conversion into view coordinates. * Outline mode has to be passed to the paintop which builds the outline as some paintops have to paint outline * always like clone paintop indicating the duplicate position */ - virtual QPainterPath brushOutline(const KisPaintInformation &info, OutlineMode mode); + virtual QPainterPath brushOutline(const KisPaintInformation &info, const OutlineMode &mode); /** * Helpers for drawing the brush outline */ static QPainterPath ellipseOutline(qreal width, qreal height, qreal scale, qreal rotation); /** * Helper for drawing a triangle representing the tilt of the stylus. * * @param start is the offset from the brush's outline's bounding box * @param lengthScale is used for deciding the size of the triangle. * Brush diameter or width are common choices for this. * @param angle is the angle between the two sides of the triangle. */ static QPainterPath makeTiltIndicator(KisPaintInformation const& info, QPointF const& start, qreal lengthScale, qreal angle); /** * Set paintop opacity directly in the properties */ void setPaintOpOpacity(qreal value); /** * Set paintop flow directly in the properties */ void setPaintOpFlow(qreal value); /** * Set paintop composite mode directly in the properties */ void setPaintOpCompositeOp(const QString &value); /** * @return opacity saved in the properties */ qreal paintOpOpacity(); /** * @return flow saved in the properties */ qreal paintOpFlow(); /** * @return composite mode saved in the properties */ QString paintOpCompositeOp(); /** * Set paintop size directly in the properties */ virtual void setPaintOpSize(qreal value) = 0; /** * @return size saved in the properties */ virtual qreal paintOpSize() const = 0; void setEraserMode(bool value); bool eraserMode(); qreal savedEraserSize() const; void setSavedEraserSize(qreal value); qreal savedBrushSize() const; void setSavedBrushSize(qreal value); qreal savedEraserOpacity() const; void setSavedEraserOpacity(qreal value); qreal savedBrushOpacity() const; void setSavedBrushOpacity(qreal value); QString effectivePaintOpCompositeOp(); void setPreset(KisPaintOpPresetWSP preset); KisPaintOpPresetWSP preset() const; /** * @return filename of the 3D brush model, empty if no brush is set */ virtual QString modelName() const; /** * Set filename of 3D brush model. By default no brush is set */ void setModelName(const QString & modelName); /// Check if the settings are valid, setting might be invalid through missing brushes etc /// Overwrite if the settings of a paintop can be invalid /// @return state of the settings, default implementation is true virtual bool isValid() const; /// Check if the settings are loadable, that might the case if we can fallback to something /// Overwrite if the settings can do some kind of fallback /// @return loadable state of the settings, by default implementation return the same as isValid() virtual bool isLoadable(); /** * These methods are populating properties with runtime * information about canvas rotation/mirroring. This information * is set directly by KisToolFreehand. Later the data is accessed * by the pressure options to make a final decision. */ void setCanvasRotation(qreal angle); void setCanvasMirroring(bool xAxisMirrored, bool yAxisMirrored); /** * Overrides the method in KisPropertiesCofiguration to allow * onPropertyChanged() callback */ void setProperty(const QString & name, const QVariant & value) override; virtual QList uniformProperties(KisPaintOpSettingsSP settings); static bool isLodUserAllowed(const KisPropertiesConfigurationSP config); static void setLodUserAllowed(KisPropertiesConfigurationSP config, bool value); virtual bool lodSizeThresholdSupported() const; qreal lodSizeThreshold() const; void setLodSizeThreshold(qreal value); /** * @return the option widget of the paintop (can be 0 is no option widgets is set) */ KisPaintOpConfigWidget* optionsWidget() const; /** * This function is called to set random offsets to the brush whenever the mouse is clicked. It is * specific to when the pattern option is set. * */ virtual void setRandomOffset(const KisPaintInformation &paintInformation); /** * @return true if this preset demands a secondary masked brush running * alongside it */ bool hasMaskingSettings() const; /** * @return a newly created settings object representing a preset of the masking * brush that should be run alongside the current brush */ KisPaintOpSettingsSP createMaskingSettings() const; /** * @return a composite op id of the masked brush rendering algorithm. * * Please take into account that the brush itself always paints in alpha- * darken mode, but the final result is combined with this composite op. */ QString maskingBrushCompositeOp() const; protected: /** * The callback is called every time when a property changes */ virtual void onPropertyChanged(); private: struct Private; const QScopedPointer d; }; #endif diff --git a/libs/image/kis_antialiasing_fade_maker.h b/libs/image/kis_antialiasing_fade_maker.h index cc9b05d412..6ef1b25032 100644 --- a/libs/image/kis_antialiasing_fade_maker.h +++ b/libs/image/kis_antialiasing_fade_maker.h @@ -1,192 +1,212 @@ /* * 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_ANTIALIASING_FADE_MAKER_H #define __KIS_ANTIALIASING_FADE_MAKER_H #include "kis_global.h" template class KisAntialiasingFadeMaker1D { public: KisAntialiasingFadeMaker1D(const BaseFade &baseFade, bool enableAntialiasing) : m_radius(0.0), m_fadeStartValue(0), m_antialiasingFadeStart(0), m_antialiasingFadeCoeff(0), m_enableAntialiasing(enableAntialiasing), m_baseFade(baseFade) { } KisAntialiasingFadeMaker1D(const KisAntialiasingFadeMaker1D &rhs, const BaseFade &baseFade) : m_radius(rhs.m_radius), m_fadeStartValue(rhs.m_fadeStartValue), m_antialiasingFadeStart(rhs.m_antialiasingFadeStart), m_antialiasingFadeCoeff(rhs.m_antialiasingFadeCoeff), m_enableAntialiasing(rhs.m_enableAntialiasing), m_baseFade(baseFade) { } void setSquareNormCoeffs(qreal xcoeff, qreal ycoeff) { m_radius = 1.0; qreal xf = qMax(0.0, ((1.0 / xcoeff) - 1.0) * xcoeff); qreal yf = qMax(0.0, ((1.0 / ycoeff) - 1.0) * ycoeff); m_antialiasingFadeStart = pow2(0.5 * (xf + yf)); m_fadeStartValue = m_baseFade.value(m_antialiasingFadeStart); m_antialiasingFadeCoeff = qMax(0.0, 255.0 - m_fadeStartValue) / (m_radius - m_antialiasingFadeStart); } void setRadius(qreal radius) { m_radius = radius; m_antialiasingFadeStart = qMax(0.0, m_radius - 1.0); m_fadeStartValue = m_baseFade.value(m_antialiasingFadeStart); m_antialiasingFadeCoeff = qMax(0.0, 255.0 - m_fadeStartValue) / (m_radius - m_antialiasingFadeStart); } inline bool needFade(qreal dist, quint8 *value) { if (dist > m_radius) { *value = 255; return true; } if (!m_enableAntialiasing) { return false; } if (dist > m_antialiasingFadeStart) { *value = m_fadeStartValue + (dist - m_antialiasingFadeStart) * m_antialiasingFadeCoeff; return true; } return false; } + qreal getRadius(){ + return m_radius; + } + + qreal getAntialiasingFadeStart(){ + return m_antialiasingFadeStart; + } + + qreal getFadeStartValue() { + return m_fadeStartValue; + } + + qreal getAntialiasingFadeCoeff(){ + return m_antialiasingFadeCoeff; + } + + bool getAliasingEnabled(){ + return m_enableAntialiasing; + } + private: qreal m_radius; quint8 m_fadeStartValue; qreal m_antialiasingFadeStart; qreal m_antialiasingFadeCoeff; bool m_enableAntialiasing; const BaseFade &m_baseFade; }; template class KisAntialiasingFadeMaker2D { public: KisAntialiasingFadeMaker2D(const BaseFade &baseFade, bool enableAntialiasing) : m_xLimit(0), m_yLimit(0), m_xFadeLimitStart(0), m_yFadeLimitStart(0), m_xFadeCoeff(0), m_yFadeCoeff(0), m_enableAntialiasing(enableAntialiasing), m_baseFade(baseFade) { } KisAntialiasingFadeMaker2D(const KisAntialiasingFadeMaker2D &rhs, const BaseFade &baseFade) : m_xLimit(rhs.m_xLimit), m_yLimit(rhs.m_yLimit), m_xFadeLimitStart(rhs.m_xFadeLimitStart), m_yFadeLimitStart(rhs.m_yFadeLimitStart), m_xFadeCoeff(rhs.m_xFadeCoeff), m_yFadeCoeff(rhs.m_yFadeCoeff), m_enableAntialiasing(rhs.m_enableAntialiasing), m_baseFade(baseFade) { } void setLimits(qreal halfWidth, qreal halfHeight) { m_xLimit = halfWidth; m_yLimit = halfHeight; m_xFadeLimitStart = m_xLimit - 1.0; m_yFadeLimitStart = m_yLimit - 1.0; m_xFadeCoeff = 1.0 / (m_xLimit - m_xFadeLimitStart); m_yFadeCoeff = 1.0 / (m_yLimit - m_yFadeLimitStart); } inline bool needFade(qreal x, qreal y, quint8 *value) { x = qAbs(x); y = qAbs(y); if (x > m_xLimit) { *value = 255; return true; } if (y > m_yLimit) { *value = 255; return true; } if (!m_enableAntialiasing) { return false; } if (x > m_xFadeLimitStart) { quint8 baseValue = m_baseFade.value(x, y); *value = baseValue + (255.0 - baseValue) * (x - m_xFadeLimitStart) * m_xFadeCoeff; if (y > m_yFadeLimitStart && *value < 255) { *value += (255.0 - *value) * (y - m_yFadeLimitStart) * m_yFadeCoeff; } return true; } if (y > m_yFadeLimitStart) { quint8 baseValue = m_baseFade.value(x, y); *value = baseValue + (255.0 - baseValue) * (y - m_yFadeLimitStart) * m_yFadeCoeff; if (x > m_xFadeLimitStart && *value < 255) { *value += (255.0 - *value) * (x - m_xFadeLimitStart) * m_xFadeCoeff; } return true; } return false; } private: qreal m_xLimit; qreal m_yLimit; qreal m_xFadeLimitStart; qreal m_yFadeLimitStart; qreal m_xFadeCoeff; qreal m_yFadeCoeff; bool m_enableAntialiasing; const BaseFade &m_baseFade; }; #endif /* __KIS_ANTIALIASING_FADE_MAKER_H */ diff --git a/libs/image/kis_brush_mask_applicator_factories.cpp b/libs/image/kis_brush_mask_applicator_factories.cpp index 2c54a61ef8..45b933b5b2 100644 --- a/libs/image/kis_brush_mask_applicator_factories.cpp +++ b/libs/image/kis_brush_mask_applicator_factories.cpp @@ -1,137 +1,250 @@ /* * Copyright (c) 2012 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_brush_mask_applicator_factories.h" #include "kis_circle_mask_generator.h" #include "kis_circle_mask_generator_p.h" +#include "kis_gauss_circle_mask_generator_p.h" #include "kis_brush_mask_applicators.h" #include "kis_brush_mask_applicator_base.h" #define a(_s) #_s #define b(_s) a(_s) template<> template<> MaskApplicatorFactory::ReturnType MaskApplicatorFactory::create(ParamType maskGenerator) { return new KisBrushMaskScalarApplicator(maskGenerator); } template<> template<> MaskApplicatorFactory::ReturnType MaskApplicatorFactory::create(ParamType maskGenerator) { return new KisBrushMaskVectorApplicator(maskGenerator); } +template<> +template<> +MaskApplicatorFactory::ReturnType +MaskApplicatorFactory::create(ParamType maskGenerator) +{ + return new KisBrushMaskVectorApplicator(maskGenerator); +} + #if defined HAVE_VC struct KisCircleMaskGenerator::FastRowProcessor { FastRowProcessor(KisCircleMaskGenerator *maskGenerator) : d(maskGenerator->d.data()) {} template void process(float* buffer, int width, float y, float cosa, float sina, float centerX, float centerY); KisCircleMaskGenerator::Private *d; }; template<> void KisCircleMaskGenerator:: FastRowProcessor::process(float* buffer, int width, float y, float cosa, float sina, float centerX, float centerY) { const bool useSmoothing = d->copyOfAntialiasEdges; const bool noFading = d->noFading; float y_ = y - centerY; float sinay_ = sina * y_; float cosay_ = cosa * y_; float* bufferPointer = buffer; Vc::float_v currentIndices = Vc::float_v::IndexesFromZero(); Vc::float_v increment((float)Vc::float_v::size()); Vc::float_v vCenterX(centerX); Vc::float_v vCosa(cosa); Vc::float_v vSina(sina); Vc::float_v vCosaY_(cosay_); Vc::float_v vSinaY_(sinay_); Vc::float_v vXCoeff(d->xcoef); Vc::float_v vYCoeff(d->ycoef); Vc::float_v vTransformedFadeX(d->transformedFadeX); Vc::float_v vTransformedFadeY(d->transformedFadeY); Vc::float_v vOne(Vc::One); for (int i=0; i < width; i+= Vc::float_v::size()){ Vc::float_v x_ = currentIndices - vCenterX; Vc::float_v xr = x_ * vCosa - vSinaY_; Vc::float_v yr = x_ * vSina + vCosaY_; Vc::float_v n = pow2(xr * vXCoeff) + pow2(yr * vYCoeff); Vc::float_m outsideMask = n > vOne; if (!outsideMask.isFull()) { if (noFading) { Vc::float_v vFade(Vc::Zero); vFade(outsideMask) = vOne; vFade.store(bufferPointer, Vc::Aligned); } else { if (useSmoothing) { xr = Vc::abs(xr) + vOne; yr = Vc::abs(yr) + vOne; } Vc::float_v vNormFade = pow2(xr * vTransformedFadeX) + pow2(yr * vTransformedFadeY); //255 * n * (normeFade - 1) / (normeFade - n) Vc::float_v vFade = n * (vNormFade - vOne) / (vNormFade - n); // Mask in the inner circe of the mask Vc::float_m mask = vNormFade < vOne; vFade.setZero(mask); // Mask out the outer circe of the mask vFade(outsideMask) = vOne; vFade.store(bufferPointer, Vc::Aligned); } } else { // Mask out everything outside the circle vOne.store(bufferPointer, Vc::Aligned); } currentIndices = currentIndices + increment; bufferPointer += Vc::float_v::size(); } } + +struct KisGaussCircleMaskGenerator::FastRowProcessor +{ + FastRowProcessor(KisGaussCircleMaskGenerator *maskGenerator) + : d(maskGenerator->d.data()) {} + + template + void process(float* buffer, int width, float y, float cosa, float sina, + float centerX, float centerY); + + KisGaussCircleMaskGenerator::Private *d; +}; + +template<> void KisGaussCircleMaskGenerator:: +FastRowProcessor::process(float* buffer, int width, float y, float cosa, float sina, + float centerX, float centerY) +{ + const bool antialiasOn = d->fadeMaker.getAliasingEnabled(); + + float y_ = y - centerY; + float sinay_ = sina * y_; + float cosay_ = cosa * y_; + + float* bufferPointer = buffer; + + Vc::float_v currentIndices = Vc::float_v::IndexesFromZero(); + + Vc::float_v increment((float)Vc::float_v::size()); + Vc::float_v vCenterX(centerX); + Vc::float_v vCenter(d->center); + + Vc::float_v vCosa(cosa); + Vc::float_v vSina(sina); + Vc::float_v vCosaY_(cosay_); + Vc::float_v vSinaY_(sinay_); + + Vc::float_v vYCoeff(d->ycoef); + Vc::float_v vDistfactor(d->distfactor); + Vc::float_v vAlphafactor(d->alphafactor); + + Vc::float_v vFadeRadius(d->fadeMaker.getRadius()); + Vc::float_v vFadeStartValue(d->fadeMaker.getFadeStartValue()); + Vc::float_v vFadeAFadeStart(d->fadeMaker.getAntialiasingFadeStart()); + Vc::float_v vFadeAFadeCoeff(d->fadeMaker.getAntialiasingFadeCoeff()); + + Vc::float_v vOne(Vc::One); + Vc::float_v vZero(Vc::Zero); + Vc::float_v vValMax(255.f); + + for (int i=0; i < width; i+= Vc::float_v::size()){ + + Vc::float_v x_ = currentIndices - vCenterX; + + Vc::float_v xr = x_ * vCosa - vSinaY_; + Vc::float_v yr = x_ * vSina + vCosaY_; + + Vc::float_v dist = sqrt(pow2(xr) + pow2(yr * vYCoeff)); + + // BEGIN FadeMaker needFade vectorized + // follow fademaker rules for outsideMask + Vc::float_m outsideMask = dist > vFadeRadius; + dist(outsideMask) = vOne; + + Vc::float_m fadeStartMask(false); + // if antialias is off, do not process + if(antialiasOn){ + fadeStartMask = dist > vFadeAFadeStart; + dist((outsideMask ^ fadeStartMask) & fadeStartMask) = (vFadeStartValue + (dist - vFadeAFadeStart) * vFadeAFadeCoeff) / vValMax; + } + Vc::float_m excludeMask(outsideMask | fadeStartMask); + + if (!excludeMask.isFull()) { + Vc::float_v valDist = dist * vDistfactor; + Vc::float_v fullFade = vAlphafactor * ( d->vErf(valDist + vCenter) - d->vErf(valDist - vCenter)); + + Vc::float_m mask; + // Mask undefined values, out of range are out of mask + mask = Vc::isfinite(fullFade); + fullFade.setZero(!mask); + + // Mask in the inner circe of the mask + mask = fullFade < vZero; + fullFade.setZero(mask); + + // Mask the outter circle + mask = fullFade > 254.974f; + fullFade(mask) = vValMax; + + // Mask (value - value), presicion errors. + Vc::float_v vFade = (vValMax - fullFade) / vValMax; + + // return original dist values before vFade transform + vFade(excludeMask) = dist; + vFade.store(bufferPointer, Vc::Aligned); + + } else { + dist.store(bufferPointer, Vc::Aligned); + } + currentIndices = currentIndices + increment; + + bufferPointer += Vc::float_v::size(); + } +} + #endif /* defined HAVE_VC */ diff --git a/libs/image/kis_circle_mask_generator.cpp b/libs/image/kis_circle_mask_generator.cpp index a27db05731..6c64dcf3c1 100644 --- a/libs/image/kis_circle_mask_generator.cpp +++ b/libs/image/kis_circle_mask_generator.cpp @@ -1,141 +1,146 @@ /* * Copyright (c) 2004,2007-2009 Cyrille Berger * Copyright (c) 2010 Lukáš Tvrdý * Copyright (c) 2012 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 #include #ifdef HAVE_VC #if defined(__clang__) #pragma GCC diagnostic ignored "-Wundef" #pragma GCC diagnostic ignored "-Wlocal-type-template-args" #endif #if defined _MSC_VER // Lets shut up the "possible loss of data" and "forcing value to bool 'true' or 'false' #pragma warning ( push ) #pragma warning ( disable : 4244 ) #pragma warning ( disable : 4800 ) #endif #include #include #if defined _MSC_VER #pragma warning ( pop ) #endif #endif #include #include "kis_fast_math.h" #include "kis_circle_mask_generator.h" #include "kis_circle_mask_generator_p.h" #include "kis_base_mask_generator.h" #include "kis_brush_mask_applicator_factories.h" #include "kis_brush_mask_applicator_base.h" KisCircleMaskGenerator::KisCircleMaskGenerator(qreal diameter, qreal ratio, qreal fh, qreal fv, int spikes, bool antialiasEdges) : KisMaskGenerator(diameter, ratio, fh, fv, spikes, antialiasEdges, CIRCLE, DefaultId), d(new Private) { setScale(1.0, 1.0); // store the variable locally to allow vector implementation read it easily d->copyOfAntialiasEdges = antialiasEdges; d->applicator.reset(createOptimizedClass >(this)); } KisCircleMaskGenerator::KisCircleMaskGenerator(const KisCircleMaskGenerator &rhs) : KisMaskGenerator(rhs), d(new Private(*rhs.d)) { d->applicator.reset(createOptimizedClass >(this)); } KisMaskGenerator* KisCircleMaskGenerator::clone() const { return new KisCircleMaskGenerator(*this); } void KisCircleMaskGenerator::setScale(qreal scaleX, qreal scaleY) { KisMaskGenerator::setScale(scaleX, scaleY); d->xcoef = 2.0 / effectiveSrcWidth(); d->ycoef = 2.0 / effectiveSrcHeight(); d->xfadecoef = (horizontalFade() == 0) ? 1 : (2.0 / (horizontalFade() * effectiveSrcWidth())); d->yfadecoef = (verticalFade() == 0) ? 1 : (2.0 / (verticalFade() * effectiveSrcHeight())); d->transformedFadeX = KisMaskGenerator::softness() * d->xfadecoef; d->transformedFadeY = KisMaskGenerator::softness() * d->yfadecoef; d->noFading = !d->copyOfAntialiasEdges && qFuzzyCompare(d->xcoef, d->transformedFadeX) && qFuzzyCompare(d->ycoef, d->transformedFadeY); } KisCircleMaskGenerator::~KisCircleMaskGenerator() { } bool KisCircleMaskGenerator::shouldSupersample() const { return effectiveSrcWidth() < 10 || effectiveSrcHeight() < 10; } bool KisCircleMaskGenerator::shouldVectorize() const { return !shouldSupersample() && spikes() == 2; } KisBrushMaskApplicatorBase* KisCircleMaskGenerator::applicator() { return d->applicator.data(); } quint8 KisCircleMaskGenerator::valueAt(qreal x, qreal y) const { if (isEmpty()) return 255; qreal xr = (x /*- m_xcenter*/); qreal yr = qAbs(y /*- m_ycenter*/); fixRotation(xr, yr); qreal n = norme(xr * d->xcoef, yr * d->ycoef); if (n > 1.0) return 255; // we add +1.0 to ensure correct antialiasing on the border if (antialiasEdges()) { xr = qAbs(xr) + 1.0; yr = qAbs(yr) + 1.0; } qreal nf = norme(xr * d->transformedFadeX, yr * d->transformedFadeY); if (nf < 1.0) return 0; return 255 * n * (nf - 1.0) / (nf - n); } void KisCircleMaskGenerator::setSoftness(qreal softness) { KisMaskGenerator::setSoftness(softness); qreal safeSoftnessCoeff = qreal(1.0) / qMax(qreal(0.01), softness); d->transformedFadeX = d->xfadecoef * safeSoftnessCoeff; d->transformedFadeY = d->yfadecoef * safeSoftnessCoeff; } + +void KisCircleMaskGenerator::resetMaskApplicator(bool forceScalar) +{ + d->applicator.reset(createOptimizedClass >(this,forceScalar)); +} diff --git a/libs/image/kis_circle_mask_generator.h b/libs/image/kis_circle_mask_generator.h index c6aaa04143..0fdb389d9a 100644 --- a/libs/image/kis_circle_mask_generator.h +++ b/libs/image/kis_circle_mask_generator.h @@ -1,63 +1,65 @@ /* * Copyright (c) 2008-2009 Cyrille Berger * * 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_CIRCLE_MASK_GENERATOR_H_ #define _KIS_CIRCLE_MASK_GENERATOR_H_ #include "kritaimage_export.h" #include "kis_mask_generator.h" #include /** * Create, serialize and deserialize an elliptical 8-bit mask. */ class KRITAIMAGE_EXPORT KisCircleMaskGenerator : public KisMaskGenerator { public: struct FastRowProcessor; public: KisCircleMaskGenerator(qreal radius, qreal ratio, qreal fh, qreal fv, int spikes, bool antialiasEdges); KisCircleMaskGenerator(const KisCircleMaskGenerator &rhs); ~KisCircleMaskGenerator() override; KisMaskGenerator* clone() const override; quint8 valueAt(qreal x, qreal y) const override; bool shouldSupersample() const override; bool shouldVectorize() const override; KisBrushMaskApplicatorBase* applicator() override; void setSoftness(qreal softness) override; void setScale(qreal scaleX, qreal scaleY) override; + void resetMaskApplicator(bool forceScalar); + private: qreal norme(qreal a, qreal b) const { return a*a + b * b; } private: struct Private; const QScopedPointer d; }; #endif diff --git a/libs/image/kis_cubic_curve.cpp b/libs/image/kis_cubic_curve.cpp index 1ba7b9efbd..1f12831310 100644 --- a/libs/image/kis_cubic_curve.cpp +++ b/libs/image/kis_cubic_curve.cpp @@ -1,506 +1,519 @@ /* * Copyright (c) 2005 C. Boemann * Copyright (c) 2009 Dmitry Kazakov * Copyright (c) 2010 Cyrille Berger * * 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_cubic_curve.h" #include #include #include #include #include "kis_dom_utils.h" #include "kis_algebra_2d.h" template class KisTridiagonalSystem { /* * e.g. * |b0 c0 0 0 0| |x0| |f0| * |a0 b1 c1 0 0| |x1| |f1| * |0 a1 b2 c2 0|*|x2|=|f2| * |0 0 a2 b3 c3| |x3| |f3| * |0 0 0 a3 b4| |x4| |f4| */ public: /** * @return - vector that is storing x[] */ static QVector calculate(QList &a, QList &b, QList &c, QList &f) { QVector x; QVector alpha; QVector beta; int i; int size = b.size(); Q_ASSERT(a.size() == size - 1 && c.size() == size - 1 && f.size() == size); x.resize(size); /** * Check for special case when * order of the matrix is equal to 1 */ if (size == 1) { x[0] = f[0] / b[0]; return x; } /** * Common case */ alpha.resize(size); beta.resize(size); alpha[1] = -c[0] / b[0]; beta[1] = f[0] / b[0]; for (i = 1; i < size - 1; i++) { alpha[i+1] = -c[i] / (a[i-1] * alpha[i] + b[i]); beta[i+1] = (f[i] - a[i-1] * beta[i]) / (a[i-1] * alpha[i] + b[i]); } x.last() = (f.last() - a.last() * beta.last()) / (b.last() + a.last() * alpha.last()); for (i = size - 2; i >= 0; i--) x[i] = alpha[i+1] * x[i+1] + beta[i+1]; return x; } }; template class KisCubicSpline { /** * s[i](x)=a[i] + * b[i] * (x-x[i]) + * 1/2 * c[i] * (x-x[i])^2 + * 1/6 * d[i] * (x-x[i])^3 * * h[i]=x[i+1]-x[i] * */ protected: QList m_a; QVector m_b; QVector m_c; QVector m_d; QVector m_h; T m_begin; T m_end; int m_intervals; public: KisCubicSpline() {} KisCubicSpline(const QList &a) { createSpline(a); } /** * Create new spline and precalculate some values * for future * * @a - base points of the spline */ void createSpline(const QList &a) { int intervals = m_intervals = a.size() - 1; int i; m_begin = a.first().x(); m_end = a.last().x(); m_a.clear(); m_b.resize(intervals); m_c.clear(); m_d.resize(intervals); m_h.resize(intervals); for (i = 0; i < intervals; i++) { m_h[i] = a[i+1].x() - a[i].x(); m_a.append(a[i].y()); } m_a.append(a.last().y()); QList tri_b; QList tri_f; QList tri_a; /* equals to @tri_c */ for (i = 0; i < intervals - 1; i++) { tri_b.append(2.*(m_h[i] + m_h[i+1])); tri_f.append(6.*((m_a[i+2] - m_a[i+1]) / m_h[i+1] - (m_a[i+1] - m_a[i]) / m_h[i])); } for (i = 1; i < intervals - 1; i++) tri_a.append(m_h[i]); if (intervals > 1) { m_c = KisTridiagonalSystem::calculate(tri_a, tri_b, tri_a, tri_f); } m_c.prepend(0); m_c.append(0); for (i = 0; i < intervals; i++) m_d[i] = (m_c[i+1] - m_c[i]) / m_h[i]; for (i = 0; i < intervals; i++) m_b[i] = -0.5 * (m_c[i] * m_h[i]) - (1 / 6.0) * (m_d[i] * m_h[i] * m_h[i]) + (m_a[i+1] - m_a[i]) / m_h[i]; } /** * Get value of precalculated spline in the point @x */ T getValue(T x) const { T x0; int i = findRegion(x, x0); /* TODO: check for asm equivalent */ return m_a[i] + m_b[i] *(x - x0) + 0.5 * m_c[i] *(x - x0) *(x - x0) + (1 / 6.0)* m_d[i] *(x - x0) *(x - x0) *(x - x0); } T begin() const { return m_begin; } T end() const { return m_end; } protected: /** * findRegion - Searches for the region containing @x * @x0 - out parameter, containing beginning of the region * @return - index of the region */ int findRegion(T x, T &x0) const { int i; x0 = m_begin; for (i = 0; i < m_intervals; i++) { if (x >= x0 && x < x0 + m_h[i]) return i; x0 += m_h[i]; } if (x >= x0) { x0 -= m_h[m_intervals-1]; return m_intervals - 1; } qDebug("X value: %f\n", x); qDebug("m_begin: %f\n", m_begin); qDebug("m_end : %f\n", m_end); Q_ASSERT_X(0, "findRegion", "X value is outside regions"); /* **never reached** */ return -1; } }; static bool pointLessThan(const QPointF &a, const QPointF &b) { return a.x() < b.x(); } struct Q_DECL_HIDDEN KisCubicCurve::Data : public QSharedData { Data() { init(); } Data(const Data& data) : QSharedData() { init(); points = data.points; name = data.name; } void init() { validSpline = false; validU16Transfer = false; validFTransfer = false; } ~Data() { } mutable QString name; mutable KisCubicSpline spline; QList points; mutable bool validSpline; mutable QVector u8Transfer; mutable bool validU8Transfer; mutable QVector u16Transfer; mutable bool validU16Transfer; mutable QVector fTransfer; mutable bool validFTransfer; void updateSpline(); void keepSorted(); qreal value(qreal x); void invalidate(); template void updateTransfer(QVector<_T_>* transfer, bool& valid, _T2_ min, _T2_ max, int size); }; void KisCubicCurve::Data::updateSpline() { if (validSpline) return; validSpline = true; spline.createSpline(points); } void KisCubicCurve::Data::invalidate() { validSpline = false; validFTransfer = false; validU16Transfer = false; } void KisCubicCurve::Data::keepSorted() { std::sort(points.begin(), points.end(), pointLessThan); } qreal KisCubicCurve::Data::value(qreal x) { updateSpline(); /* Automatically extend non-existing parts of the curve * (e.g. before the first point) and cut off big y-values */ x = qBound(spline.begin(), x, spline.end()); qreal y = spline.getValue(x); return qBound(qreal(0.0), y, qreal(1.0)); } template void KisCubicCurve::Data::updateTransfer(QVector<_T_>* transfer, bool& valid, _T2_ min, _T2_ max, int size) { if (!valid || transfer->size() != size) { if (transfer->size() != size) { transfer->resize(size); } qreal end = 1.0 / (size - 1); for (int i = 0; i < size; ++i) { /* Direct uncached version */ _T2_ val = value(i * end ) * max; val = qBound(min, val, max); (*transfer)[i] = val; } valid = true; } } struct Q_DECL_HIDDEN KisCubicCurve::Private { QSharedDataPointer data; }; KisCubicCurve::KisCubicCurve() : d(new Private) { d->data = new Data; QPointF p; p.rx() = 0.0; p.ry() = 0.0; d->data->points.append(p); p.rx() = 1.0; p.ry() = 1.0; d->data->points.append(p); } KisCubicCurve::KisCubicCurve(const QList& points) : d(new Private) { d->data = new Data; d->data->points = points; d->data->keepSorted(); } KisCubicCurve::KisCubicCurve(const KisCubicCurve& curve) : d(new Private(*curve.d)) { } KisCubicCurve::~KisCubicCurve() { delete d; } KisCubicCurve& KisCubicCurve::operator=(const KisCubicCurve & curve) { if (&curve != this) { *d = *curve.d; } return *this; } bool KisCubicCurve::operator==(const KisCubicCurve& curve) const { if (d->data == curve.d->data) return true; return d->data->points == curve.d->data->points; } qreal KisCubicCurve::value(qreal x) const { qreal value = d->data->value(x); return value; } QList KisCubicCurve::points() const { return d->data->points; } void KisCubicCurve::setPoints(const QList& points) { d->data.detach(); d->data->points = points; d->data->invalidate(); } void KisCubicCurve::setPoint(int idx, const QPointF& point) { d->data.detach(); d->data->points[idx] = point; d->data->keepSorted(); d->data->invalidate(); } int KisCubicCurve::addPoint(const QPointF& point) { d->data.detach(); d->data->points.append(point); d->data->keepSorted(); d->data->invalidate(); return d->data->points.indexOf(point); } void KisCubicCurve::removePoint(int idx) { d->data.detach(); d->data->points.removeAt(idx); d->data->invalidate(); } -bool KisCubicCurve::isNull() const +bool KisCubicCurve::isIdentity() const { const QList &points = d->data->points; Q_FOREACH (const QPointF &pt, points) { if (!qFuzzyCompare(pt.x(), pt.y())) { return false; } } return true; } +bool KisCubicCurve::isConstant(qreal c) const +{ + const QList &points = d->data->points; + + Q_FOREACH (const QPointF &pt, points) { + if (!qFuzzyCompare(c, pt.y())) { + return false; + } + } + + return true; +} + const QString& KisCubicCurve::name() const { return d->data->name; } qreal KisCubicCurve::interpolateLinear(qreal normalizedValue, const QVector &transfer) { const qreal maxValue = transfer.size() - 1; const qreal bilinearX = qBound(0.0, maxValue * normalizedValue, maxValue); const qreal xFloored = std::floor(bilinearX); const qreal xCeiled = std::ceil(bilinearX); const qreal t = bilinearX - xFloored; constexpr qreal eps = 1e-6; qreal newValue = normalizedValue; if (t < eps) { newValue = transfer[int(xFloored)]; } else if (t > (1.0 - eps)) { newValue = transfer[int(xCeiled)]; } else { qreal a = transfer[int(xFloored)]; qreal b = transfer[int(xCeiled)]; newValue = a + t * (b - a); } return KisAlgebra2D::copysign(newValue, normalizedValue); } void KisCubicCurve::setName(const QString& name) { d->data->name = name; } QString KisCubicCurve::toString() const { QString sCurve; if(d->data->points.count() < 1) return sCurve; Q_FOREACH (const QPointF & pair, d->data->points) { sCurve += QString::number(pair.x()); sCurve += ','; sCurve += QString::number(pair.y()); sCurve += ';'; } return sCurve; } void KisCubicCurve::fromString(const QString& string) { QStringList data = string.split(';'); QList points; Q_FOREACH (const QString & pair, data) { if (pair.indexOf(',') > -1) { QPointF p; p.rx() = KisDomUtils::toDouble(pair.section(',', 0, 0)); p.ry() = KisDomUtils::toDouble(pair.section(',', 1, 1)); points.append(p); } } setPoints(points); } const QVector KisCubicCurve::uint16Transfer(int size) const { d->data->updateTransfer(&d->data->u16Transfer, d->data->validU16Transfer, 0x0, 0xFFFF, size); return d->data->u16Transfer; } const QVector KisCubicCurve::floatTransfer(int size) const { d->data->updateTransfer(&d->data->fTransfer, d->data->validFTransfer, 0.0, 1.0, size); return d->data->fTransfer; } diff --git a/libs/image/kis_cubic_curve.h b/libs/image/kis_cubic_curve.h index 5e763d0e94..9184fde4ee 100644 --- a/libs/image/kis_cubic_curve.h +++ b/libs/image/kis_cubic_curve.h @@ -1,83 +1,91 @@ /* * Copyright (c) 2010 Cyrille Berger * * 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_CUBIC_CURVE_H_ #define _KIS_CUBIC_CURVE_H_ #include #include #include #include class QPointF; const QString DEFAULT_CURVE_STRING = "0,0;1,1;"; /** * Hold the data for a cubic curve. */ class KRITAIMAGE_EXPORT KisCubicCurve { public: KisCubicCurve(); KisCubicCurve(const QList& points); KisCubicCurve(const QVector& points); KisCubicCurve(const KisCubicCurve& curve); ~KisCubicCurve(); KisCubicCurve& operator=(const KisCubicCurve& curve); bool operator==(const KisCubicCurve& curve) const; public: qreal value(qreal x) const; QList points() const; void setPoints(const QList& points); void setPoint(int idx, const QPointF& point); /** * Add a point to the curve, the list of point is always sorted. * @return the index of the inserted point */ int addPoint(const QPointF& point); void removePoint(int idx); - bool isNull() const; + /* + * Check whether the curve maps all values to themselves. + */ + bool isIdentity() const; + + /* + * Check whether the curve maps all values to given constant. + */ + bool isConstant(qreal c) const; /** * This allows us to carry around a display name for the curve internally. It is used * currently in Sketch for perchannel, but would potentially be useful anywhere * curves are used in the UI */ void setName(const QString& name); const QString& name() const; static qreal interpolateLinear(qreal normalizedValue, const QVector &transfer); public: const QVector uint16Transfer(int size = 256) const; const QVector floatTransfer(int size = 256) const; public: QString toString() const; void fromString(const QString&); private: struct Data; struct Private; Private* const d; }; Q_DECLARE_METATYPE(KisCubicCurve) #endif diff --git a/libs/image/kis_edge_detection_kernel.h b/libs/image/kis_edge_detection_kernel.h index 334e4afc7b..b6630da26f 100644 --- a/libs/image/kis_edge_detection_kernel.h +++ b/libs/image/kis_edge_detection_kernel.h @@ -1,129 +1,129 @@ /* * Copyright (c) 2017 Wolthera van Hövell tot Westerflier * * 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_EDGE_DETECTION_KERNEL_H #define KIS_EDGE_DETECTION_KERNEL_H #include "kritaimage_export.h" #include "kis_types.h" #include class QRect; class KRITAIMAGE_EXPORT KisEdgeDetectionKernel { public: KisEdgeDetectionKernel(); enum FilterType { Simple, //A weird simple method used in our old sobel filter Prewit, //The simpler prewitt detection, which doesn't smooth. - SobolVector //Sobol does smooth. The creation of bigger kernels is based on an approach regarding vectors. + SobelVector //Sobel does smooth. The creation of bigger kernels is based on an approach regarding vectors. }; enum FilterOutput { pythagorean, xGrowth, xFall, yGrowth, yFall, radian }; /** * @brief createHorizontalMatrix * @param radius the radius. 1 makes a 3x3 kernel. * @param type One of the entries in the enum Filtertype * @param reverse which direction the gradient goes. * The horizontal gradient by default detects the rightmost edges. * Reversed it selects the leftmost edges. * @return */ static Eigen::Matrix createHorizontalMatrix(qreal radius, FilterType type, bool reverse = false); /** * @brief createVerticalMatrix * @param radius the radius. 1 makes a 3x3 kernel. * @param type One of the entries in the enum Filtertype * @param reverse which direction the gradient goes. * The vertical gradient by default detects the topmost edges. * Reversed it selects the bottommost edges. * @return */ static Eigen::Matrix createVerticalMatrix(qreal radius, FilterType type, bool reverse = false); static KisConvolutionKernelSP createHorizontalKernel(qreal radius, FilterType type, bool denormalize = true, bool reverse = false); static KisConvolutionKernelSP createVerticalKernel(qreal radius, FilterType type, bool denormalize = true, bool reverse = false); static int kernelSizeFromRadius(qreal radius); static qreal sigmaFromRadius(qreal radius); /** * @brief applyEdgeDetection * This applies the edge detection filter to the device. * @param device the device to apply to. * @param rect the affected rect. * @param xRadius the radius of the horizontal sampling, radius of 0 is effectively disabling it. * @param yRadius the radius of the vertical sampling, refius of 0 is effectively disabling it. - * @param type the type can be prewitt, sobol or simple, each of which + * @param type the type can be prewitt, sobel or simple, each of which * have a different sampling for the eventual edge detection. * @param channelFlags the affected channels. * @param progressUpdater the progress updater if it exists. * @param writeToAlpha whether or not to have the result applied to the transparency than the color channels, * this is useful for fringe effects. */ static void applyEdgeDetection(KisPaintDeviceSP device, const QRect& rect, qreal xRadius, qreal yRadius, FilterType type, const QBitArray &channelFlags, KoUpdater *progressUpdater, FilterOutput output = pythagorean, bool writeToAlpha = false); /** * @brief converToNormalMap * Conver a channel of the device to a normal map. The channel will be interpretted as a heightmap. * @param device the device * @param rect the rectangle to apply this to. * @param xRadius the xradius * @param yRadius the yradius * @param type the edge detection filter. * @param channelToConvert the channel to use as a grayscale. * @param channelOrder the order in which the xyz coordinates ought to be written to the pixels. * @param channelFlags * @param progressUpdater */ static void convertToNormalMap(KisPaintDeviceSP device, const QRect & rect, qreal xRadius, qreal yRadius, FilterType type, int channelToConvert, QVector channelOrder, QVector channelFlip, const QBitArray &channelFlags, KoUpdater *progressUpdater); }; #endif // KIS_EDGE_DETECTION_KERNEL_H diff --git a/libs/image/kis_gauss_circle_mask_generator.cpp b/libs/image/kis_gauss_circle_mask_generator.cpp index 50977c95f6..91e08a7cd7 100644 --- a/libs/image/kis_gauss_circle_mask_generator.cpp +++ b/libs/image/kis_gauss_circle_mask_generator.cpp @@ -1,127 +1,150 @@ /* * Copyright (c) 2010 Lukáš Tvrdý * Copyright (c) 2011 Geoffry Song * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include //MSVC requires that Vc come first #include +#include +#ifdef HAVE_VC +#if defined(__clang__) +#pragma GCC diagnostic ignored "-Wundef" +#pragma GCC diagnostic ignored "-Wlocal-type-template-args" +#endif +#if defined _MSC_VER +// Lets shut up the "possible loss of data" and "forcing value to bool 'true' or 'false' +#pragma warning ( push ) +#pragma warning ( disable : 4244 ) +#pragma warning ( disable : 4800 ) +#endif +#include +#include +#if defined _MSC_VER +#pragma warning ( pop ) +#endif +#endif + #include #include #include #include #include "kis_fast_math.h" #include "kis_base_mask_generator.h" -#include "kis_gauss_circle_mask_generator.h" #include "kis_antialiasing_fade_maker.h" +#include "kis_brush_mask_applicator_factories.h" +#include "kis_brush_mask_applicator_base.h" +#include "kis_gauss_circle_mask_generator.h" +#include "kis_gauss_circle_mask_generator_p.h" #define M_SQRT_2 1.41421356237309504880 #ifdef Q_OS_WIN // on windows we get our erf() from boost #include #define erf(x) boost::math::erf(x) #endif -struct Q_DECL_HIDDEN KisGaussCircleMaskGenerator::Private -{ - Private(bool enableAntialiasing) - : fadeMaker(*this, enableAntialiasing) - { - } - - Private(const Private &rhs) - : ycoef(rhs.ycoef), - fade(rhs.fade), - center(rhs.center), - distfactor(rhs.distfactor), - alphafactor(rhs.alphafactor), - fadeMaker(rhs.fadeMaker, *this) - { - } - - qreal ycoef; - qreal fade; - qreal center, distfactor, alphafactor; - KisAntialiasingFadeMaker1D fadeMaker; - - inline quint8 value(qreal dist) const; -}; - KisGaussCircleMaskGenerator::KisGaussCircleMaskGenerator(qreal diameter, qreal ratio, qreal fh, qreal fv, int spikes, bool antialiasEdges) : KisMaskGenerator(diameter, ratio, fh, fv, spikes, antialiasEdges, CIRCLE, GaussId), d(new Private(antialiasEdges)) { d->ycoef = 1.0 / ratio; d->fade = 1.0 - (fh + fv) / 2.0; + if (d->fade == 0.0) d->fade = 1e-6; else if (d->fade == 1.0) d->fade = 1.0 - 1e-6; // would become undefined for fade == 0 or 1 + d->center = (2.5 * (6761.0*d->fade-10000.0))/(M_SQRT_2*6761.0*d->fade); d->alphafactor = 255.0 / (2.0 * erf(d->center)); + + d->applicator.reset(createOptimizedClass >(this)); + } KisGaussCircleMaskGenerator::KisGaussCircleMaskGenerator(const KisGaussCircleMaskGenerator &rhs) : KisMaskGenerator(rhs), d(new Private(*rhs.d)) { + d->applicator.reset(createOptimizedClass >(this)); } KisMaskGenerator* KisGaussCircleMaskGenerator::clone() const { return new KisGaussCircleMaskGenerator(*this); } void KisGaussCircleMaskGenerator::setScale(qreal scaleX, qreal scaleY) { KisMaskGenerator::setScale(scaleX, scaleY); d->ycoef = scaleX / (scaleY * ratio()); d->distfactor = M_SQRT_2 * 12500.0 / (6761.0 * d->fade * effectiveSrcWidth() / 2.0); d->fadeMaker.setRadius(0.5 * effectiveSrcWidth()); } KisGaussCircleMaskGenerator::~KisGaussCircleMaskGenerator() { } inline quint8 KisGaussCircleMaskGenerator::Private::value(qreal dist) const { dist *= distfactor; quint8 ret = alphafactor * (erf(dist + center) - erf(dist - center)); return (quint8) 255 - ret; } +bool KisGaussCircleMaskGenerator::shouldSupersample() const +{ + return effectiveSrcWidth() < 10 || effectiveSrcHeight() < 10; +} + +bool KisGaussCircleMaskGenerator::shouldVectorize() const +{ + return !shouldSupersample() && spikes() == 2; +} + +KisBrushMaskApplicatorBase* KisGaussCircleMaskGenerator::applicator() +{ + return d->applicator.data(); +} + quint8 KisGaussCircleMaskGenerator::valueAt(qreal x, qreal y) const { if (isEmpty()) return 255; qreal xr = x; qreal yr = qAbs(y); fixRotation(xr, yr); qreal dist = sqrt(norme(xr, yr * d->ycoef)); quint8 value; if (d->fadeMaker.needFade(dist, &value)) { return value; } return d->value(dist); } + +void KisGaussCircleMaskGenerator::resetMaskApplicator(bool forceScalar) +{ + d->applicator.reset(createOptimizedClass >(this,forceScalar)); +} diff --git a/libs/image/kis_gauss_circle_mask_generator.h b/libs/image/kis_gauss_circle_mask_generator.h index ae6158c9ee..1872641f48 100644 --- a/libs/image/kis_gauss_circle_mask_generator.h +++ b/libs/image/kis_gauss_circle_mask_generator.h @@ -1,55 +1,64 @@ /* * Copyright (c) 2010 Lukáš Tvrdý * Copyright (c) 2011 Geoffry Song * * 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_GAUSS_MASK_GENERATOR_H_ #define _KIS_GAUSS_MASK_GENERATOR_H_ -#include #include "kritaimage_export.h" +#include "kis_mask_generator.h" +#include /** * This mask generator uses a Gaussian-blurred circle */ class KRITAIMAGE_EXPORT KisGaussCircleMaskGenerator : public KisMaskGenerator { - +public: + struct FastRowProcessor; public: KisGaussCircleMaskGenerator(qreal diameter, qreal ratio, qreal fh, qreal fv, int spikes, bool antialiasEdges); KisGaussCircleMaskGenerator(const KisGaussCircleMaskGenerator &rhs); ~KisGaussCircleMaskGenerator() override; KisMaskGenerator* clone() const override; quint8 valueAt(qreal x, qreal y) const override; void setScale(qreal scaleX, qreal scaleY) override; + bool shouldSupersample() const override; + + bool shouldVectorize() const override; + KisBrushMaskApplicatorBase* applicator() override; + + void resetMaskApplicator(bool forceScalar); + private: qreal norme(qreal a, qreal b) const { return a*a + b*b; } private: struct Private; const QScopedPointer d; }; #endif diff --git a/libs/image/kis_gauss_circle_mask_generator_p.h b/libs/image/kis_gauss_circle_mask_generator_p.h new file mode 100644 index 0000000000..d922ef98f2 --- /dev/null +++ b/libs/image/kis_gauss_circle_mask_generator_p.h @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2008-2009 Cyrille Berger + * + * 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_GAUSS_MASK_GENERATOR_P_H_ +#define _KIS_GAUSS_MASK_GENERATOR_P_H_ + +#include "kis_antialiasing_fade_maker.h" + +struct Q_DECL_HIDDEN KisGaussCircleMaskGenerator::Private +{ + Private(bool enableAntialiasing) + : fadeMaker(*this, enableAntialiasing) + { + } + + Private(const Private &rhs) + : ycoef(rhs.ycoef), + fade(rhs.fade), + center(rhs.center), + distfactor(rhs.distfactor), + alphafactor(rhs.alphafactor), + fadeMaker(rhs.fadeMaker, *this) + { + } + + qreal ycoef; + qreal fade; + qreal center; + qreal distfactor; + qreal alphafactor; + KisAntialiasingFadeMaker1D fadeMaker; + + QScopedPointer applicator; + + inline quint8 value(qreal dist) const; + + #if defined HAVE_VC + // vectorized erf function, precision 1e-5 + Vc::float_v vErf(Vc::float_v x) { + Vc::float_v xa = abs(x); + Vc::float_m precisionLimit(xa >= 9.3f); // wrong result for any number beyond this + xa(precisionLimit) = 0; + Vc::float_v sign(Vc::One); + Vc::float_m invertMask = x < 0.f; + sign(invertMask) = -1.f; + + // CONSTANTS + float a1 = 0.254829592; + float a2 = -0.284496736; + float a3 = 1.421413741; + float a4 = -1.453152027; + float a5 = 1.061405429; + float p = 0.3275911; + + Vc::float_v t = 1.0f / (1.0f + p * xa); + Vc::float_v y = 1.0f - (((((a5 * t + a4) * t) + a3) * t + a2) * t + a1) * t * exp(-xa * xa); + y(precisionLimit) = 1.0f; + return sign * y; + } + #endif /* defined HAVE_VC */ +}; + +#endif /* _KIS_GAUSS_MASK_GENERATOR_P_H_ */ diff --git a/libs/image/kis_image.cc b/libs/image/kis_image.cc index 108ccfec4b..51be3f46df 100644 --- a/libs/image/kis_image.cc +++ b/libs/image/kis_image.cc @@ -1,1773 +1,1778 @@ /* * Copyright (c) 2002 Patrick Julien * 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_image.h" #include // WORDS_BIGENDIAN #include #include #include #include #include #include #include #include #include #include #include "KoColorSpaceRegistry.h" #include "KoColor.h" #include "KoColorProfile.h" #include #include "KisProofingConfiguration.h" #include "recorder/kis_action_recorder.h" #include "kis_adjustment_layer.h" #include "kis_annotation.h" #include "kis_change_profile_visitor.h" #include "kis_colorspace_convert_visitor.h" #include "kis_count_visitor.h" #include "kis_filter_strategy.h" #include "kis_group_layer.h" #include "commands/kis_image_commands.h" #include "kis_layer.h" #include "kis_meta_data_merge_strategy_registry.h" #include "kis_name_server.h" #include "kis_paint_layer.h" +#include "kis_projection_leaf.h" #include "kis_painter.h" #include "kis_selection.h" #include "kis_transaction.h" #include "kis_meta_data_merge_strategy.h" #include "kis_memory_statistics_server.h" #include "kis_image_config.h" #include "kis_update_scheduler.h" #include "kis_image_signal_router.h" #include "kis_image_animation_interface.h" #include "kis_stroke_strategy.h" #include "kis_simple_stroke_strategy.h" #include "kis_image_barrier_locker.h" #include "kis_undo_stores.h" #include "kis_legacy_undo_adapter.h" #include "kis_post_execution_undo_adapter.h" #include "kis_transform_worker.h" #include "kis_processing_applicator.h" #include "processing/kis_crop_processing_visitor.h" #include "processing/kis_crop_selections_processing_visitor.h" #include "processing/kis_transform_processing_visitor.h" #include "commands_new/kis_image_resize_command.h" #include "commands_new/kis_image_set_resolution_command.h" #include "commands_new/kis_activate_selection_mask_command.h" #include "kis_composite_progress_proxy.h" #include "kis_layer_composition.h" #include "kis_wrapped_rect.h" #include "kis_crop_saved_extra_data.h" #include "kis_layer_utils.h" #include "kis_lod_transform.h" #include "kis_suspend_projection_updates_stroke_strategy.h" #include "kis_sync_lod_cache_stroke_strategy.h" #include "kis_projection_updates_filter.h" #include "kis_layer_projection_plane.h" #include "kis_update_time_monitor.h" #include "kis_image_barrier_locker.h" #include #include #include "kis_time_range.h" // #define SANITY_CHECKS #ifdef SANITY_CHECKS #define SANITY_CHECK_LOCKED(name) \ if (!locked()) warnKrita() << "Locking policy failed:" << name \ << "has been called without the image" \ "being locked"; #else #define SANITY_CHECK_LOCKED(name) #endif struct KisImageSPStaticRegistrar { KisImageSPStaticRegistrar() { qRegisterMetaType("KisImageSP"); } }; static KisImageSPStaticRegistrar __registrar; class KisImage::KisImagePrivate { public: KisImagePrivate(KisImage *_q, qint32 w, qint32 h, const KoColorSpace *c, KisUndoStore *undo, KisImageAnimationInterface *_animationInterface) : q(_q) , lockedForReadOnly(false) , width(w) , height(h) , colorSpace(c ? c : KoColorSpaceRegistry::instance()->rgb8()) , nserver(1) , undoStore(undo ? undo : new KisDumbUndoStore()) , legacyUndoAdapter(undoStore.data(), _q) , postExecutionUndoAdapter(undoStore.data(), _q) , recorder(_q) , signalRouter(_q) , animationInterface(_animationInterface) , scheduler(_q, _q) , axesCenter(QPointF(0.5, 0.5)) { { KisImageConfig cfg; if (cfg.enableProgressReporting()) { scheduler.setProgressProxy(&compositeProgressProxy); } // Each of these lambdas defines a new factory function. scheduler.setLod0ToNStrokeStrategyFactory( [=](bool forgettable) { return KisLodSyncPair( new KisSyncLodCacheStrokeStrategy(KisImageWSP(q), forgettable), KisSyncLodCacheStrokeStrategy::createJobsData(KisImageWSP(q))); }); scheduler.setSuspendUpdatesStrokeStrategyFactory( [=]() { return KisSuspendResumePair( new KisSuspendProjectionUpdatesStrokeStrategy(KisImageWSP(q), true), KisSuspendProjectionUpdatesStrokeStrategy::createSuspendJobsData(KisImageWSP(q))); }); scheduler.setResumeUpdatesStrokeStrategyFactory( [=]() { return KisSuspendResumePair( new KisSuspendProjectionUpdatesStrokeStrategy(KisImageWSP(q), false), KisSuspendProjectionUpdatesStrokeStrategy::createResumeJobsData(KisImageWSP(q))); }); } connect(q, SIGNAL(sigImageModified()), KisMemoryStatisticsServer::instance(), SLOT(notifyImageChanged())); } ~KisImagePrivate() { /** * Stop animation interface. It may use the rootLayer. */ delete animationInterface; /** * First delete the nodes, while strokes * and undo are still alive */ rootLayer.clear(); } KisImage *q; quint32 lockCount = 0; bool lockedForReadOnly; qint32 width; qint32 height; double xres = 1.0; double yres = 1.0; const KoColorSpace * colorSpace; KisProofingConfigurationSP proofingConfig; KisSelectionSP deselectedGlobalSelection; KisGroupLayerSP rootLayer; // The layers are contained in here QList compositions; KisNodeSP isolatedRootNode; bool wrapAroundModePermitted = false; KisNameServer nserver; QScopedPointer undoStore; KisLegacyUndoAdapter legacyUndoAdapter; KisPostExecutionUndoAdapter postExecutionUndoAdapter; KisActionRecorder recorder; vKisAnnotationSP annotations; QAtomicInt disableUIUpdateSignals; KisProjectionUpdatesFilterSP projectionUpdatesFilter; KisImageSignalRouter signalRouter; KisImageAnimationInterface *animationInterface; KisUpdateScheduler scheduler; QAtomicInt disableDirtyRequests; KisCompositeProgressProxy compositeProgressProxy; bool blockLevelOfDetail = false; QPointF axesCenter; bool tryCancelCurrentStrokeAsync(); void notifyProjectionUpdatedInPatches(const QRect &rc); }; KisImage::KisImage(KisUndoStore *undoStore, qint32 width, qint32 height, const KoColorSpace * colorSpace, const QString& name) : QObject(0) , KisShared() , m_d(new KisImagePrivate(this, width, height, colorSpace, undoStore, new KisImageAnimationInterface(this))) { // make sure KisImage belongs to the GUI thread moveToThread(qApp->thread()); connect(this, SIGNAL(sigInternalStopIsolatedModeRequested()), SLOT(stopIsolatedMode())); setObjectName(name); setRootLayer(new KisGroupLayer(this, "root", OPACITY_OPAQUE_U8)); } KisImage::~KisImage() { dbgImage << "deleting kisimage" << objectName(); /** * Request the tools to end currently running strokes */ waitForDone(); delete m_d; disconnect(); // in case Qt gets confused } KisImage *KisImage::clone(bool exactCopy) { return new KisImage(*this, 0, exactCopy); } KisImage::KisImage(const KisImage& rhs, KisUndoStore *undoStore, bool exactCopy) : KisNodeFacade(), KisNodeGraphListener(), KisShared(), m_d(new KisImagePrivate(this, rhs.width(), rhs.height(), rhs.colorSpace(), undoStore ? undoStore : new KisDumbUndoStore(), new KisImageAnimationInterface(*rhs.animationInterface(), this))) { // make sure KisImage belongs to the GUI thread moveToThread(qApp->thread()); connect(this, SIGNAL(sigInternalStopIsolatedModeRequested()), SLOT(stopIsolatedMode())); setObjectName(rhs.objectName()); m_d->xres = rhs.m_d->xres; m_d->yres = rhs.m_d->yres; if (rhs.m_d->proofingConfig) { m_d->proofingConfig = toQShared(new KisProofingConfiguration(*rhs.m_d->proofingConfig)); } KisNodeSP newRoot = rhs.root()->clone(); newRoot->setGraphListener(this); newRoot->setImage(this); m_d->rootLayer = dynamic_cast(newRoot.data()); setRoot(newRoot); if (exactCopy || rhs.m_d->isolatedRootNode) { QQueue linearizedNodes; KisLayerUtils::recursiveApplyNodes(rhs.root(), [&linearizedNodes](KisNodeSP node) { linearizedNodes.enqueue(node); }); KisLayerUtils::recursiveApplyNodes(newRoot, [&linearizedNodes, exactCopy, &rhs, this](KisNodeSP node) { KisNodeSP refNode = linearizedNodes.dequeue(); if (exactCopy) { node->setUuid(refNode->uuid()); } if (rhs.m_d->isolatedRootNode && rhs.m_d->isolatedRootNode == refNode) { m_d->isolatedRootNode = node; } }); } Q_FOREACH (KisLayerCompositionSP comp, rhs.m_d->compositions) { m_d->compositions << toQShared(new KisLayerComposition(*comp, this)); } rhs.m_d->nserver = KisNameServer(rhs.m_d->nserver); vKisAnnotationSP newAnnotations; Q_FOREACH (KisAnnotationSP annotation, rhs.m_d->annotations) { newAnnotations << annotation->clone(); } m_d->annotations = newAnnotations; KIS_ASSERT_RECOVER_NOOP(!rhs.m_d->projectionUpdatesFilter); KIS_ASSERT_RECOVER_NOOP(!rhs.m_d->disableUIUpdateSignals); KIS_ASSERT_RECOVER_NOOP(!rhs.m_d->disableDirtyRequests); m_d->blockLevelOfDetail = rhs.m_d->blockLevelOfDetail; } void KisImage::aboutToAddANode(KisNode *parent, int index) { KisNodeGraphListener::aboutToAddANode(parent, index); SANITY_CHECK_LOCKED("aboutToAddANode"); } void KisImage::nodeHasBeenAdded(KisNode *parent, int index) { KisNodeGraphListener::nodeHasBeenAdded(parent, index); SANITY_CHECK_LOCKED("nodeHasBeenAdded"); m_d->signalRouter.emitNodeHasBeenAdded(parent, index); KisNodeSP newNode = parent->at(index); if (!dynamic_cast(newNode.data())) { emit sigInternalStopIsolatedModeRequested(); } } void KisImage::aboutToRemoveANode(KisNode *parent, int index) { KisNodeSP deletedNode = parent->at(index); if (!dynamic_cast(deletedNode.data())) { emit sigInternalStopIsolatedModeRequested(); } KisNodeGraphListener::aboutToRemoveANode(parent, index); SANITY_CHECK_LOCKED("aboutToRemoveANode"); m_d->signalRouter.emitAboutToRemoveANode(parent, index); } void KisImage::nodeChanged(KisNode* node) { KisNodeGraphListener::nodeChanged(node); requestStrokeEnd(); m_d->signalRouter.emitNodeChanged(node); } void KisImage::invalidateAllFrames() { invalidateFrames(KisTimeRange::infinite(0), QRect()); } KisSelectionSP KisImage::globalSelection() const { KisSelectionMaskSP selectionMask = m_d->rootLayer->selectionMask(); if (selectionMask) { return selectionMask->selection(); } else { return 0; } } void KisImage::setGlobalSelection(KisSelectionSP globalSelection) { KisSelectionMaskSP selectionMask = m_d->rootLayer->selectionMask(); if (!globalSelection) { if (selectionMask) { removeNode(selectionMask); } } else { if (!selectionMask) { selectionMask = new KisSelectionMask(this); selectionMask->initSelection(m_d->rootLayer); addNode(selectionMask); // If we do not set the selection now, the setActive call coming next // can be very, very expensive, depending on the size of the image. selectionMask->setSelection(globalSelection); selectionMask->setActive(true); } else { selectionMask->setSelection(globalSelection); } Q_ASSERT(m_d->rootLayer->childCount() > 0); Q_ASSERT(m_d->rootLayer->selectionMask()); } m_d->deselectedGlobalSelection = 0; m_d->legacyUndoAdapter.emitSelectionChanged(); } void KisImage::deselectGlobalSelection() { KisSelectionSP savedSelection = globalSelection(); setGlobalSelection(0); m_d->deselectedGlobalSelection = savedSelection; } bool KisImage::canReselectGlobalSelection() { return m_d->deselectedGlobalSelection; } void KisImage::reselectGlobalSelection() { if(m_d->deselectedGlobalSelection) { setGlobalSelection(m_d->deselectedGlobalSelection); } } QString KisImage::nextLayerName(const QString &_baseName) const { QString baseName = _baseName; if (m_d->nserver.currentSeed() == 0) { m_d->nserver.number(); return i18n("background"); } if (baseName.isEmpty()) { baseName = i18n("Layer"); } return QString("%1 %2").arg(baseName).arg(m_d->nserver.number()); } void KisImage::rollBackLayerName() { m_d->nserver.rollback(); } KisCompositeProgressProxy* KisImage::compositeProgressProxy() { return &m_d->compositeProgressProxy; } bool KisImage::locked() const { return m_d->lockCount != 0; } void KisImage::barrierLock(bool readOnly) { if (!locked()) { requestStrokeEnd(); m_d->scheduler.barrierLock(); m_d->lockedForReadOnly = readOnly; } else { m_d->lockedForReadOnly &= readOnly; } m_d->lockCount++; } bool KisImage::tryBarrierLock(bool readOnly) { bool result = true; if (!locked()) { result = m_d->scheduler.tryBarrierLock(); m_d->lockedForReadOnly = readOnly; } if (result) { m_d->lockCount++; m_d->lockedForReadOnly &= readOnly; } return result; } bool KisImage::isIdle(bool allowLocked) { return (allowLocked || !locked()) && m_d->scheduler.isIdle(); } void KisImage::lock() { if (!locked()) { requestStrokeEnd(); m_d->scheduler.lock(); } m_d->lockCount++; m_d->lockedForReadOnly = false; } void KisImage::unlock() { Q_ASSERT(locked()); if (locked()) { m_d->lockCount--; if (m_d->lockCount == 0) { m_d->scheduler.unlock(!m_d->lockedForReadOnly); } } } void KisImage::blockUpdates() { m_d->scheduler.blockUpdates(); } void KisImage::unblockUpdates() { m_d->scheduler.unblockUpdates(); } void KisImage::setSize(const QSize& size) { m_d->width = size.width(); m_d->height = size.height(); } void KisImage::resizeImageImpl(const QRect& newRect, bool cropLayers) { if (newRect == bounds() && !cropLayers) return; KUndo2MagicString actionName = cropLayers ? kundo2_i18n("Crop Image") : kundo2_i18n("Resize Image"); KisImageSignalVector emitSignals; emitSignals << ComplexSizeChangedSignal(newRect, newRect.size()); emitSignals << ModifiedSignal; KisCropSavedExtraData *extraData = new KisCropSavedExtraData(cropLayers ? KisCropSavedExtraData::CROP_IMAGE : KisCropSavedExtraData::RESIZE_IMAGE, newRect); KisProcessingApplicator applicator(this, m_d->rootLayer, KisProcessingApplicator::RECURSIVE | KisProcessingApplicator::NO_UI_UPDATES, emitSignals, actionName, extraData); if (cropLayers || !newRect.topLeft().isNull()) { KisProcessingVisitorSP visitor = new KisCropProcessingVisitor(newRect, cropLayers, true); applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); } applicator.applyCommand(new KisImageResizeCommand(this, newRect.size())); applicator.end(); } void KisImage::resizeImage(const QRect& newRect) { resizeImageImpl(newRect, false); } void KisImage::cropImage(const QRect& newRect) { resizeImageImpl(newRect, true); } void KisImage::cropNode(KisNodeSP node, const QRect& newRect) { bool isLayer = qobject_cast(node.data()); KUndo2MagicString actionName = isLayer ? kundo2_i18n("Crop Layer") : kundo2_i18n("Crop Mask"); KisImageSignalVector emitSignals; emitSignals << ModifiedSignal; KisCropSavedExtraData *extraData = new KisCropSavedExtraData(KisCropSavedExtraData::CROP_LAYER, newRect, node); KisProcessingApplicator applicator(this, node, KisProcessingApplicator::RECURSIVE, emitSignals, actionName, extraData); KisProcessingVisitorSP visitor = new KisCropProcessingVisitor(newRect, true, false); applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); applicator.end(); } void KisImage::scaleImage(const QSize &size, qreal xres, qreal yres, KisFilterStrategy *filterStrategy) { bool resolutionChanged = xres != xRes() && yres != yRes(); bool sizeChanged = size != this->size(); if (!resolutionChanged && !sizeChanged) return; KisImageSignalVector emitSignals; if (resolutionChanged) emitSignals << ResolutionChangedSignal; if (sizeChanged) emitSignals << ComplexSizeChangedSignal(bounds(), size); emitSignals << ModifiedSignal; KUndo2MagicString actionName = sizeChanged ? kundo2_i18n("Scale Image") : kundo2_i18n("Change Image Resolution"); KisProcessingApplicator::ProcessingFlags signalFlags = (resolutionChanged || sizeChanged) ? KisProcessingApplicator::NO_UI_UPDATES : KisProcessingApplicator::NONE; KisProcessingApplicator applicator(this, m_d->rootLayer, KisProcessingApplicator::RECURSIVE | signalFlags, emitSignals, actionName); qreal sx = qreal(size.width()) / this->size().width(); qreal sy = qreal(size.height()) / this->size().height(); QTransform shapesCorrection; if (resolutionChanged) { shapesCorrection = QTransform::fromScale(xRes() / xres, yRes() / yres); } KisProcessingVisitorSP visitor = new KisTransformProcessingVisitor(sx, sy, 0, 0, QPointF(), 0, 0, 0, filterStrategy, shapesCorrection); applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); if (resolutionChanged) { KUndo2Command *parent = new KisResetShapesCommand(m_d->rootLayer); new KisImageSetResolutionCommand(this, xres, yres, parent); applicator.applyCommand(parent); } if (sizeChanged) { applicator.applyCommand(new KisImageResizeCommand(this, size)); } applicator.end(); } void KisImage::scaleNode(KisNodeSP node, qreal scaleX, qreal scaleY, KisFilterStrategy *filterStrategy) { KUndo2MagicString actionName(kundo2_i18n("Scale Layer")); KisImageSignalVector emitSignals; emitSignals << ModifiedSignal; KisProcessingApplicator applicator(this, node, KisProcessingApplicator::RECURSIVE, emitSignals, actionName); KisProcessingVisitorSP visitor = new KisTransformProcessingVisitor(scaleX, scaleY, 0, 0, QPointF(), 0, 0, 0, filterStrategy); applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); applicator.end(); } void KisImage::rotateImpl(const KUndo2MagicString &actionName, KisNodeSP rootNode, bool resizeImage, double radians) { QPointF offset; QSize newSize; { KisTransformWorker worker(0, 1.0, 1.0, 0, 0, 0, 0, radians, 0, 0, 0, 0); QTransform transform = worker.transform(); if (resizeImage) { QRect newRect = transform.mapRect(bounds()); newSize = newRect.size(); offset = -newRect.topLeft(); } else { QPointF origin = QRectF(rootNode->exactBounds()).center(); newSize = size(); offset = -(transform.map(origin) - origin); } } bool sizeChanged = resizeImage && (newSize.width() != width() || newSize.height() != height()); // These signals will be emitted after processing is done KisImageSignalVector emitSignals; if (sizeChanged) emitSignals << ComplexSizeChangedSignal(bounds(), newSize); emitSignals << ModifiedSignal; // These flags determine whether updates are transferred to the UI during processing KisProcessingApplicator::ProcessingFlags signalFlags = sizeChanged ? KisProcessingApplicator::NO_UI_UPDATES : KisProcessingApplicator::NONE; KisProcessingApplicator applicator(this, rootNode, KisProcessingApplicator::RECURSIVE | signalFlags, emitSignals, actionName); KisFilterStrategy *filter = KisFilterStrategyRegistry::instance()->value("Bicubic"); KisProcessingVisitorSP visitor = new KisTransformProcessingVisitor(1.0, 1.0, 0.0, 0.0, QPointF(), radians, offset.x(), offset.y(), filter); applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); if (sizeChanged) { applicator.applyCommand(new KisImageResizeCommand(this, newSize)); } applicator.end(); } void KisImage::rotateImage(double radians) { rotateImpl(kundo2_i18n("Rotate Image"), root(), true, radians); } void KisImage::rotateNode(KisNodeSP node, double radians) { if (node->inherits("KisMask")) { rotateImpl(kundo2_i18n("Rotate Mask"), node, false, radians); } else { rotateImpl(kundo2_i18n("Rotate Layer"), node, false, radians); } } void KisImage::shearImpl(const KUndo2MagicString &actionName, KisNodeSP rootNode, bool resizeImage, double angleX, double angleY, const QPointF &origin) { //angleX, angleY are in degrees const qreal pi = 3.1415926535897932385; const qreal deg2rad = pi / 180.0; qreal tanX = tan(angleX * deg2rad); qreal tanY = tan(angleY * deg2rad); QPointF offset; QSize newSize; { KisTransformWorker worker(0, 1.0, 1.0, tanX, tanY, origin.x(), origin.y(), 0, 0, 0, 0, 0); QRect newRect = worker.transform().mapRect(bounds()); newSize = newRect.size(); if (resizeImage) offset = -newRect.topLeft(); } if (newSize == size()) return; KisImageSignalVector emitSignals; if (resizeImage) emitSignals << ComplexSizeChangedSignal(bounds(), newSize); emitSignals << ModifiedSignal; KisProcessingApplicator::ProcessingFlags signalFlags = KisProcessingApplicator::RECURSIVE; if (resizeImage) signalFlags |= KisProcessingApplicator::NO_UI_UPDATES; KisProcessingApplicator applicator(this, rootNode, signalFlags, emitSignals, actionName); KisFilterStrategy *filter = KisFilterStrategyRegistry::instance()->value("Bilinear"); KisProcessingVisitorSP visitor = new KisTransformProcessingVisitor(1.0, 1.0, tanX, tanY, origin, 0, offset.x(), offset.y(), filter); applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); if (resizeImage) { applicator.applyCommand(new KisImageResizeCommand(this, newSize)); } applicator.end(); } void KisImage::shearNode(KisNodeSP node, double angleX, double angleY) { QPointF shearOrigin = QRectF(bounds()).center(); if (node->inherits("KisMask")) { shearImpl(kundo2_i18n("Shear Mask"), node, false, angleX, angleY, shearOrigin); } else { shearImpl(kundo2_i18n("Shear Layer"), node, false, angleX, angleY, shearOrigin); } } void KisImage::shear(double angleX, double angleY) { shearImpl(kundo2_i18n("Shear Image"), m_d->rootLayer, true, angleX, angleY, QPointF()); } void KisImage::convertImageColorSpace(const KoColorSpace *dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { if (!dstColorSpace) return; const KoColorSpace *srcColorSpace = m_d->colorSpace; undoAdapter()->beginMacro(kundo2_i18n("Convert Image Color Space")); undoAdapter()->addCommand(new KisImageLockCommand(KisImageWSP(this), true)); undoAdapter()->addCommand(new KisImageSetProjectionColorSpaceCommand(KisImageWSP(this), dstColorSpace)); KisColorSpaceConvertVisitor visitor(this, srcColorSpace, dstColorSpace, renderingIntent, conversionFlags); m_d->rootLayer->accept(visitor); undoAdapter()->addCommand(new KisImageLockCommand(KisImageWSP(this), false)); undoAdapter()->endMacro(); setModified(); } bool KisImage::assignImageProfile(const KoColorProfile *profile) { if (!profile) return false; const KoColorSpace *dstCs = KoColorSpaceRegistry::instance()->colorSpace(colorSpace()->colorModelId().id(), colorSpace()->colorDepthId().id(), profile); const KoColorSpace *srcCs = colorSpace(); if (!dstCs) return false; m_d->colorSpace = dstCs; KisChangeProfileVisitor visitor(srcCs, dstCs); bool retval = m_d->rootLayer->accept(visitor); m_d->signalRouter.emitNotification(ProfileChangedSignal); return retval; } void KisImage::convertProjectionColorSpace(const KoColorSpace *dstColorSpace) { if (*m_d->colorSpace == *dstColorSpace) return; undoAdapter()->beginMacro(kundo2_i18n("Convert Projection Color Space")); undoAdapter()->addCommand(new KisImageLockCommand(KisImageWSP(this), true)); undoAdapter()->addCommand(new KisImageSetProjectionColorSpaceCommand(KisImageWSP(this), dstColorSpace)); undoAdapter()->addCommand(new KisImageLockCommand(KisImageWSP(this), false)); undoAdapter()->endMacro(); setModified(); } void KisImage::setProjectionColorSpace(const KoColorSpace * colorSpace) { m_d->colorSpace = colorSpace; m_d->rootLayer->resetCache(); m_d->signalRouter.emitNotification(ColorSpaceChangedSignal); } const KoColorSpace * KisImage::colorSpace() const { return m_d->colorSpace; } const KoColorProfile * KisImage::profile() const { return colorSpace()->profile(); } double KisImage::xRes() const { return m_d->xres; } double KisImage::yRes() const { return m_d->yres; } void KisImage::setResolution(double xres, double yres) { m_d->xres = xres; m_d->yres = yres; m_d->signalRouter.emitNotification(ResolutionChangedSignal); } QPointF KisImage::documentToPixel(const QPointF &documentCoord) const { return QPointF(documentCoord.x() * xRes(), documentCoord.y() * yRes()); } QPoint KisImage::documentToImagePixelFloored(const QPointF &documentCoord) const { QPointF pixelCoord = documentToPixel(documentCoord); return QPoint(qFloor(pixelCoord.x()), qFloor(pixelCoord.y())); } QRectF KisImage::documentToPixel(const QRectF &documentRect) const { return QRectF(documentToPixel(documentRect.topLeft()), documentToPixel(documentRect.bottomRight())); } QPointF KisImage::pixelToDocument(const QPointF &pixelCoord) const { return QPointF(pixelCoord.x() / xRes(), pixelCoord.y() / yRes()); } QPointF KisImage::pixelToDocument(const QPoint &pixelCoord) const { return QPointF((pixelCoord.x() + 0.5) / xRes(), (pixelCoord.y() + 0.5) / yRes()); } QRectF KisImage::pixelToDocument(const QRectF &pixelCoord) const { return QRectF(pixelToDocument(pixelCoord.topLeft()), pixelToDocument(pixelCoord.bottomRight())); } qint32 KisImage::width() const { return m_d->width; } qint32 KisImage::height() const { return m_d->height; } KisGroupLayerSP KisImage::rootLayer() const { Q_ASSERT(m_d->rootLayer); return m_d->rootLayer; } KisPaintDeviceSP KisImage::projection() const { if (m_d->isolatedRootNode) { return m_d->isolatedRootNode->projection(); } Q_ASSERT(m_d->rootLayer); KisPaintDeviceSP projection = m_d->rootLayer->projection(); Q_ASSERT(projection); return projection; } qint32 KisImage::nlayers() const { QStringList list; list << "KisLayer"; KisCountVisitor visitor(list, KoProperties()); m_d->rootLayer->accept(visitor); return visitor.count(); } qint32 KisImage::nHiddenLayers() const { QStringList list; list << "KisLayer"; KoProperties properties; properties.setProperty("visible", false); KisCountVisitor visitor(list, properties); m_d->rootLayer->accept(visitor); return visitor.count(); } void KisImage::flatten() { KisLayerUtils::flattenImage(this); } void KisImage::mergeMultipleLayers(QList mergedNodes, KisNodeSP putAfter) { if (!KisLayerUtils::tryMergeSelectionMasks(this, mergedNodes, putAfter)) { KisLayerUtils::mergeMultipleLayers(this, mergedNodes, putAfter); } } void KisImage::mergeDown(KisLayerSP layer, const KisMetaData::MergeStrategy* strategy) { KisLayerUtils::mergeDown(this, layer, strategy); } void KisImage::flattenLayer(KisLayerSP layer) { KisLayerUtils::flattenLayer(this, layer); } void KisImage::setModified() { m_d->signalRouter.emitNotification(ModifiedSignal); } QImage KisImage::convertToQImage(QRect imageRect, const KoColorProfile * profile) { qint32 x; qint32 y; qint32 w; qint32 h; imageRect.getRect(&x, &y, &w, &h); return convertToQImage(x, y, w, h, profile); } QImage KisImage::convertToQImage(qint32 x, qint32 y, qint32 w, qint32 h, const KoColorProfile * profile) { KisPaintDeviceSP dev = projection(); if (!dev) return QImage(); QImage image = dev->convertToQImage(const_cast(profile), x, y, w, h, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); return image; } QImage KisImage::convertToQImage(const QSize& scaledImageSize, const KoColorProfile *profile) { if (scaledImageSize.isEmpty()) { return QImage(); } KisPaintDeviceSP dev = new KisPaintDevice(colorSpace()); KisPainter gc; gc.copyAreaOptimized(QPoint(0, 0), projection(), dev, bounds()); gc.end(); double scaleX = qreal(scaledImageSize.width()) / width(); double scaleY = qreal(scaledImageSize.height()) / height(); QPointer updater = new KoDummyUpdater(); KisTransformWorker worker(dev, scaleX, scaleY, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, updater, KisFilterStrategyRegistry::instance()->value("Bicubic")); worker.run(); delete updater; return dev->convertToQImage(profile); } void KisImage::notifyLayersChanged() { m_d->signalRouter.emitNotification(LayersChangedSignal); } QRect KisImage::bounds() const { return QRect(0, 0, width(), height()); } QRect KisImage::effectiveLodBounds() const { QRect boundRect = bounds(); const int lod = currentLevelOfDetail(); if (lod > 0) { KisLodTransform t(lod); boundRect = t.map(boundRect); } return boundRect; } KisPostExecutionUndoAdapter* KisImage::postExecutionUndoAdapter() const { const int lod = currentLevelOfDetail(); return lod > 0 ? m_d->scheduler.lodNPostExecutionUndoAdapter() : &m_d->postExecutionUndoAdapter; } void KisImage::setUndoStore(KisUndoStore *undoStore) { m_d->legacyUndoAdapter.setUndoStore(undoStore); m_d->postExecutionUndoAdapter.setUndoStore(undoStore); m_d->undoStore.reset(undoStore); } KisUndoStore* KisImage::undoStore() { return m_d->undoStore.data(); } KisUndoAdapter* KisImage::undoAdapter() const { return &m_d->legacyUndoAdapter; } KisActionRecorder* KisImage::actionRecorder() const { return &m_d->recorder; } void KisImage::setDefaultProjectionColor(const KoColor &color) { KIS_ASSERT_RECOVER_RETURN(m_d->rootLayer); m_d->rootLayer->setDefaultProjectionColor(color); } KoColor KisImage::defaultProjectionColor() const { KIS_ASSERT_RECOVER(m_d->rootLayer) { return KoColor(Qt::transparent, m_d->colorSpace); } return m_d->rootLayer->defaultProjectionColor(); } void KisImage::setRootLayer(KisGroupLayerSP rootLayer) { emit sigInternalStopIsolatedModeRequested(); KoColor defaultProjectionColor(Qt::transparent, m_d->colorSpace); if (m_d->rootLayer) { m_d->rootLayer->setGraphListener(0); m_d->rootLayer->disconnect(); KisPaintDeviceSP original = m_d->rootLayer->original(); defaultProjectionColor = original->defaultPixel(); } m_d->rootLayer = rootLayer; m_d->rootLayer->disconnect(); m_d->rootLayer->setGraphListener(this); m_d->rootLayer->setImage(this); KisPaintDeviceSP newOriginal = m_d->rootLayer->original(); newOriginal->setDefaultPixel(defaultProjectionColor); setRoot(m_d->rootLayer.data()); } void KisImage::addAnnotation(KisAnnotationSP annotation) { // Find the icc annotation, if there is one vKisAnnotationSP_it it = m_d->annotations.begin(); while (it != m_d->annotations.end()) { if ((*it)->type() == annotation->type()) { *it = annotation; return; } ++it; } m_d->annotations.push_back(annotation); } KisAnnotationSP KisImage::annotation(const QString& type) { vKisAnnotationSP_it it = m_d->annotations.begin(); while (it != m_d->annotations.end()) { if ((*it)->type() == type) { return *it; } ++it; } return KisAnnotationSP(0); } void KisImage::removeAnnotation(const QString& type) { vKisAnnotationSP_it it = m_d->annotations.begin(); while (it != m_d->annotations.end()) { if ((*it)->type() == type) { m_d->annotations.erase(it); return; } ++it; } } vKisAnnotationSP_it KisImage::beginAnnotations() { return m_d->annotations.begin(); } vKisAnnotationSP_it KisImage::endAnnotations() { return m_d->annotations.end(); } void KisImage::notifyAboutToBeDeleted() { emit sigAboutToBeDeleted(); } KisImageSignalRouter* KisImage::signalRouter() { return &m_d->signalRouter; } void KisImage::waitForDone() { requestStrokeEnd(); m_d->scheduler.waitForDone(); } KisStrokeId KisImage::startStroke(KisStrokeStrategy *strokeStrategy) { /** * Ask open strokes to end gracefully. All the strokes clients * (including the one calling this method right now) will get * a notification that they should probably end their strokes. * However this is purely their choice whether to end a stroke * or not. */ if (strokeStrategy->requestsOtherStrokesToEnd()) { requestStrokeEnd(); } /** * Some of the strokes can cancel their work with undoing all the * changes they did to the paint devices. The problem is that undo * stack will know nothing about it. Therefore, just notify it * explicitly */ if (strokeStrategy->clearsRedoOnStart()) { m_d->undoStore->purgeRedoState(); } return m_d->scheduler.startStroke(strokeStrategy); } void KisImage::KisImagePrivate::notifyProjectionUpdatedInPatches(const QRect &rc) { KisImageConfig imageConfig; int patchWidth = imageConfig.updatePatchWidth(); int patchHeight = imageConfig.updatePatchHeight(); for (int y = 0; y < rc.height(); y += patchHeight) { for (int x = 0; x < rc.width(); x += patchWidth) { QRect patchRect(x, y, patchWidth, patchHeight); patchRect &= rc; QtConcurrent::run(std::bind(&KisImage::notifyProjectionUpdated, q, patchRect)); } } } bool KisImage::startIsolatedMode(KisNodeSP node) { struct StartIsolatedModeStroke : public KisSimpleStrokeStrategy { StartIsolatedModeStroke(KisNodeSP node, KisImageSP image) : KisSimpleStrokeStrategy("start-isolated-mode", kundo2_noi18n("start-isolated-mode")), m_node(node), m_image(image) { - this->enableJob(JOB_INIT); + this->enableJob(JOB_INIT, true, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); setClearsRedoOnStart(false); } void initStrokeCallback() { + // pass-though node don't have any projection prepared, so we should + // explicitly regenerate it before activating isolated mode. + m_node->projectionLeaf()->explicitlyRegeneratePassThroughProjection(); + m_image->m_d->isolatedRootNode = m_node; emit m_image->sigIsolatedModeChanged(); // the GUI uses our thread to do the color space conversion so we // need to emit this signal in multiple threads m_image->m_d->notifyProjectionUpdatedInPatches(m_image->bounds()); m_image->invalidateAllFrames(); } private: KisNodeSP m_node; KisImageSP m_image; }; KisStrokeId id = startStroke(new StartIsolatedModeStroke(node, this)); endStroke(id); return true; } void KisImage::stopIsolatedMode() { if (!m_d->isolatedRootNode) return; struct StopIsolatedModeStroke : public KisSimpleStrokeStrategy { StopIsolatedModeStroke(KisImageSP image) : KisSimpleStrokeStrategy("stop-isolated-mode", kundo2_noi18n("stop-isolated-mode")), m_image(image) { this->enableJob(JOB_INIT); setClearsRedoOnStart(false); } void initStrokeCallback() { if (!m_image->m_d->isolatedRootNode) return; //KisNodeSP oldRootNode = m_image->m_d->isolatedRootNode; m_image->m_d->isolatedRootNode = 0; emit m_image->sigIsolatedModeChanged(); // the GUI uses our thread to do the color space conversion so we // need to emit this signal in multiple threads m_image->m_d->notifyProjectionUpdatedInPatches(m_image->bounds()); m_image->invalidateAllFrames(); // TODO: Substitute notifyProjectionUpdated() with this code // when update optimization is implemented // // QRect updateRect = bounds() | oldRootNode->extent(); // oldRootNode->setDirty(updateRect); } private: KisImageSP m_image; }; KisStrokeId id = startStroke(new StopIsolatedModeStroke(this)); endStroke(id); } KisNodeSP KisImage::isolatedModeRoot() const { return m_d->isolatedRootNode; } void KisImage::addJob(KisStrokeId id, KisStrokeJobData *data) { KisUpdateTimeMonitor::instance()->reportJobStarted(data); m_d->scheduler.addJob(id, data); } void KisImage::endStroke(KisStrokeId id) { m_d->scheduler.endStroke(id); } bool KisImage::cancelStroke(KisStrokeId id) { return m_d->scheduler.cancelStroke(id); } bool KisImage::KisImagePrivate::tryCancelCurrentStrokeAsync() { return scheduler.tryCancelCurrentStrokeAsync(); } void KisImage::requestUndoDuringStroke() { emit sigUndoDuringStrokeRequested(); } void KisImage::requestStrokeCancellation() { if (!m_d->tryCancelCurrentStrokeAsync()) { emit sigStrokeCancellationRequested(); } } UndoResult KisImage::tryUndoUnfinishedLod0Stroke() { return m_d->scheduler.tryUndoLastStrokeAsync(); } void KisImage::requestStrokeEnd() { emit sigStrokeEndRequested(); emit sigStrokeEndRequestedActiveNodeFiltered(); } void KisImage::requestStrokeEndActiveNode() { emit sigStrokeEndRequested(); } void KisImage::refreshGraph(KisNodeSP root) { refreshGraph(root, bounds(), bounds()); } void KisImage::refreshGraph(KisNodeSP root, const QRect &rc, const QRect &cropRect) { if (!root) root = m_d->rootLayer; m_d->animationInterface->notifyNodeChanged(root.data(), rc, true); m_d->scheduler.fullRefresh(root, rc, cropRect); } void KisImage::initialRefreshGraph() { /** * NOTE: Tricky part. We set crop rect to null, so the clones * will not rely on precalculated projections of their sources */ refreshGraphAsync(0, bounds(), QRect()); waitForDone(); } void KisImage::refreshGraphAsync(KisNodeSP root) { refreshGraphAsync(root, bounds(), bounds()); } void KisImage::refreshGraphAsync(KisNodeSP root, const QRect &rc) { refreshGraphAsync(root, rc, bounds()); } void KisImage::refreshGraphAsync(KisNodeSP root, const QRect &rc, const QRect &cropRect) { if (!root) root = m_d->rootLayer; m_d->animationInterface->notifyNodeChanged(root.data(), rc, true); m_d->scheduler.fullRefreshAsync(root, rc, cropRect); } void KisImage::requestProjectionUpdateNoFilthy(KisNodeSP pseudoFilthy, const QRect &rc, const QRect &cropRect) { KIS_ASSERT_RECOVER_RETURN(pseudoFilthy); m_d->animationInterface->notifyNodeChanged(pseudoFilthy.data(), rc, false); m_d->scheduler.updateProjectionNoFilthy(pseudoFilthy, rc, cropRect); } void KisImage::addSpontaneousJob(KisSpontaneousJob *spontaneousJob) { m_d->scheduler.addSpontaneousJob(spontaneousJob); } void KisImage::setProjectionUpdatesFilter(KisProjectionUpdatesFilterSP filter) { // update filters are *not* recursive! KIS_ASSERT_RECOVER_NOOP(!filter || !m_d->projectionUpdatesFilter); m_d->projectionUpdatesFilter = filter; } KisProjectionUpdatesFilterSP KisImage::projectionUpdatesFilter() const { return m_d->projectionUpdatesFilter; } void KisImage::disableDirtyRequests() { setProjectionUpdatesFilter(KisProjectionUpdatesFilterSP(new KisDropAllProjectionUpdatesFilter())); } void KisImage::enableDirtyRequests() { setProjectionUpdatesFilter(KisProjectionUpdatesFilterSP()); } void KisImage::disableUIUpdates() { m_d->disableUIUpdateSignals.ref(); } void KisImage::enableUIUpdates() { m_d->disableUIUpdateSignals.deref(); } void KisImage::notifyProjectionUpdated(const QRect &rc) { KisUpdateTimeMonitor::instance()->reportUpdateFinished(rc); if (!m_d->disableUIUpdateSignals) { int lod = currentLevelOfDetail(); QRect dirtyRect = !lod ? rc : KisLodTransform::upscaledRect(rc, lod); if (dirtyRect.isEmpty()) return; emit sigImageUpdated(dirtyRect); } } void KisImage::setWorkingThreadsLimit(int value) { m_d->scheduler.setThreadsLimit(value); } int KisImage::workingThreadsLimit() const { return m_d->scheduler.threadsLimit(); } void KisImage::notifySelectionChanged() { /** * The selection is calculated asynchromously, so it is not * handled by disableUIUpdates() and other special signals of * KisImageSignalRouter */ m_d->legacyUndoAdapter.emitSelectionChanged(); /** * Editing of selection masks doesn't necessary produce a * setDirty() call, so in the end of the stroke we need to request * direct update of the UI's cache. */ if (m_d->isolatedRootNode && dynamic_cast(m_d->isolatedRootNode.data())) { notifyProjectionUpdated(bounds()); } } void KisImage::requestProjectionUpdateImpl(KisNode *node, const QVector &rects, const QRect &cropRect) { if (rects.isEmpty()) return; m_d->scheduler.updateProjection(node, rects, cropRect); } void KisImage::requestProjectionUpdate(KisNode *node, const QVector &rects, bool resetAnimationCache) { if (m_d->projectionUpdatesFilter && m_d->projectionUpdatesFilter->filter(this, node, rects, resetAnimationCache)) { return; } if (resetAnimationCache) { m_d->animationInterface->notifyNodeChanged(node, rects, false); } /** * Here we use 'permitted' instead of 'active' intentively, * because the updates may come after the actual stroke has been * finished. And having some more updates for the stroke not * supporting the wrap-around mode will not make much harm. */ if (m_d->wrapAroundModePermitted) { QVector allSplitRects; const QRect boundRect = effectiveLodBounds(); Q_FOREACH (const QRect &rc, rects) { KisWrappedRect splitRect(rc, boundRect); allSplitRects.append(splitRect); } requestProjectionUpdateImpl(node, allSplitRects, boundRect); } else { requestProjectionUpdateImpl(node, rects, bounds()); } KisNodeGraphListener::requestProjectionUpdate(node, rects, resetAnimationCache); } void KisImage::invalidateFrames(const KisTimeRange &range, const QRect &rect) { m_d->animationInterface->invalidateFrames(range, rect); } void KisImage::requestTimeSwitch(int time) { m_d->animationInterface->requestTimeSwitchNonGUI(time); } QList KisImage::compositions() { return m_d->compositions; } void KisImage::addComposition(KisLayerCompositionSP composition) { m_d->compositions.append(composition); } void KisImage::removeComposition(KisLayerCompositionSP composition) { m_d->compositions.removeAll(composition); } bool checkMasksNeedConversion(KisNodeSP root, const QRect &bounds) { KisSelectionMask *mask = dynamic_cast(root.data()); if (mask && (!bounds.contains(mask->paintDevice()->exactBounds()) || mask->selection()->hasShapeSelection())) { return true; } KisNodeSP node = root->firstChild(); while (node) { if (checkMasksNeedConversion(node, bounds)) { return true; } node = node->nextSibling(); } return false; } void KisImage::setWrapAroundModePermitted(bool value) { if (m_d->wrapAroundModePermitted != value) { requestStrokeEnd(); } m_d->wrapAroundModePermitted = value; if (m_d->wrapAroundModePermitted && checkMasksNeedConversion(root(), bounds())) { KisProcessingApplicator applicator(this, root(), KisProcessingApplicator::RECURSIVE, KisImageSignalVector() << ModifiedSignal, kundo2_i18n("Crop Selections")); KisProcessingVisitorSP visitor = new KisCropSelectionsProcessingVisitor(bounds()); applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT); applicator.end(); } } bool KisImage::wrapAroundModePermitted() const { return m_d->wrapAroundModePermitted; } bool KisImage::wrapAroundModeActive() const { return m_d->wrapAroundModePermitted && m_d->scheduler.wrapAroundModeSupported(); } void KisImage::setDesiredLevelOfDetail(int lod) { if (m_d->blockLevelOfDetail) { qWarning() << "WARNING: KisImage::setDesiredLevelOfDetail()" << "was called while LoD functionality was being blocked!"; return; } m_d->scheduler.setDesiredLevelOfDetail(lod); } int KisImage::currentLevelOfDetail() const { if (m_d->blockLevelOfDetail) { return 0; } return m_d->scheduler.currentLevelOfDetail(); } void KisImage::setLevelOfDetailBlocked(bool value) { KisImageBarrierLockerRaw l(this); if (value && !m_d->blockLevelOfDetail) { m_d->scheduler.setDesiredLevelOfDetail(0); } m_d->blockLevelOfDetail = value; } void KisImage::explicitRegenerateLevelOfDetail() { if (!m_d->blockLevelOfDetail) { m_d->scheduler.explicitRegenerateLevelOfDetail(); } } bool KisImage::levelOfDetailBlocked() const { return m_d->blockLevelOfDetail; } void KisImage::notifyNodeCollpasedChanged() { emit sigNodeCollapsedChanged(); } KisImageAnimationInterface* KisImage::animationInterface() const { return m_d->animationInterface; } void KisImage::setProofingConfiguration(KisProofingConfigurationSP proofingConfig) { m_d->proofingConfig = proofingConfig; emit sigProofingConfigChanged(); } KisProofingConfigurationSP KisImage::proofingConfiguration() const { if (m_d->proofingConfig) { return m_d->proofingConfig; } return KisProofingConfigurationSP(); } QPointF KisImage::mirrorAxesCenter() const { return m_d->axesCenter; } void KisImage::setMirrorAxesCenter(const QPointF &value) const { m_d->axesCenter = value; } diff --git a/libs/image/kis_image_config.cpp b/libs/image/kis_image_config.cpp index 6663aade57..014aef52ab 100644 --- a/libs/image/kis_image_config.cpp +++ b/libs/image/kis_image_config.cpp @@ -1,521 +1,588 @@ /* * Copyright (c) 2010 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_image_config.h" #include #include #include #include #include #include "kis_debug.h" #include #include #include #include #include "kis_global.h" #include #ifdef Q_OS_OSX #include #endif KisImageConfig::KisImageConfig(bool readOnly) : m_config( KSharedConfig::openConfig()->group(QString())), m_readOnly(readOnly) { #ifdef Q_OS_OSX // clear /var/folders/ swap path set by old broken Krita swap implementation in order to use new default swap dir. QString swap = m_config.readEntry("swaplocation", ""); if (swap.startsWith("/var/folders/")) { m_config.deleteEntry("swaplocation"); } #endif } KisImageConfig::~KisImageConfig() { if (m_readOnly) return; if (qApp->thread() != QThread::currentThread()) { dbgKrita << "KisImageConfig: requested config synchronization from nonGUI thread! Called from" << kisBacktrace(); return; } m_config.sync(); } bool KisImageConfig::enableProgressReporting(bool requestDefault) const { return !requestDefault ? m_config.readEntry("enableProgressReporting", true) : true; } void KisImageConfig::setEnableProgressReporting(bool value) { m_config.writeEntry("enableProgressReporting", value); } bool KisImageConfig::enablePerfLog(bool requestDefault) const { return !requestDefault ? m_config.readEntry("enablePerfLog", false) :false; } void KisImageConfig::setEnablePerfLog(bool value) { m_config.writeEntry("enablePerfLog", value); } qreal KisImageConfig::transformMaskOffBoundsReadArea() const { return m_config.readEntry("transformMaskOffBoundsReadArea", 0.5); } int KisImageConfig::updatePatchHeight() const { return m_config.readEntry("updatePatchHeight", 512); } void KisImageConfig::setUpdatePatchHeight(int value) { m_config.writeEntry("updatePatchHeight", value); } int KisImageConfig::updatePatchWidth() const { return m_config.readEntry("updatePatchWidth", 512); } void KisImageConfig::setUpdatePatchWidth(int value) { m_config.writeEntry("updatePatchWidth", value); } qreal KisImageConfig::maxCollectAlpha() const { return m_config.readEntry("maxCollectAlpha", 2.5); } qreal KisImageConfig::maxMergeAlpha() const { return m_config.readEntry("maxMergeAlpha", 1.); } qreal KisImageConfig::maxMergeCollectAlpha() const { return m_config.readEntry("maxMergeCollectAlpha", 1.5); } qreal KisImageConfig::schedulerBalancingRatio() const { /** * updates-queue-size / strokes-queue-size */ return m_config.readEntry("schedulerBalancingRatio", 100.); } void KisImageConfig::setSchedulerBalancingRatio(qreal value) { m_config.writeEntry("schedulerBalancingRatio", value); } int KisImageConfig::maxSwapSize(bool requestDefault) const { return !requestDefault ? m_config.readEntry("maxSwapSize", 4096) : 4096; // in MiB } void KisImageConfig::setMaxSwapSize(int value) { m_config.writeEntry("maxSwapSize", value); } int KisImageConfig::swapSlabSize() const { return m_config.readEntry("swapSlabSize", 64); // in MiB } void KisImageConfig::setSwapSlabSize(int value) { m_config.writeEntry("swapSlabSize", value); } int KisImageConfig::swapWindowSize() const { return m_config.readEntry("swapWindowSize", 16); // in MiB } void KisImageConfig::setSwapWindowSize(int value) { m_config.writeEntry("swapWindowSize", value); } int KisImageConfig::tilesHardLimit() const { qreal hp = qreal(memoryHardLimitPercent()) / 100.0; qreal pp = qreal(memoryPoolLimitPercent()) / 100.0; return totalRAM() * hp * (1 - pp); } int KisImageConfig::tilesSoftLimit() const { qreal sp = qreal(memorySoftLimitPercent()) / 100.0; return tilesHardLimit() * sp; } int KisImageConfig::poolLimit() const { qreal hp = qreal(memoryHardLimitPercent()) / 100.0; qreal pp = qreal(memoryPoolLimitPercent()) / 100.0; return totalRAM() * hp * pp; } qreal KisImageConfig::memoryHardLimitPercent(bool requestDefault) const { return !requestDefault ? m_config.readEntry("memoryHardLimitPercent", 50.) : 50.; } void KisImageConfig::setMemoryHardLimitPercent(qreal value) { m_config.writeEntry("memoryHardLimitPercent", value); } qreal KisImageConfig::memorySoftLimitPercent(bool requestDefault) const { return !requestDefault ? m_config.readEntry("memorySoftLimitPercent", 2.) : 2.; } void KisImageConfig::setMemorySoftLimitPercent(qreal value) { m_config.writeEntry("memorySoftLimitPercent", value); } qreal KisImageConfig::memoryPoolLimitPercent(bool requestDefault) const { return !requestDefault ? m_config.readEntry("memoryPoolLimitPercent", 0.0) : 0.0; } void KisImageConfig::setMemoryPoolLimitPercent(qreal value) { m_config.writeEntry("memoryPoolLimitPercent", value); } -QString KisImageConfig::swapDir(bool requestDefault) +QString KisImageConfig::safelyGetWritableTempLocation(const QString &suffix, const QString &configKey, bool requestDefault) const { #ifdef Q_OS_OSX // On OSX, QDir::tempPath() gives us a folder we cannot reply upon (usually // something like /var/folders/.../...) and that will have vanished when we // try to create the tmp file in KisMemoryWindow::KisMemoryWindow using // swapFileTemplate. thus, we just pick the home folder if swapDir does not // tell us otherwise. // the other option here would be to use a "garbled name" temp file (i.e. no name // KRITA_SWAP_FILE_XXXXXX) in an obscure /var/folders place, which is not // nice to the user. having a clearly named swap file in the home folder is // much nicer to Krita's users. // furthermore, this is just a default and swapDir can always be configured // to another location. - QString swap = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/swap"; + QString swap = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + QDir::separator() + suffix; #else + Q_UNUSED(suffix); QString swap = QDir::tempPath(); #endif if (requestDefault) { return swap; } - QString configuredSwap = m_config.readEntry("swaplocation", swap); + const QString configuredSwap = m_config.readEntry(configKey, swap); if (!configuredSwap.isEmpty()) { swap = configuredSwap; } return swap; } + +QString KisImageConfig::swapDir(bool requestDefault) +{ + return safelyGetWritableTempLocation("swap", "swaplocation", requestDefault); +} + void KisImageConfig::setSwapDir(const QString &swapDir) { m_config.writeEntry("swaplocation", swapDir); } int KisImageConfig::numberOfOnionSkins() const { return m_config.readEntry("numberOfOnionSkins", 10); } void KisImageConfig::setNumberOfOnionSkins(int value) { m_config.writeEntry("numberOfOnionSkins", value); } int KisImageConfig::onionSkinTintFactor() const { return m_config.readEntry("onionSkinTintFactor", 192); } void KisImageConfig::setOnionSkinTintFactor(int value) { m_config.writeEntry("onionSkinTintFactor", value); } int KisImageConfig::onionSkinOpacity(int offset) const { int value = m_config.readEntry("onionSkinOpacity_" + QString::number(offset), -1); if (value < 0) { const int num = numberOfOnionSkins(); const qreal dx = qreal(qAbs(offset)) / num; value = 0.7 * exp(-pow2(dx) / 0.5) * 255; } return value; } void KisImageConfig::setOnionSkinOpacity(int offset, int value) { m_config.writeEntry("onionSkinOpacity_" + QString::number(offset), value); } bool KisImageConfig::onionSkinState(int offset) const { bool enableByDefault = (qAbs(offset) <= 2); return m_config.readEntry("onionSkinState_" + QString::number(offset), enableByDefault); } void KisImageConfig::setOnionSkinState(int offset, bool value) { m_config.writeEntry("onionSkinState_" + QString::number(offset), value); } QColor KisImageConfig::onionSkinTintColorBackward() const { return m_config.readEntry("onionSkinTintColorBackward", QColor(Qt::red)); } void KisImageConfig::setOnionSkinTintColorBackward(const QColor &value) { m_config.writeEntry("onionSkinTintColorBackward", value); } QColor KisImageConfig::onionSkinTintColorForward() const { return m_config.readEntry("oninSkinTintColorForward", QColor(Qt::green)); } void KisImageConfig::setOnionSkinTintColorForward(const QColor &value) { m_config.writeEntry("oninSkinTintColorForward", value); } bool KisImageConfig::lazyFrameCreationEnabled(bool requestDefault) const { return !requestDefault ? m_config.readEntry("lazyFrameCreationEnabled", true) : true; } void KisImageConfig::setLazyFrameCreationEnabled(bool value) { m_config.writeEntry("lazyFrameCreationEnabled", value); } #if defined Q_OS_LINUX #include #elif defined Q_OS_FREEBSD || defined Q_OS_NETBSD || defined Q_OS_OPENBSD #include #elif defined Q_OS_WIN #include #elif defined Q_OS_OSX #include #include #endif #include int KisImageConfig::totalRAM() { // let's think that default memory size is 1000MiB int totalMemory = 1000; // MiB int error = 1; #if defined Q_OS_LINUX struct sysinfo info; error = sysinfo(&info); if(!error) { totalMemory = info.totalram * info.mem_unit / (1UL << 20); } #elif defined Q_OS_FREEBSD || defined Q_OS_NETBSD || defined Q_OS_OPENBSD u_long physmem; # if defined HW_PHYSMEM64 // NetBSD only int mib[] = {CTL_HW, HW_PHYSMEM64}; # else int mib[] = {CTL_HW, HW_PHYSMEM}; # endif size_t len = sizeof(physmem); error = sysctl(mib, 2, &physmem, &len, 0, 0); if(!error) { totalMemory = physmem >> 20; } #elif defined Q_OS_WIN MEMORYSTATUSEX status; status.dwLength = sizeof(status); error = !GlobalMemoryStatusEx(&status); if (!error) { totalMemory = status.ullTotalPhys >> 20; } // For 32 bit windows, the total memory available is at max the 2GB per process memory limit. # if defined ENV32BIT totalMemory = qMin(totalMemory, 2000); # endif #elif defined Q_OS_OSX int mib[2] = { CTL_HW, HW_MEMSIZE }; u_int namelen = sizeof(mib) / sizeof(mib[0]); uint64_t size; size_t len = sizeof(size); errno = 0; if (sysctl(mib, namelen, &size, &len, 0, 0) >= 0) { totalMemory = size >> 20; error = 0; } else { dbgKrita << "sysctl(\"hw.memsize\") raised error" << strerror(errno); } #endif if (error) { warnKrita << "Cannot get the size of your RAM. Using 1 GiB by default."; } return totalMemory; } bool KisImageConfig::showAdditionalOnionSkinsSettings(bool requestDefault) const { return !requestDefault ? m_config.readEntry("showAdditionalOnionSkinsSettings", true) : true; } void KisImageConfig::setShowAdditionalOnionSkinsSettings(bool value) { m_config.writeEntry("showAdditionalOnionSkinsSettings", value); } int KisImageConfig::defaultFrameColorLabel() const { return m_config.readEntry("defaultFrameColorLabel", 0); } void KisImageConfig::setDefaultFrameColorLabel(int label) { m_config.writeEntry("defaultFrameColorLabel", label); } KisProofingConfigurationSP KisImageConfig::defaultProofingconfiguration() { KisProofingConfiguration *proofingConfig= new KisProofingConfiguration(); proofingConfig->proofingProfile = m_config.readEntry("defaultProofingProfileName", "Chemical proof"); proofingConfig->proofingModel = m_config.readEntry("defaultProofingProfileModel", "CMYKA"); proofingConfig->proofingDepth = m_config.readEntry("defaultProofingProfileDepth", "U8"); proofingConfig->intent = (KoColorConversionTransformation::Intent)m_config.readEntry("defaultProofingProfileIntent", 3); if (m_config.readEntry("defaultProofingBlackpointCompensation", true)) { proofingConfig->conversionFlags |= KoColorConversionTransformation::ConversionFlag::BlackpointCompensation; } else { proofingConfig->conversionFlags = proofingConfig->conversionFlags & ~KoColorConversionTransformation::ConversionFlag::BlackpointCompensation; } QColor def(Qt::green); m_config.readEntry("defaultProofingGamutwarning", def); KoColor col(KoColorSpaceRegistry::instance()->rgb8()); col.fromQColor(def); col.setOpacity(1.0); proofingConfig->warningColor = col; proofingConfig->adaptationState = (double)m_config.readEntry("defaultProofingAdaptationState", 1.0); return toQShared(proofingConfig); } void KisImageConfig::setDefaultProofingConfig(const KoColorSpace *proofingSpace, int proofingIntent, bool blackPointCompensation, KoColor warningColor, double adaptationState) { m_config.writeEntry("defaultProofingProfileName", proofingSpace->profile()->name()); m_config.writeEntry("defaultProofingProfileModel", proofingSpace->colorModelId().id()); m_config.writeEntry("defaultProofingProfileDepth", proofingSpace->colorDepthId().id()); m_config.writeEntry("defaultProofingProfileIntent", proofingIntent); m_config.writeEntry("defaultProofingBlackpointCompensation", blackPointCompensation); QColor c; c = warningColor.toQColor(); m_config.writeEntry("defaultProofingGamutwarning", c); m_config.writeEntry("defaultProofingAdaptationState",adaptationState); } bool KisImageConfig::useLodForColorizeMask(bool requestDefault) const { return !requestDefault ? m_config.readEntry("useLodForColorizeMask", false) : false; } void KisImageConfig::setUseLodForColorizeMask(bool value) { m_config.writeEntry("useLodForColorizeMask", value); } int KisImageConfig::maxNumberOfThreads(bool defaultValue) const { return (defaultValue ? QThread::idealThreadCount() : m_config.readEntry("maxNumberOfThreads", QThread::idealThreadCount())); } void KisImageConfig::setMaxNumberOfThreads(int value) { if (value == QThread::idealThreadCount()) { m_config.deleteEntry("maxNumberOfThreads"); } else { m_config.writeEntry("maxNumberOfThreads", value); } } int KisImageConfig::frameRenderingClones(bool defaultValue) const { const int defaultClonesCount = qMax(1, maxNumberOfThreads(defaultValue) / 2); return defaultValue ? defaultClonesCount : m_config.readEntry("frameRenderingClones", defaultClonesCount); } void KisImageConfig::setFrameRenderingClones(int value) { m_config.writeEntry("frameRenderingClones", value); } int KisImageConfig::fpsLimit(bool defaultValue) const { return defaultValue ? 100 : m_config.readEntry("fpsLimit", 100); } void KisImageConfig::setFpsLimit(int value) { m_config.writeEntry("fpsLimit", value); } + +bool KisImageConfig::useOnDiskAnimationCacheSwapping(bool defaultValue) const +{ + return defaultValue ? true : m_config.readEntry("useOnDiskAnimationCacheSwapping", true); +} + +void KisImageConfig::setUseOnDiskAnimationCacheSwapping(bool value) +{ + m_config.writeEntry("useOnDiskAnimationCacheSwapping", value); +} + +QString KisImageConfig::animationCacheDir(bool defaultValue) const +{ + return safelyGetWritableTempLocation("animation_cache", "animationCacheDir", defaultValue); +} + +void KisImageConfig::setAnimationCacheDir(const QString &value) +{ + m_config.writeEntry("animationCacheDir", value); +} + +bool KisImageConfig::useAnimationCacheFrameSizeLimit(bool defaultValue) const +{ + return defaultValue ? true : m_config.readEntry("useAnimationCacheFrameSizeLimit", true); +} + +void KisImageConfig::setUseAnimationCacheFrameSizeLimit(bool value) +{ + m_config.writeEntry("useAnimationCacheFrameSizeLimit", value); +} + +int KisImageConfig::animationCacheFrameSizeLimit(bool defaultValue) const +{ + return defaultValue ? 2500 : m_config.readEntry("animationCacheFrameSizeLimit", 2500); +} + +void KisImageConfig::setAnimationCacheFrameSizeLimit(int value) +{ + m_config.writeEntry("animationCacheFrameSizeLimit", value); +} + +bool KisImageConfig::useAnimationCacheRegionOfInterest(bool defaultValue) const +{ + return defaultValue ? true : m_config.readEntry("useAnimationCacheRegionOfInterest", true); +} + +void KisImageConfig::setUseAnimationCacheRegionOfInterest(bool value) +{ + m_config.writeEntry("useAnimationCacheRegionOfInterest", value); +} + +qreal KisImageConfig::animationCacheRegionOfInterestMargin(bool defaultValue) const +{ + return defaultValue ? 0.25 : m_config.readEntry("animationCacheRegionOfInterestMargin", 0.25); +} + +void KisImageConfig::setAnimationCacheRegionOfInterestMargin(qreal value) +{ + m_config.writeEntry("animationCacheRegionOfInterestMargin", value); +} diff --git a/libs/image/kis_image_config.h b/libs/image/kis_image_config.h index b701c1d2ab..78ba9f02b5 100644 --- a/libs/image/kis_image_config.h +++ b/libs/image/kis_image_config.h @@ -1,133 +1,153 @@ /* * Copyright (c) 2010 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_IMAGE_CONFIG_H_ #define KIS_IMAGE_CONFIG_H_ #include #include "kritaimage_export.h" #include "KisProofingConfiguration.h" #include "kis_types.h" class KRITAIMAGE_EXPORT KisImageConfig { public: KisImageConfig(bool readOnly = false); ~KisImageConfig(); bool enableProgressReporting(bool requestDefault = false) const; void setEnableProgressReporting(bool value); bool enablePerfLog(bool requestDefault = false) const; void setEnablePerfLog(bool value); qreal transformMaskOffBoundsReadArea() const; int updatePatchHeight() const; void setUpdatePatchHeight(int value); int updatePatchWidth() const; void setUpdatePatchWidth(int value); qreal maxCollectAlpha() const; qreal maxMergeAlpha() const; qreal maxMergeCollectAlpha() const; qreal schedulerBalancingRatio() const; void setSchedulerBalancingRatio(qreal value); int maxSwapSize(bool requestDefault = false) const; void setMaxSwapSize(int value); int swapSlabSize() const; void setSwapSlabSize(int value); int swapWindowSize() const; void setSwapWindowSize(int value); int tilesHardLimit() const; // MiB int tilesSoftLimit() const; // MiB int poolLimit() const; // MiB qreal memoryHardLimitPercent(bool requestDefault = false) const; // % of total RAM qreal memorySoftLimitPercent(bool requestDefault = false) const; // % of memoryHardLimitPercent() * (1 - 0.01 * memoryPoolLimitPercent()) qreal memoryPoolLimitPercent(bool requestDefault = false) const; // % of memoryHardLimitPercent() void setMemoryHardLimitPercent(qreal value); void setMemorySoftLimitPercent(qreal value); void setMemoryPoolLimitPercent(qreal value); static int totalRAM(); // MiB /** * @return a specific directory for the swapfile, if set. If not set, return an * empty QString and use the default KDE directory. */ QString swapDir(bool requestDefault = false); void setSwapDir(const QString &swapDir); int numberOfOnionSkins() const; void setNumberOfOnionSkins(int value); int onionSkinTintFactor() const; void setOnionSkinTintFactor(int value); int onionSkinOpacity(int offset) const; void setOnionSkinOpacity(int offset, int value); bool onionSkinState(int offset) const; void setOnionSkinState(int offset, bool value); QColor onionSkinTintColorBackward() const; void setOnionSkinTintColorBackward(const QColor &value); QColor onionSkinTintColorForward() const; void setOnionSkinTintColorForward(const QColor &value); bool lazyFrameCreationEnabled(bool requestDefault = false) const; void setLazyFrameCreationEnabled(bool value); bool showAdditionalOnionSkinsSettings(bool requestDefault = false) const; void setShowAdditionalOnionSkinsSettings(bool value); int defaultFrameColorLabel() const; void setDefaultFrameColorLabel(int label); KisProofingConfigurationSP defaultProofingconfiguration(); void setDefaultProofingConfig(const KoColorSpace *proofingSpace, int proofingIntent, bool blackPointCompensation, KoColor warningColor, double adaptationState); bool useLodForColorizeMask(bool requestDefault = false) const; void setUseLodForColorizeMask(bool value); int maxNumberOfThreads(bool defaultValue = false) const; void setMaxNumberOfThreads(int value); int frameRenderingClones(bool defaultValue = false) const; void setFrameRenderingClones(int value); int fpsLimit(bool defaultValue = false) const; void setFpsLimit(int value); + bool useOnDiskAnimationCacheSwapping(bool defaultValue = false) const; + void setUseOnDiskAnimationCacheSwapping(bool value); + + QString animationCacheDir(bool defaultValue = false) const; + void setAnimationCacheDir(const QString &value); + + bool useAnimationCacheFrameSizeLimit(bool defaultValue = false) const; + void setUseAnimationCacheFrameSizeLimit(bool value); + + int animationCacheFrameSizeLimit(bool defaultValue = false) const; + void setAnimationCacheFrameSizeLimit(int value); + + bool useAnimationCacheRegionOfInterest(bool defaultValue = false) const; + void setUseAnimationCacheRegionOfInterest(bool value); + + qreal animationCacheRegionOfInterestMargin(bool defaultValue = false) const; + void setAnimationCacheRegionOfInterestMargin(qreal value); + private: Q_DISABLE_COPY(KisImageConfig) + QString safelyGetWritableTempLocation(const QString &suffix, const QString &configKey, bool requestDefault) const; + private: KConfigGroup m_config; bool m_readOnly; }; #endif /* KIS_IMAGE_CONFIG_H_ */ diff --git a/libs/image/kis_layer.cc b/libs/image/kis_layer.cc index 85ce35c0b3..005a079d17 100644 --- a/libs/image/kis_layer.cc +++ b/libs/image/kis_layer.cc @@ -1,948 +1,948 @@ /* * 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 "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" class KisSafeProjection { public: KisPaintDeviceSP getDeviceLazy(KisPaintDeviceSP prototype) { QMutexLocker locker(&m_lock); if (!m_reusablePaintDevice) { m_reusablePaintDevice = new KisPaintDevice(*prototype); } if(!m_projection || *m_projection->colorSpace() != *prototype->colorSpace()) { m_projection = m_reusablePaintDevice; m_projection->makeCloneFromRough(prototype, prototype->extent()); m_projection->setProjectionDevice(true); } return m_projection; } void freeDevice() { QMutexLocker locker(&m_lock); m_projection = 0; if(m_reusablePaintDevice) { m_reusablePaintDevice->clear(); } } private: QMutex m_lock; KisPaintDeviceSP m_projection; KisPaintDeviceSP m_reusablePaintDevice; }; 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; }; struct Q_DECL_HIDDEN KisLayer::Private { KisImageWSP image; QBitArray channelFlags; KisMetaData::Store* metaDataStore; KisSafeProjection safeProjection; KisCloneLayersList clonesList; KisPSDLayerStyleSP layerStyle; KisAbstractProjectionPlaneSP layerStyleProjectionPlane; KisAbstractProjectionPlaneSP projectionPlane; KisSelectionMaskSP selectionMask; QList effectMasks; }; KisLayer::KisLayer(KisImageWSP image, const QString &name, quint8 opacity) : KisNode() , m_d(new Private) { setName(name); setOpacity(opacity); m_d->image = image; m_d->metaDataStore = new KisMetaData::Store(); m_d->projectionPlane = toQShared(new KisLayerProjectionPlane(this)); notifyChildMaskChanged(KisNodeSP()); } KisLayer::KisLayer(const KisLayer& rhs) : KisNode(rhs) , m_d(new Private()) { if (this != &rhs) { m_d->image = rhs.m_d->image; 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)); if (rhs.m_d->layerStyle) { setLayerStyle(rhs.m_d->layerStyle->clone()); } notifyChildMaskChanged(KisNodeSP()); } } KisLayer::~KisLayer() { delete m_d->metaDataStore; delete m_d; } const KoColorSpace * KisLayer::colorSpace() const { KisImageSP image = m_d->image.toStrongRef(); 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; KisAbstractProjectionPlaneSP plane = !layerStyle->isEmpty() ? KisAbstractProjectionPlaneSP(new KisLayerStyleProjectionPlane(this)) : KisAbstractProjectionPlaneSP(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("Composite Mode")), compositeOp->description()); + 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); } KisImageWSP KisLayer::image() const { return m_d->image; } void KisLayer::setImage(KisImageWSP image) { m_d->image = 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)); } KisNodeSP node = firstChild(); while (node) { KisLayerUtils::recursiveApplyNodes(node, [image] (KisNodeSP node) { node->setImage(image); }); node = node->nextSibling(); } } 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(KisNodeSP changedChildMask) { Q_UNUSED(changedChildMask); updateSelectionMask(); updateEffectMasks(); } KisSelectionMaskSP KisLayer::selectionMask() const { return m_d->selectionMask; } void KisLayer::updateSelectionMask() { KoProperties properties; properties.setProperty("active", true); properties.setProperty("visible", true); QList masks = childNodes(QStringList("KisSelectionMask"), properties); // return the first visible mask Q_FOREACH (KisNodeSP mask, masks) { if (mask) { m_d->selectionMask = dynamic_cast(mask.data()); return; } } m_d->selectionMask = KisSelectionMaskSP(); } KisSelectionSP KisLayer::selection() const { KisSelectionMaskSP mask = selectionMask(); if (mask) { return mask->selection(); } KisImageSP image = m_d->image.toStrongRef(); if (image) { return image->globalSelection(); } return KisSelectionSP(); } /////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////// const QList &KisLayer::effectMasks() const { return m_d->effectMasks; } QList KisLayer::effectMasks(KisNodeSP lastNode) const { if (lastNode.isNull()) { return effectMasks(); } else { // happens rarely. return searchEffectMasks(lastNode); } } void KisLayer::updateEffectMasks() { m_d->effectMasks = searchEffectMasks(KisNodeSP()); } QList KisLayer::searchEffectMasks(KisNodeSP lastNode) const { QList masks; if (childCount() > 0) { KoProperties properties; properties.setProperty("visible", true); QList nodes = childNodes(QStringList("KisEffectMask"), properties); Q_FOREACH (const KisNodeSP& node, nodes) { if (node == lastNode) break; KisEffectMaskSP mask = dynamic_cast(const_cast(node.data())); if (mask) masks.append(mask); } } return masks; } bool KisLayer::hasEffectMasks() const { return !m_d->effectMasks.empty(); } 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() || !originalDevice) return QRect(); if (!needProjection() && !hasEffectMasks()) { m_d->safeProjection.freeDevice(); } 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 ? m_d->layerStyleProjectionPlane : m_d->projectionPlane; } KisAbstractProjectionPlaneSP 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::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(changedChildNode); } } QRect KisLayer::incomingChangeRect(const QRect &rect) const { return rect; } QRect KisLayer::outgoingChangeRect(const QRect &rect) const { return rect; } 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_utils.cpp b/libs/image/kis_layer_utils.cpp index 4ae1b4cf26..11fc3432fa 100644 --- a/libs/image/kis_layer_utils.cpp +++ b/libs/image/kis_layer_utils.cpp @@ -1,1471 +1,1470 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_layer_utils.h" #include #include #include #include #include "kis_painter.h" #include "kis_image.h" #include "kis_node.h" #include "kis_layer.h" #include "kis_paint_layer.h" #include "kis_clone_layer.h" #include "kis_group_layer.h" #include "kis_selection.h" #include "kis_selection_mask.h" #include "kis_meta_data_merge_strategy.h" #include #include "commands/kis_image_layer_add_command.h" #include "commands/kis_image_layer_remove_command.h" #include "commands/kis_image_layer_move_command.h" #include "commands/kis_image_change_layers_command.h" #include "commands_new/kis_activate_selection_mask_command.h" #include "commands/kis_image_change_visibility_command.h" #include "kis_abstract_projection_plane.h" #include "kis_processing_applicator.h" #include "kis_image_animation_interface.h" #include "kis_keyframe_channel.h" #include "kis_command_utils.h" #include "kis_processing_applicator.h" #include "commands_new/kis_change_projection_color_command.h" #include "kis_layer_properties_icons.h" #include "lazybrush/kis_colorize_mask.h" #include "commands/kis_node_property_list_command.h" #include "commands/kis_node_compositeop_command.h" #include #include "krita_utils.h" namespace KisLayerUtils { void fetchSelectionMasks(KisNodeList mergedNodes, QVector &selectionMasks) { foreach (KisNodeSP node, mergedNodes) { KisLayerSP layer = qobject_cast(node.data()); KisSelectionMaskSP mask; if (layer && (mask = layer->selectionMask())) { selectionMasks.append(mask); } } } struct MergeDownInfoBase { MergeDownInfoBase(KisImageSP _image) : image(_image), storage(new SwitchFrameCommand::SharedStorage()) { } virtual ~MergeDownInfoBase() {} KisImageWSP image; QVector selectionMasks; KisNodeSP dstNode; SwitchFrameCommand::SharedStorageSP storage; QSet frames; bool useInTimeline = false; bool enableOnionSkins = false; virtual KisNodeList allSrcNodes() = 0; KisLayerSP dstLayer() { return qobject_cast(dstNode.data()); } }; struct MergeDownInfo : public MergeDownInfoBase { MergeDownInfo(KisImageSP _image, KisLayerSP _prevLayer, KisLayerSP _currLayer) : MergeDownInfoBase(_image), prevLayer(_prevLayer), currLayer(_currLayer) { frames = fetchLayerFramesRecursive(prevLayer) | fetchLayerFramesRecursive(currLayer); useInTimeline = prevLayer->useInTimeline() || currLayer->useInTimeline(); const KisPaintLayer *paintLayer = qobject_cast(currLayer.data()); if (paintLayer) enableOnionSkins |= paintLayer->onionSkinEnabled(); paintLayer = qobject_cast(prevLayer.data()); if (paintLayer) enableOnionSkins |= paintLayer->onionSkinEnabled(); } KisLayerSP prevLayer; KisLayerSP currLayer; KisNodeList allSrcNodes() override { KisNodeList mergedNodes; mergedNodes << currLayer; mergedNodes << prevLayer; return mergedNodes; } }; struct MergeMultipleInfo : public MergeDownInfoBase { MergeMultipleInfo(KisImageSP _image, KisNodeList _mergedNodes) : MergeDownInfoBase(_image), mergedNodes(_mergedNodes) { foreach (KisNodeSP node, mergedNodes) { frames |= fetchLayerFramesRecursive(node); useInTimeline |= node->useInTimeline(); const KisPaintLayer *paintLayer = qobject_cast(node.data()); if (paintLayer) { enableOnionSkins |= paintLayer->onionSkinEnabled(); } } } KisNodeList mergedNodes; bool nodesCompositingVaries = false; KisNodeList allSrcNodes() override { return mergedNodes; } }; typedef QSharedPointer MergeDownInfoBaseSP; typedef QSharedPointer MergeDownInfoSP; typedef QSharedPointer MergeMultipleInfoSP; struct FillSelectionMasks : public KUndo2Command { FillSelectionMasks(MergeDownInfoBaseSP info) : m_info(info) {} void redo() override { fetchSelectionMasks(m_info->allSrcNodes(), m_info->selectionMasks); } private: MergeDownInfoBaseSP m_info; }; struct DisableColorizeKeyStrokes : public KisCommandUtils::AggregateCommand { DisableColorizeKeyStrokes(MergeDownInfoBaseSP info) : m_info(info) {} void populateChildCommands() override { Q_FOREACH (KisNodeSP node, m_info->allSrcNodes()) { recursiveApplyNodes(node, [this] (KisNodeSP node) { if (dynamic_cast(node.data()) && KisLayerPropertiesIcons::nodeProperty(node, KisLayerPropertiesIcons::colorizeEditKeyStrokes, true).toBool()) { KisBaseNode::PropertyList props = node->sectionModelProperties(); KisLayerPropertiesIcons::setNodeProperty(&props, KisLayerPropertiesIcons::colorizeEditKeyStrokes, false); addCommand(new KisNodePropertyListCommand(node, props)); } }); } } private: MergeDownInfoBaseSP m_info; }; struct DisableOnionSkins : public KisCommandUtils::AggregateCommand { DisableOnionSkins(MergeDownInfoBaseSP info) : m_info(info) {} void populateChildCommands() override { Q_FOREACH (KisNodeSP node, m_info->allSrcNodes()) { recursiveApplyNodes(node, [this] (KisNodeSP node) { if (KisLayerPropertiesIcons::nodeProperty(node, KisLayerPropertiesIcons::onionSkins, false).toBool()) { KisBaseNode::PropertyList props = node->sectionModelProperties(); KisLayerPropertiesIcons::setNodeProperty(&props, KisLayerPropertiesIcons::onionSkins, false); addCommand(new KisNodePropertyListCommand(node, props)); } }); } } private: MergeDownInfoBaseSP m_info; }; struct DisableExtraCompositing : public KisCommandUtils::AggregateCommand { DisableExtraCompositing(MergeMultipleInfoSP info) : m_info(info) {} void populateChildCommands() override { /** * We disable extra compositing only in case all the layers have * the same compositing properties, therefore, we can just sum them using * Normal blend mode */ if (m_info->nodesCompositingVaries) return; // we should disable dirty requests on **redo only**, otherwise // the state of the layers will not be recovered on undo m_info->image->disableDirtyRequests(); Q_FOREACH (KisNodeSP node, m_info->allSrcNodes()) { if (node->compositeOpId() != COMPOSITE_OVER) { addCommand(new KisNodeCompositeOpCommand(node, node->compositeOpId(), COMPOSITE_OVER)); } if (KisLayerPropertiesIcons::nodeProperty(node, KisLayerPropertiesIcons::inheritAlpha, false).toBool()) { KisBaseNode::PropertyList props = node->sectionModelProperties(); KisLayerPropertiesIcons::setNodeProperty(&props, KisLayerPropertiesIcons::inheritAlpha, false); addCommand(new KisNodePropertyListCommand(node, props)); } } m_info->image->enableDirtyRequests(); } private: MergeMultipleInfoSP m_info; }; struct DisablePassThroughForHeadsOnly : public KisCommandUtils::AggregateCommand { DisablePassThroughForHeadsOnly(MergeDownInfoBaseSP info, bool skipIfDstIsGroup = false) : m_info(info), m_skipIfDstIsGroup(skipIfDstIsGroup) { } void populateChildCommands() override { if (m_skipIfDstIsGroup && m_info->dstLayer() && m_info->dstLayer()->inherits("KisGroupLayer")) { return; } Q_FOREACH (KisNodeSP node, m_info->allSrcNodes()) { if (KisLayerPropertiesIcons::nodeProperty(node, KisLayerPropertiesIcons::passThrough, false).toBool()) { KisBaseNode::PropertyList props = node->sectionModelProperties(); KisLayerPropertiesIcons::setNodeProperty(&props, KisLayerPropertiesIcons::passThrough, false); addCommand(new KisNodePropertyListCommand(node, props)); } } } private: MergeDownInfoBaseSP m_info; bool m_skipIfDstIsGroup; }; struct RefreshHiddenAreas : public KUndo2Command { RefreshHiddenAreas(MergeDownInfoBaseSP info) : m_info(info) {} void redo() override { KisImageAnimationInterface *interface = m_info->image->animationInterface(); const QRect preparedRect = !interface->externalFrameActive() ? m_info->image->bounds() : QRect(); foreach (KisNodeSP node, m_info->allSrcNodes()) { refreshHiddenAreaAsync(node, preparedRect); } } private: QRect realNodeExactBounds(KisNodeSP rootNode, QRect currentRect = QRect()) { KisNodeSP node = rootNode->firstChild(); while(node) { currentRect |= realNodeExactBounds(node, currentRect); node = node->nextSibling(); } // TODO: it would be better to count up changeRect inside // node's extent() method currentRect |= rootNode->projectionPlane()->changeRect(rootNode->exactBounds()); return currentRect; } void refreshHiddenAreaAsync(KisNodeSP rootNode, const QRect &preparedArea) { QRect realNodeRect = realNodeExactBounds(rootNode); if (!preparedArea.contains(realNodeRect)) { QRegion dirtyRegion = realNodeRect; dirtyRegion -= preparedArea; foreach(const QRect &rc, dirtyRegion.rects()) { m_info->image->refreshGraphAsync(rootNode, rc, realNodeRect); } } } private: MergeDownInfoBaseSP m_info; }; struct RefreshDelayedUpdateLayers : public KUndo2Command { RefreshDelayedUpdateLayers(MergeDownInfoBaseSP info) : m_info(info) {} void redo() override { foreach (KisNodeSP node, m_info->allSrcNodes()) { forceAllDelayedNodesUpdate(node); } } private: MergeDownInfoBaseSP m_info; }; struct KeepMergedNodesSelected : public KisCommandUtils::AggregateCommand { KeepMergedNodesSelected(MergeDownInfoSP info, bool finalizing) : m_singleInfo(info), m_finalizing(finalizing) {} KeepMergedNodesSelected(MergeMultipleInfoSP info, KisNodeSP putAfter, bool finalizing) : m_multipleInfo(info), m_finalizing(finalizing), m_putAfter(putAfter) {} void populateChildCommands() override { KisNodeSP prevNode; KisNodeSP nextNode; KisNodeList prevSelection; KisNodeList nextSelection; KisImageSP image; if (m_singleInfo) { prevNode = m_singleInfo->currLayer; nextNode = m_singleInfo->dstNode; image = m_singleInfo->image; } else if (m_multipleInfo) { prevNode = m_putAfter; nextNode = m_multipleInfo->dstNode; prevSelection = m_multipleInfo->allSrcNodes(); image = m_multipleInfo->image; } if (!m_finalizing) { addCommand(new KeepNodesSelectedCommand(prevSelection, KisNodeList(), prevNode, KisNodeSP(), image, false)); } else { addCommand(new KeepNodesSelectedCommand(KisNodeList(), nextSelection, KisNodeSP(), nextNode, image, true)); } } private: MergeDownInfoSP m_singleInfo; MergeMultipleInfoSP m_multipleInfo; bool m_finalizing; KisNodeSP m_putAfter; }; struct CreateMergedLayer : public KisCommandUtils::AggregateCommand { CreateMergedLayer(MergeDownInfoSP info) : m_info(info) {} void populateChildCommands() override { // actual merging done by KisLayer::createMergedLayer (or specialized descendant) m_info->dstNode = m_info->currLayer->createMergedLayerTemplate(m_info->prevLayer); if (m_info->frames.size() > 0) { m_info->dstNode->enableAnimation(); m_info->dstNode->getKeyframeChannel(KisKeyframeChannel::Content.id(), true); } m_info->dstNode->setUseInTimeline(m_info->useInTimeline); KisPaintLayer *dstPaintLayer = qobject_cast(m_info->dstNode.data()); if (dstPaintLayer) { dstPaintLayer->setOnionSkinEnabled(m_info->enableOnionSkins); } } private: MergeDownInfoSP m_info; }; struct CreateMergedLayerMultiple : public KisCommandUtils::AggregateCommand { CreateMergedLayerMultiple(MergeMultipleInfoSP info, const QString name = QString() ) : m_info(info), m_name(name) {} void populateChildCommands() override { QString mergedLayerName; if (m_name.isEmpty()){ const QString mergedLayerSuffix = i18n("Merged"); mergedLayerName = m_info->mergedNodes.first()->name(); if (!mergedLayerName.endsWith(mergedLayerSuffix)) { mergedLayerName = QString("%1 %2") .arg(mergedLayerName).arg(mergedLayerSuffix); } } else { mergedLayerName = m_name; } KisPaintLayer *dstPaintLayer = new KisPaintLayer(m_info->image, mergedLayerName, OPACITY_OPAQUE_U8); m_info->dstNode = dstPaintLayer; if (m_info->frames.size() > 0) { m_info->dstNode->enableAnimation(); m_info->dstNode->getKeyframeChannel(KisKeyframeChannel::Content.id(), true); } auto channelFlagsLazy = [](KisNodeSP node) { KisLayer *layer = dynamic_cast(node.data()); return layer ? layer->channelFlags() : QBitArray(); }; QString compositeOpId; QBitArray channelFlags; bool compositionVaries = false; bool isFirstCycle = true; foreach (KisNodeSP node, m_info->allSrcNodes()) { if (isFirstCycle) { compositeOpId = node->compositeOpId(); channelFlags = channelFlagsLazy(node); isFirstCycle = false; } else if (compositeOpId != node->compositeOpId() || channelFlags != channelFlagsLazy(node)) { compositionVaries = true; break; } KisLayerSP layer = qobject_cast(node.data()); if (layer && layer->layerStyle()) { compositionVaries = true; break; } } if (!compositionVaries) { if (!compositeOpId.isEmpty()) { m_info->dstNode->setCompositeOpId(compositeOpId); } if (m_info->dstLayer() && !channelFlags.isEmpty()) { m_info->dstLayer()->setChannelFlags(channelFlags); } } m_info->nodesCompositingVaries = compositionVaries; m_info->dstNode->setUseInTimeline(m_info->useInTimeline); dstPaintLayer->setOnionSkinEnabled(m_info->enableOnionSkins); } private: MergeMultipleInfoSP m_info; QString m_name; }; struct MergeLayers : public KisCommandUtils::AggregateCommand { MergeLayers(MergeDownInfoSP info) : m_info(info) {} void populateChildCommands() override { // actual merging done by KisLayer::createMergedLayer (or specialized descendant) m_info->currLayer->fillMergedLayerTemplate(m_info->dstLayer(), m_info->prevLayer); } private: MergeDownInfoSP m_info; }; struct MergeLayersMultiple : public KisCommandUtils::AggregateCommand { MergeLayersMultiple(MergeMultipleInfoSP info) : m_info(info) {} void populateChildCommands() override { KisPainter gc(m_info->dstNode->paintDevice()); foreach (KisNodeSP node, m_info->allSrcNodes()) { QRect rc = node->exactBounds() | m_info->image->bounds(); node->projectionPlane()->apply(&gc, rc); } } private: MergeMultipleInfoSP m_info; }; struct MergeMetaData : public KUndo2Command { MergeMetaData(MergeDownInfoSP info, const KisMetaData::MergeStrategy* strategy) : m_info(info), m_strategy(strategy) {} void redo() override { QRect layerProjectionExtent = m_info->currLayer->projection()->extent(); QRect prevLayerProjectionExtent = m_info->prevLayer->projection()->extent(); int prevLayerArea = prevLayerProjectionExtent.width() * prevLayerProjectionExtent.height(); int layerArea = layerProjectionExtent.width() * layerProjectionExtent.height(); QList scores; double norm = qMax(prevLayerArea, layerArea); scores.append(prevLayerArea / norm); scores.append(layerArea / norm); QList srcs; srcs.append(m_info->prevLayer->metaData()); srcs.append(m_info->currLayer->metaData()); m_strategy->merge(m_info->dstLayer()->metaData(), srcs, scores); } private: MergeDownInfoSP m_info; const KisMetaData::MergeStrategy *m_strategy; }; KeepNodesSelectedCommand::KeepNodesSelectedCommand(const KisNodeList &selectedBefore, const KisNodeList &selectedAfter, KisNodeSP activeBefore, KisNodeSP activeAfter, KisImageSP image, bool finalize, KUndo2Command *parent) : FlipFlopCommand(finalize, parent), m_selectedBefore(selectedBefore), m_selectedAfter(selectedAfter), m_activeBefore(activeBefore), m_activeAfter(activeAfter), m_image(image) { } void KeepNodesSelectedCommand::end() { KisImageSignalType type; if (isFinalizing()) { type = ComplexNodeReselectionSignal(m_activeAfter, m_selectedAfter); } else { type = ComplexNodeReselectionSignal(m_activeBefore, m_selectedBefore); } m_image->signalRouter()->emitNotification(type); } KisLayerSP constructDefaultLayer(KisImageSP image) { return new KisPaintLayer(image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8, image->colorSpace()); } RemoveNodeHelper::~RemoveNodeHelper() { } /** * The removal of two nodes in one go may be a bit tricky, because one * of them may be the clone of another. If we remove the source of a * clone layer, it will reincarnate into a paint layer. In this case * the pointer to the second layer will be lost. * * That's why we need to care about the order of the nodes removal: * the clone --- first, the source --- last. */ void RemoveNodeHelper::safeRemoveMultipleNodes(KisNodeList nodes, KisImageSP image) { const bool lastLayer = scanForLastLayer(image, nodes); while (!nodes.isEmpty()) { KisNodeList::iterator it = nodes.begin(); while (it != nodes.end()) { if (!checkIsSourceForClone(*it, nodes)) { KisNodeSP node = *it; addCommandImpl(new KisImageLayerRemoveCommand(image, node, false, true)); it = nodes.erase(it); } else { ++it; } } } if (lastLayer) { KisLayerSP newLayer = constructDefaultLayer(image); addCommandImpl(new KisImageLayerAddCommand(image, newLayer, image->root(), KisNodeSP(), false, false)); } } bool RemoveNodeHelper::checkIsSourceForClone(KisNodeSP src, const KisNodeList &nodes) { foreach (KisNodeSP node, nodes) { if (node == src) continue; KisCloneLayer *clone = dynamic_cast(node.data()); if (clone && KisNodeSP(clone->copyFrom()) == src) { return true; } } return false; } bool RemoveNodeHelper::scanForLastLayer(KisImageWSP image, KisNodeList nodesToRemove) { bool removeLayers = false; Q_FOREACH(KisNodeSP nodeToRemove, nodesToRemove) { if (qobject_cast(nodeToRemove.data())) { removeLayers = true; break; } } if (!removeLayers) return false; bool lastLayer = true; KisNodeSP node = image->root()->firstChild(); while (node) { if (!nodesToRemove.contains(node) && qobject_cast(node.data())) { lastLayer = false; break; } node = node->nextSibling(); } return lastLayer; } SimpleRemoveLayers::SimpleRemoveLayers(const KisNodeList &nodes, KisImageSP image) : m_nodes(nodes), m_image(image) { } void SimpleRemoveLayers::populateChildCommands() { if (m_nodes.isEmpty()) return; safeRemoveMultipleNodes(m_nodes, m_image); } void SimpleRemoveLayers::addCommandImpl(KUndo2Command *cmd) { addCommand(cmd); } struct InsertNode : public KisCommandUtils::AggregateCommand { InsertNode(MergeDownInfoBaseSP info, KisNodeSP putAfter) : m_info(info), m_putAfter(putAfter) {} void populateChildCommands() override { addCommand(new KisImageLayerAddCommand(m_info->image, m_info->dstNode, m_putAfter->parent(), m_putAfter, true, false)); } private: virtual void addCommandImpl(KUndo2Command *cmd) { addCommand(cmd); } private: MergeDownInfoBaseSP m_info; KisNodeSP m_putAfter; }; struct CleanUpNodes : private RemoveNodeHelper, public KisCommandUtils::AggregateCommand { CleanUpNodes(MergeDownInfoBaseSP info, KisNodeSP putAfter) : m_info(info), m_putAfter(putAfter) {} static void findPerfectParent(KisNodeList nodesToDelete, KisNodeSP &putAfter, KisNodeSP &parent) { if (!putAfter) { putAfter = nodesToDelete.last(); } // Add the new merged node on top of the active node -- checking // whether the parent is going to be deleted parent = putAfter->parent(); while (parent && nodesToDelete.contains(parent)) { parent = parent->parent(); } } void populateChildCommands() override { KisNodeList nodesToDelete = m_info->allSrcNodes(); KisNodeSP parent; findPerfectParent(nodesToDelete, m_putAfter, parent); if (!parent) { KisNodeSP oldRoot = m_info->image->root(); KisNodeSP newRoot(new KisGroupLayer(m_info->image, "root", OPACITY_OPAQUE_U8)); addCommand(new KisImageLayerAddCommand(m_info->image, m_info->dstNode, newRoot, KisNodeSP(), true, false)); addCommand(new KisImageChangeLayersCommand(m_info->image, oldRoot, newRoot)); } else { if (parent == m_putAfter->parent()) { addCommand(new KisImageLayerAddCommand(m_info->image, m_info->dstNode, parent, m_putAfter, true, false)); } else { addCommand(new KisImageLayerAddCommand(m_info->image, m_info->dstNode, parent, parent->lastChild(), true, false)); } /** * We can merge selection masks, in this case dstLayer is not defined! */ if (m_info->dstLayer()) { reparentSelectionMasks(m_info->image, m_info->dstLayer(), m_info->selectionMasks); } KisNodeList safeNodesToDelete = m_info->allSrcNodes(); for (KisNodeList::iterator it = safeNodesToDelete.begin(); it != safeNodesToDelete.end(); ++it) { KisNodeSP node = *it; if (node->userLocked() && node->visible()) { addCommand(new KisImageChangeVisibilityCommand(false, node)); } } KritaUtils::filterContainer(safeNodesToDelete, [this](KisNodeSP node) { return !node->userLocked(); }); safeRemoveMultipleNodes(safeNodesToDelete, m_info->image); } } private: void addCommandImpl(KUndo2Command *cmd) override { addCommand(cmd); } void reparentSelectionMasks(KisImageSP image, KisLayerSP newLayer, const QVector &selectionMasks) { KIS_SAFE_ASSERT_RECOVER_RETURN(newLayer); foreach (KisSelectionMaskSP mask, selectionMasks) { addCommand(new KisImageLayerMoveCommand(image, mask, newLayer, newLayer->lastChild())); addCommand(new KisActivateSelectionMaskCommand(mask, false)); } } private: MergeDownInfoBaseSP m_info; KisNodeSP m_putAfter; }; SwitchFrameCommand::SharedStorage::~SharedStorage() { } SwitchFrameCommand::SwitchFrameCommand(KisImageSP image, int time, bool finalize, SharedStorageSP storage) : FlipFlopCommand(finalize), m_image(image), m_newTime(time), m_storage(storage) {} SwitchFrameCommand::~SwitchFrameCommand() {} void SwitchFrameCommand::init() { KisImageAnimationInterface *interface = m_image->animationInterface(); const int currentTime = interface->currentTime(); if (currentTime == m_newTime) { m_storage->value = m_newTime; return; } interface->image()->disableUIUpdates(); interface->saveAndResetCurrentTime(m_newTime, &m_storage->value); } void SwitchFrameCommand::end() { KisImageAnimationInterface *interface = m_image->animationInterface(); const int currentTime = interface->currentTime(); if (currentTime == m_storage->value) { return; } interface->restoreCurrentTime(&m_storage->value); interface->image()->enableUIUpdates(); } struct AddNewFrame : public KisCommandUtils::AggregateCommand { AddNewFrame(MergeDownInfoBaseSP info, int frame) : m_info(info), m_frame(frame) {} void populateChildCommands() override { KUndo2Command *cmd = new KisCommandUtils::SkipFirstRedoWrapper(); KisKeyframeChannel *channel = m_info->dstNode->getKeyframeChannel(KisKeyframeChannel::Content.id()); KisKeyframeSP keyframe = channel->addKeyframe(m_frame, cmd); applyKeyframeColorLabel(keyframe); addCommand(cmd); } void applyKeyframeColorLabel(KisKeyframeSP dstKeyframe) { Q_FOREACH(KisNodeSP srcNode, m_info->allSrcNodes()) { Q_FOREACH(KisKeyframeChannel *channel, srcNode->keyframeChannels().values()) { KisKeyframeSP keyframe = channel->keyframeAt(m_frame); if (!keyframe.isNull() && keyframe->colorLabel() != 0) { dstKeyframe->setColorLabel(keyframe->colorLabel()); return; } } } dstKeyframe->setColorLabel(0); } private: MergeDownInfoBaseSP m_info; int m_frame; }; QSet fetchLayerFrames(KisNodeSP node) { KisKeyframeChannel *channel = node->getKeyframeChannel(KisKeyframeChannel::Content.id()); if (!channel) return QSet(); return channel->allKeyframeIds(); } QSet fetchLayerFramesRecursive(KisNodeSP rootNode) { QSet frames = fetchLayerFrames(rootNode); KisNodeSP node = rootNode->firstChild(); while(node) { frames |= fetchLayerFramesRecursive(node); node = node->nextSibling(); } return frames; } void updateFrameJobs(FrameJobs *jobs, KisNodeSP node) { QSet frames = fetchLayerFrames(node); if (frames.isEmpty()) { (*jobs)[0].insert(node); } else { foreach (int frame, frames) { (*jobs)[frame].insert(node); } } } void updateFrameJobsRecursive(FrameJobs *jobs, KisNodeSP rootNode) { updateFrameJobs(jobs, rootNode); KisNodeSP node = rootNode->firstChild(); while(node) { updateFrameJobsRecursive(jobs, node); node = node->nextSibling(); } } void mergeDown(KisImageSP image, KisLayerSP layer, const KisMetaData::MergeStrategy* strategy) { if (!layer->prevSibling()) return; // XXX: this breaks if we allow free mixing of masks and layers KisLayerSP prevLayer = qobject_cast(layer->prevSibling().data()); if (!prevLayer) return; if (!layer->visible() && !prevLayer->visible()) { return; } KisImageSignalVector emitSignals; emitSignals << ModifiedSignal; KisProcessingApplicator applicator(image, 0, KisProcessingApplicator::NONE, emitSignals, kundo2_i18n("Merge Down")); if (layer->visible() && prevLayer->visible()) { MergeDownInfoSP info(new MergeDownInfo(image, prevLayer, layer)); // disable key strokes on all colorize masks, all onion skins on // paint layers and wait until update is finished with a barrier applicator.applyCommand(new DisableColorizeKeyStrokes(info)); applicator.applyCommand(new DisableOnionSkins(info)); applicator.applyCommand(new KUndo2Command(), KisStrokeJobData::BARRIER); applicator.applyCommand(new KeepMergedNodesSelected(info, false)); applicator.applyCommand(new FillSelectionMasks(info)); applicator.applyCommand(new CreateMergedLayer(info), KisStrokeJobData::BARRIER); // NOTE: shape layer may have emitted spontaneous jobs during layer creation, // wait for them to complete! applicator.applyCommand(new RefreshDelayedUpdateLayers(info), KisStrokeJobData::BARRIER); applicator.applyCommand(new KUndo2Command(), KisStrokeJobData::BARRIER); // in two-layer mode we disable pass trhough only when the destination layer // is not a group layer applicator.applyCommand(new DisablePassThroughForHeadsOnly(info, true)); applicator.applyCommand(new KUndo2Command(), KisStrokeJobData::BARRIER); if (info->frames.size() > 0) { foreach (int frame, info->frames) { applicator.applyCommand(new SwitchFrameCommand(info->image, frame, false, info->storage)); applicator.applyCommand(new AddNewFrame(info, frame)); applicator.applyCommand(new RefreshHiddenAreas(info)); applicator.applyCommand(new RefreshDelayedUpdateLayers(info), KisStrokeJobData::BARRIER); applicator.applyCommand(new MergeLayers(info), KisStrokeJobData::BARRIER); applicator.applyCommand(new SwitchFrameCommand(info->image, frame, true, info->storage)); } } else { applicator.applyCommand(new RefreshHiddenAreas(info)); applicator.applyCommand(new RefreshDelayedUpdateLayers(info), KisStrokeJobData::BARRIER); applicator.applyCommand(new MergeLayers(info), KisStrokeJobData::BARRIER); } applicator.applyCommand(new MergeMetaData(info, strategy), KisStrokeJobData::BARRIER); applicator.applyCommand(new CleanUpNodes(info, layer), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); applicator.applyCommand(new KeepMergedNodesSelected(info, true)); } else if (layer->visible()) { applicator.applyCommand(new KeepNodesSelectedCommand(KisNodeList(), KisNodeList(), layer, KisNodeSP(), image, false)); applicator.applyCommand( new SimpleRemoveLayers(KisNodeList() << prevLayer, image), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); applicator.applyCommand(new KeepNodesSelectedCommand(KisNodeList(), KisNodeList(), KisNodeSP(), layer, image, true)); } else if (prevLayer->visible()) { applicator.applyCommand(new KeepNodesSelectedCommand(KisNodeList(), KisNodeList(), layer, KisNodeSP(), image, false)); applicator.applyCommand( new SimpleRemoveLayers(KisNodeList() << layer, image), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); applicator.applyCommand(new KeepNodesSelectedCommand(KisNodeList(), KisNodeList(), KisNodeSP(), prevLayer, image, true)); } applicator.end(); } bool checkIsChildOf(KisNodeSP node, const KisNodeList &parents) { KisNodeList nodeParents; KisNodeSP parent = node->parent(); while (parent) { nodeParents << parent; parent = parent->parent(); } foreach(KisNodeSP perspectiveParent, parents) { if (nodeParents.contains(perspectiveParent)) { return true; } } return false; } bool checkIsCloneOf(KisNodeSP node, const KisNodeList &nodes) { bool result = false; KisCloneLayer *clone = dynamic_cast(node.data()); if (clone) { KisNodeSP cloneSource = KisNodeSP(clone->copyFrom()); Q_FOREACH(KisNodeSP subtree, nodes) { result = recursiveFindNode(subtree, [cloneSource](KisNodeSP node) -> bool { return node == cloneSource; }); if (!result) { result = checkIsCloneOf(cloneSource, nodes); } if (result) { break; } } } return result; } void filterMergableNodes(KisNodeList &nodes, bool allowMasks) { KisNodeList::iterator it = nodes.begin(); while (it != nodes.end()) { if ((!allowMasks && !qobject_cast(it->data())) || checkIsChildOf(*it, nodes)) { - - qDebug() << "Skipping node" << ppVar((*it)->name()); + //qDebug() << "Skipping node" << ppVar((*it)->name()); it = nodes.erase(it); } else { ++it; } } } void sortMergableNodes(KisNodeSP root, KisNodeList &inputNodes, KisNodeList &outputNodes) { KisNodeList::iterator it = std::find(inputNodes.begin(), inputNodes.end(), root); if (it != inputNodes.end()) { outputNodes << *it; inputNodes.erase(it); } if (inputNodes.isEmpty()) { return; } KisNodeSP child = root->firstChild(); while (child) { sortMergableNodes(child, inputNodes, outputNodes); child = child->nextSibling(); } /** * By the end of recursion \p inputNodes must be empty */ KIS_ASSERT_RECOVER_NOOP(root->parent() || inputNodes.isEmpty()); } KisNodeList sortMergableNodes(KisNodeSP root, KisNodeList nodes) { KisNodeList result; sortMergableNodes(root, nodes, result); return result; } KisNodeList sortAndFilterMergableInternalNodes(KisNodeList nodes, bool allowMasks) { KIS_ASSERT_RECOVER(!nodes.isEmpty()) { return nodes; } KisNodeSP root; Q_FOREACH(KisNodeSP node, nodes) { KisNodeSP localRoot = node; while (localRoot->parent()) { localRoot = localRoot->parent(); } if (!root) { root = localRoot; } KIS_ASSERT_RECOVER(root == localRoot) { return nodes; } } KisNodeList result; sortMergableNodes(root, nodes, result); filterMergableNodes(result, allowMasks); return result; } void addCopyOfNameTag(KisNodeSP node) { const QString prefix = i18n("Copy of"); QString newName = node->name(); if (!newName.startsWith(prefix)) { newName = QString("%1 %2").arg(prefix).arg(newName); node->setName(newName); } } KisNodeList findNodesWithProps(KisNodeSP root, const KoProperties &props, bool excludeRoot) { KisNodeList nodes; if ((!excludeRoot || root->parent()) && root->check(props)) { nodes << root; } KisNodeSP node = root->firstChild(); while (node) { nodes += findNodesWithProps(node, props, excludeRoot); node = node->nextSibling(); } return nodes; } KisNodeList filterInvisibleNodes(const KisNodeList &nodes, KisNodeList *invisibleNodes, KisNodeSP *putAfter) { KIS_ASSERT_RECOVER(invisibleNodes) { return nodes; } KIS_ASSERT_RECOVER(putAfter) { return nodes; } KisNodeList visibleNodes; int putAfterIndex = -1; Q_FOREACH(KisNodeSP node, nodes) { if (node->visible() || node->userLocked()) { visibleNodes << node; } else { *invisibleNodes << node; if (node == *putAfter) { putAfterIndex = visibleNodes.size() - 1; } } } if (!visibleNodes.isEmpty() && putAfterIndex >= 0) { putAfterIndex = qBound(0, putAfterIndex, visibleNodes.size() - 1); *putAfter = visibleNodes[putAfterIndex]; } return visibleNodes; } void changeImageDefaultProjectionColor(KisImageSP image, const KoColor &color) { KisImageSignalVector emitSignals; emitSignals << ModifiedSignal; KisProcessingApplicator applicator(image, image->root(), KisProcessingApplicator::RECURSIVE, emitSignals, kundo2_i18n("Change projection color"), 0, 142857 + 1); applicator.applyCommand(new KisChangeProjectionColorCommand(image, color), KisStrokeJobData::BARRIER, KisStrokeJobData::EXCLUSIVE); applicator.end(); } void mergeMultipleLayersImpl(KisImageSP image, KisNodeList mergedNodes, KisNodeSP putAfter, bool flattenSingleLayer, const KUndo2MagicString &actionName, bool cleanupNodes = true, const QString layerName = QString()) { if (!putAfter) { putAfter = mergedNodes.first(); } filterMergableNodes(mergedNodes); { KisNodeList tempNodes; std::swap(mergedNodes, tempNodes); sortMergableNodes(image->root(), tempNodes, mergedNodes); } if (mergedNodes.size() <= 1 && (!flattenSingleLayer && mergedNodes.size() == 1)) return; KisImageSignalVector emitSignals; emitSignals << ModifiedSignal; emitSignals << ComplexNodeReselectionSignal(KisNodeSP(), KisNodeList(), KisNodeSP(), mergedNodes); KisProcessingApplicator applicator(image, 0, KisProcessingApplicator::NONE, emitSignals, actionName); KisNodeList originalNodes = mergedNodes; KisNodeList invisibleNodes; mergedNodes = filterInvisibleNodes(originalNodes, &invisibleNodes, &putAfter); if (!invisibleNodes.isEmpty()) { applicator.applyCommand( new SimpleRemoveLayers(invisibleNodes, image), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); } if (mergedNodes.size() > 1 || invisibleNodes.isEmpty()) { MergeMultipleInfoSP info(new MergeMultipleInfo(image, mergedNodes)); // disable key strokes on all colorize masks, all onion skins on // paint layers and wait until update is finished with a barrier applicator.applyCommand(new DisableColorizeKeyStrokes(info)); applicator.applyCommand(new DisableOnionSkins(info)); applicator.applyCommand(new DisablePassThroughForHeadsOnly(info)); applicator.applyCommand(new KUndo2Command(), KisStrokeJobData::BARRIER); applicator.applyCommand(new KeepMergedNodesSelected(info, putAfter, false)); applicator.applyCommand(new FillSelectionMasks(info)); applicator.applyCommand(new CreateMergedLayerMultiple(info, layerName), KisStrokeJobData::BARRIER); applicator.applyCommand(new DisableExtraCompositing(info)); applicator.applyCommand(new KUndo2Command(), KisStrokeJobData::BARRIER); if (info->frames.size() > 0) { foreach (int frame, info->frames) { applicator.applyCommand(new SwitchFrameCommand(info->image, frame, false, info->storage)); applicator.applyCommand(new AddNewFrame(info, frame)); applicator.applyCommand(new RefreshHiddenAreas(info)); applicator.applyCommand(new RefreshDelayedUpdateLayers(info), KisStrokeJobData::BARRIER); applicator.applyCommand(new MergeLayersMultiple(info), KisStrokeJobData::BARRIER); applicator.applyCommand(new SwitchFrameCommand(info->image, frame, true, info->storage)); } } else { applicator.applyCommand(new RefreshHiddenAreas(info)); applicator.applyCommand(new RefreshDelayedUpdateLayers(info), KisStrokeJobData::BARRIER); applicator.applyCommand(new MergeLayersMultiple(info), KisStrokeJobData::BARRIER); } //applicator.applyCommand(new MergeMetaData(info, strategy), KisStrokeJobData::BARRIER); if (cleanupNodes){ applicator.applyCommand(new CleanUpNodes(info, putAfter), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); } else { applicator.applyCommand(new InsertNode(info, putAfter), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); } applicator.applyCommand(new KeepMergedNodesSelected(info, putAfter, true)); } applicator.end(); } void mergeMultipleLayers(KisImageSP image, KisNodeList mergedNodes, KisNodeSP putAfter) { mergeMultipleLayersImpl(image, mergedNodes, putAfter, false, kundo2_i18n("Merge Selected Nodes")); } void newLayerFromVisible(KisImageSP image, KisNodeSP putAfter) { KisNodeList mergedNodes; mergedNodes << image->root(); mergeMultipleLayersImpl(image, mergedNodes, putAfter, true, kundo2_i18n("New From Visible"), false, i18nc("New layer created from all the visible layers", "Visible")); } struct MergeSelectionMasks : public KisCommandUtils::AggregateCommand { MergeSelectionMasks(MergeDownInfoBaseSP info, KisNodeSP putAfter) : m_info(info), m_putAfter(putAfter){} void populateChildCommands() override { KisNodeSP parent; CleanUpNodes::findPerfectParent(m_info->allSrcNodes(), m_putAfter, parent); KisLayerSP parentLayer; do { parentLayer = qobject_cast(parent.data()); parent = parent->parent(); } while(!parentLayer && parent); KisSelectionSP selection = new KisSelection(); foreach (KisNodeSP node, m_info->allSrcNodes()) { KisMaskSP mask = dynamic_cast(node.data()); if (!mask) continue; selection->pixelSelection()->applySelection( mask->selection()->pixelSelection(), SELECTION_ADD); } KisSelectionMaskSP mergedMask = new KisSelectionMask(m_info->image); mergedMask->initSelection(parentLayer); mergedMask->setSelection(selection); m_info->dstNode = mergedMask; } private: MergeDownInfoBaseSP m_info; KisNodeSP m_putAfter; }; struct ActivateSelectionMask : public KisCommandUtils::AggregateCommand { ActivateSelectionMask(MergeDownInfoBaseSP info) : m_info(info) {} void populateChildCommands() override { KisSelectionMaskSP mergedMask = dynamic_cast(m_info->dstNode.data()); addCommand(new KisActivateSelectionMaskCommand(mergedMask, true)); } private: MergeDownInfoBaseSP m_info; }; bool tryMergeSelectionMasks(KisImageSP image, KisNodeList mergedNodes, KisNodeSP putAfter) { QList selectionMasks; for (auto it = mergedNodes.begin(); it != mergedNodes.end(); /*noop*/) { KisSelectionMaskSP mask = dynamic_cast(it->data()); if (!mask) { it = mergedNodes.erase(it); } else { selectionMasks.append(mask); ++it; } } if (mergedNodes.isEmpty()) return false; KisLayerSP parentLayer = qobject_cast(selectionMasks.first()->parent().data()); KIS_ASSERT_RECOVER(parentLayer) { return 0; } KisImageSignalVector emitSignals; emitSignals << ModifiedSignal; KisProcessingApplicator applicator(image, 0, KisProcessingApplicator::NONE, emitSignals, kundo2_i18n("Merge Selection Masks")); MergeMultipleInfoSP info(new MergeMultipleInfo(image, mergedNodes)); applicator.applyCommand(new MergeSelectionMasks(info, putAfter)); applicator.applyCommand(new CleanUpNodes(info, putAfter), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); applicator.applyCommand(new ActivateSelectionMask(info)); applicator.end(); return true; } void flattenLayer(KisImageSP image, KisLayerSP layer) { if (!layer->childCount() && !layer->layerStyle()) return; KisNodeList mergedNodes; mergedNodes << layer; mergeMultipleLayersImpl(image, mergedNodes, layer, true, kundo2_i18n("Flatten Layer")); } void flattenImage(KisImageSP image) { KisNodeList mergedNodes; mergedNodes << image->root(); mergeMultipleLayersImpl(image, mergedNodes, 0, true, kundo2_i18n("Flatten Image")); } KisSimpleUpdateCommand::KisSimpleUpdateCommand(KisNodeList nodes, bool finalize, KUndo2Command *parent) : FlipFlopCommand(finalize, parent), m_nodes(nodes) { } void KisSimpleUpdateCommand::end() { updateNodes(m_nodes); } void KisSimpleUpdateCommand::updateNodes(const KisNodeList &nodes) { Q_FOREACH(KisNodeSP node, nodes) { node->setDirty(node->extent()); } } void recursiveApplyNodes(KisNodeSP node, std::function func) { func(node); node = node->firstChild(); while (node) { recursiveApplyNodes(node, func); node = node->nextSibling(); } } KisNodeSP recursiveFindNode(KisNodeSP node, std::function func) { if (func(node)) { return node; } node = node->firstChild(); while (node) { KisNodeSP resultNode = recursiveFindNode(node, func); if (resultNode) { return resultNode; } node = node->nextSibling(); } return 0; } KisNodeSP findNodeByUuid(KisNodeSP root, const QUuid &uuid) { return recursiveFindNode(root, [uuid] (KisNodeSP node) { return node->uuid() == uuid; }); } void forceAllDelayedNodesUpdate(KisNodeSP root) { KisLayerUtils::recursiveApplyNodes(root, [] (KisNodeSP node) { KisDelayedUpdateNodeInterface *delayedUpdate = dynamic_cast(node.data()); if (delayedUpdate) { delayedUpdate->forceUpdateTimedNode(); } }); } } diff --git a/libs/image/kis_mask.cc b/libs/image/kis_mask.cc index 5e9625068d..37afc516b0 100644 --- a/libs/image/kis_mask.cc +++ b/libs/image/kis_mask.cc @@ -1,427 +1,460 @@ /* * Copyright (c) 2006 Boudewijn Rempt * (c) 2009 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_mask.h" #include // to prevent incomplete class types on "delete selection->flatten();" #include #include #include #include #include #include "kis_paint_device.h" #include "kis_selection.h" #include "kis_pixel_selection.h" #include "kis_painter.h" #include "kis_image.h" #include "kis_layer.h" #include "kis_cached_paint_device.h" #include "kis_mask_projection_plane.h" #include "kis_raster_keyframe_channel.h" struct Q_DECL_HIDDEN KisMask::Private { Private(KisMask *_q) : q(_q), projectionPlane(new KisMaskProjectionPlane(q)) { } mutable KisSelectionSP selection; KisCachedPaintDevice paintDeviceCache; KisMask *q; /** * Due to the design of the Kra format the X,Y offset of the paint * device belongs to the node, but not to the device itself. So * the offset is set when the node is created, but not when the * selection is initialized. This causes the X,Y values to be * lost, since the selection doen not exist at the moment. That is * why we save it separately. */ QScopedPointer deferredSelectionOffset; KisAbstractProjectionPlaneSP projectionPlane; KisCachedSelection cachedSelection; void initSelectionImpl(KisSelectionSP copyFrom, KisLayerSP parentLayer, KisPaintDeviceSP copyFromDevice); }; KisMask::KisMask(const QString & name) : KisNode() , m_d(new Private(this)) { setName(name); } KisMask::KisMask(const KisMask& rhs) : KisNode(rhs) , KisIndirectPaintingSupport() , m_d(new Private(this)) { setName(rhs.name()); if (rhs.m_d->selection) { m_d->selection = new KisSelection(*rhs.m_d->selection.data()); m_d->selection->setParentNode(this); KisPixelSelectionSP pixelSelection = m_d->selection->pixelSelection(); if (pixelSelection->framesInterface()) { addKeyframeChannel(pixelSelection->keyframeChannel()); enableAnimation(); } } } KisMask::~KisMask() { delete m_d; } void KisMask::setImage(KisImageWSP image) { KisPaintDeviceSP parentPaintDevice = parent() ? parent()->original() : 0; KisDefaultBoundsBaseSP defaultBounds = new KisSelectionDefaultBounds(parentPaintDevice, image); if (m_d->selection) { m_d->selection->setDefaultBounds(defaultBounds); } } bool KisMask::allowAsChild(KisNodeSP node) const { Q_UNUSED(node); return false; } const KoColorSpace * KisMask::colorSpace() const { KisNodeSP parentNode = parent(); return parentNode ? parentNode->colorSpace() : 0; } const KoCompositeOp * KisMask::compositeOp() const { /** * FIXME: This function duplicates the same function from * KisLayer. We can't move it to KisBaseNode as it doesn't * know anything about parent() method of KisNode * Please think it over... */ const KoColorSpace *colorSpace = this->colorSpace(); if (!colorSpace) return 0; const KoCompositeOp* op = colorSpace->compositeOp(compositeOpId()); return op ? op : colorSpace->compositeOp(COMPOSITE_OVER); } void KisMask::initSelection(KisSelectionSP copyFrom, KisLayerSP parentLayer) { m_d->initSelectionImpl(copyFrom, parentLayer, 0); } void KisMask::initSelection(KisPaintDeviceSP copyFromDevice, KisLayerSP parentLayer) { m_d->initSelectionImpl(0, parentLayer, copyFromDevice); } void KisMask::initSelection(KisLayerSP parentLayer) { m_d->initSelectionImpl(0, parentLayer, 0); } void KisMask::Private::initSelectionImpl(KisSelectionSP copyFrom, KisLayerSP parentLayer, KisPaintDeviceSP copyFromDevice) { Q_ASSERT(parentLayer); KisPaintDeviceSP parentPaintDevice = parentLayer->original(); if (copyFrom) { /** * We can't use setSelection as we may not have parent() yet */ selection = new KisSelection(*copyFrom); selection->setDefaultBounds(new KisSelectionDefaultBounds(parentPaintDevice, parentLayer->image())); if (copyFrom->hasShapeSelection()) { delete selection->flatten(); } } else if (copyFromDevice) { selection = new KisSelection(new KisSelectionDefaultBounds(parentPaintDevice, parentLayer->image())); QRect rc(copyFromDevice->extent()); KisPainter::copyAreaOptimized(rc.topLeft(), copyFromDevice, selection->pixelSelection(), rc); selection->pixelSelection()->invalidateOutlineCache(); } else { selection = new KisSelection(new KisSelectionDefaultBounds(parentPaintDevice, parentLayer->image())); selection->pixelSelection()->setDefaultPixel(KoColor(Qt::white, selection->pixelSelection()->colorSpace())); if (deferredSelectionOffset) { selection->setX(deferredSelectionOffset->x()); selection->setY(deferredSelectionOffset->y()); deferredSelectionOffset.reset(); } } selection->setParentNode(q); selection->updateProjection(); } KisSelectionSP KisMask::selection() const { return m_d->selection; } KisPaintDeviceSP KisMask::paintDevice() const { return selection()->pixelSelection(); } KisPaintDeviceSP KisMask::original() const { return paintDevice(); } KisPaintDeviceSP KisMask::projection() const { return paintDevice(); } KisAbstractProjectionPlaneSP KisMask::projectionPlane() const { return m_d->projectionPlane; } void KisMask::setSelection(KisSelectionSP selection) { m_d->selection = selection; if (parent()) { const KisLayer *parentLayer = qobject_cast(parent()); m_d->selection->setDefaultBounds(new KisDefaultBounds(parentLayer->image())); } m_d->selection->setParentNode(this); } void KisMask::select(const QRect & rc, quint8 selectedness) { KisSelectionSP sel = selection(); KisPixelSelectionSP psel = sel->pixelSelection(); psel->select(rc, selectedness); sel->updateProjection(rc); } QRect KisMask::decorateRect(KisPaintDeviceSP &src, KisPaintDeviceSP &dst, const QRect & rc, PositionToFilthy maskPos) const { Q_UNUSED(src); Q_UNUSED(dst); Q_UNUSED(maskPos); Q_ASSERT_X(0, "KisMask::decorateRect", "Should be overridden by successors"); return rc; } void KisMask::apply(KisPaintDeviceSP projection, const QRect &applyRect, const QRect &needRect, PositionToFilthy maskPos) const { if (selection()) { m_d->selection->updateProjection(applyRect); KisSelectionSP effectiveSelection = m_d->selection; QRect effectiveExtent = effectiveSelection->selectedRect(); { // Access temporary target under the lock held KisIndirectPaintingSupport::ReadLocker l(this); if (hasTemporaryTarget()) { effectiveExtent |= temporaryTarget()->extent(); } if(!effectiveExtent.intersects(applyRect)) { return; } if (hasTemporaryTarget()) { effectiveSelection = m_d->cachedSelection.getSelection(); effectiveSelection->setDefaultBounds(m_d->selection->pixelSelection()->defaultBounds()); KisPainter::copyAreaOptimized(applyRect.topLeft(), m_d->selection->pixelSelection(), effectiveSelection->pixelSelection(), applyRect); KisPainter gc(effectiveSelection->pixelSelection()); setupTemporaryPainter(&gc); gc.bitBlt(applyRect.topLeft(), temporaryTarget(), applyRect); } } KisPaintDeviceSP cacheDevice = m_d->paintDeviceCache.getDevice(projection); QRect updatedRect = decorateRect(projection, cacheDevice, applyRect, maskPos); // masks don't have any compositioning KisPainter::copyAreaOptimized(updatedRect.topLeft(), cacheDevice, projection, updatedRect, effectiveSelection); m_d->paintDeviceCache.putDevice(cacheDevice); if (effectiveSelection != m_d->selection) { m_d->cachedSelection.putSelection(effectiveSelection); } } else { KisPaintDeviceSP cacheDevice = m_d->paintDeviceCache.getDevice(projection); cacheDevice->makeCloneFromRough(projection, needRect); projection->clear(needRect); decorateRect(cacheDevice, projection, applyRect, maskPos); m_d->paintDeviceCache.putDevice(cacheDevice); } } QRect KisMask::needRect(const QRect &rect, PositionToFilthy pos) const { Q_UNUSED(pos); QRect resultRect = rect; - if (m_d->selection) - resultRect &= m_d->selection->selectedRect(); + if (m_d->selection) { + QRect selectionExtent = m_d->selection->selectedRect(); + + // copy for thread safety! + KisPaintDeviceSP temporaryTarget = this->temporaryTarget(); + + if (temporaryTarget) { + selectionExtent |= temporaryTarget->extent(); + } + + resultRect &= selectionExtent; + } return resultRect; } QRect KisMask::changeRect(const QRect &rect, PositionToFilthy pos) const { - Q_UNUSED(pos); - QRect resultRect = rect; - if (m_d->selection) - resultRect &= m_d->selection->selectedRect(); - - return resultRect; + return KisMask::needRect(rect, pos); } QRect KisMask::extent() const { - return m_d->selection ? m_d->selection->selectedRect() : - parent() ? parent()->extent() : QRect(); + QRect resultRect; + + if (m_d->selection) { + resultRect = m_d->selection->selectedRect(); + + // copy for thread safety! + KisPaintDeviceSP temporaryTarget = this->temporaryTarget(); + + if (temporaryTarget) { + resultRect |= temporaryTarget->extent(); + } + } else if (KisNodeSP parent = this->parent()) { + resultRect = parent->extent(); + } + + return resultRect; } QRect KisMask::exactBounds() const { - return m_d->selection ? m_d->selection->selectedExactRect() : - parent() ? parent()->exactBounds() : QRect(); + QRect resultRect; + + if (m_d->selection) { + resultRect = m_d->selection->selectedExactRect(); + + // copy for thread safety! + KisPaintDeviceSP temporaryTarget = this->temporaryTarget(); + + if (temporaryTarget) { + resultRect |= temporaryTarget->exactBounds(); + } + } else if (KisNodeSP parent = this->parent()) { + resultRect = parent->exactBounds(); + } + + return resultRect; } qint32 KisMask::x() const { return m_d->selection ? m_d->selection->x() : m_d->deferredSelectionOffset ? m_d->deferredSelectionOffset->x() : parent() ? parent()->x() : 0; } qint32 KisMask::y() const { return m_d->selection ? m_d->selection->y() : m_d->deferredSelectionOffset ? m_d->deferredSelectionOffset->y() : parent() ? parent()->y() : 0; } void KisMask::setX(qint32 x) { if (m_d->selection) { m_d->selection->setX(x); } else if (!m_d->deferredSelectionOffset) { m_d->deferredSelectionOffset.reset(new QPoint(x, 0)); } else { m_d->deferredSelectionOffset->rx() = x; } } void KisMask::setY(qint32 y) { if (m_d->selection) { m_d->selection->setY(y); } else if (!m_d->deferredSelectionOffset) { m_d->deferredSelectionOffset.reset(new QPoint(0, y)); } else { m_d->deferredSelectionOffset->ry() = y; } } QRect KisMask::nonDependentExtent() const { return QRect(); } QImage KisMask::createThumbnail(qint32 w, qint32 h) { KisPaintDeviceSP originalDevice = selection() ? selection()->projection() : 0; return originalDevice ? originalDevice->createThumbnail(w, h, 1, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()) : QImage(); } void KisMask::testingInitSelection(const QRect &rect, KisLayerSP parentLayer) { if (parentLayer) { m_d->selection = new KisSelection(new KisSelectionDefaultBounds(parentLayer->paintDevice(), parentLayer->image())); } else { m_d->selection = new KisSelection(); } m_d->selection->pixelSelection()->select(rect, OPACITY_OPAQUE_U8); m_d->selection->updateProjection(rect); m_d->selection->setParentNode(this); } KisKeyframeChannel *KisMask::requestKeyframeChannel(const QString &id) { if (id == KisKeyframeChannel::Content.id()) { KisPaintDeviceSP device = paintDevice(); if (device) { KisRasterKeyframeChannel *contentChannel = device->createKeyframeChannel(KisKeyframeChannel::Content); contentChannel->setFilenameSuffix(".pixelselection"); return contentChannel; } } return KisNode::requestKeyframeChannel(id); } void KisMask::baseNodeChangedCallback() { KisNodeSP up = parent(); KisLayer *layer = dynamic_cast(up.data()); if (layer) { layer->notifyChildMaskChanged(this); } KisNode::baseNodeChangedCallback(); } diff --git a/libs/image/kis_paint_device.cc b/libs/image/kis_paint_device.cc index c12dc9d33f..a331359180 100644 --- a/libs/image/kis_paint_device.cc +++ b/libs/image/kis_paint_device.cc @@ -1,2170 +1,2208 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_paint_device.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_image.h" #include "kis_random_sub_accessor.h" #include "kis_selection.h" #include "kis_node.h" #include "kis_datamanager.h" #include "kis_paint_device_writer.h" #include "kis_selection_component.h" #include "kis_pixel_selection.h" #include "kis_repeat_iterators_pixel.h" #include "kis_fixed_paint_device.h" #include "tiles3/kis_hline_iterator.h" #include "tiles3/kis_vline_iterator.h" #include "tiles3/kis_random_accessor.h" #include "kis_default_bounds.h" #include "kis_lod_transform.h" #include "kis_raster_keyframe_channel.h" #include "kis_paint_device_cache.h" #include "kis_paint_device_data.h" #include "kis_paint_device_frames_interface.h" #include "kis_transform_worker.h" #include "kis_filter_strategy.h" #include "krita_utils.h" struct KisPaintDeviceSPStaticRegistrar { KisPaintDeviceSPStaticRegistrar() { qRegisterMetaType("KisPaintDeviceSP"); } }; static KisPaintDeviceSPStaticRegistrar __registrar; struct KisPaintDevice::Private { /** * Used when the paint device is loading to ensure no lod/animation * interferes the process. */ static const KisDefaultBoundsSP transitionalDefaultBounds; public: class KisPaintDeviceStrategy; class KisPaintDeviceWrappedStrategy; Private(KisPaintDevice *paintDevice); ~Private(); KisPaintDevice *q; KisNodeWSP parent; QScopedPointer contentChannel; KisDefaultBoundsBaseSP defaultBounds; QScopedPointer basicStrategy; QScopedPointer wrappedStrategy; QMutex m_wrappedStrategyMutex; QScopedPointer framesInterface; bool isProjectionDevice; KisPaintDeviceStrategy* currentStrategy(); void init(const KoColorSpace *cs, const quint8 *defaultPixel); KUndo2Command* convertColorSpace(const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags); bool assignProfile(const KoColorProfile * profile); inline const KoColorSpace* colorSpace() const { return currentData()->colorSpace(); } inline KisDataManagerSP dataManager() const { return currentData()->dataManager(); } inline qint32 x() const { return currentData()->x(); } inline qint32 y() const { return currentData()->y(); } inline void setX(qint32 x) { currentData()->setX(x); } inline void setY(qint32 y) { currentData()->setY(y); } inline KisPaintDeviceCache* cache() { return currentData()->cache(); } inline KisIteratorCompleteListener* cacheInvalidator() { return currentData()->cacheInvalidator(); } void cloneAllDataObjects(Private *rhs, bool copyFrames) { m_lodData.reset(); m_externalFrameData.reset(); if (!m_frames.isEmpty()) { m_frames.clear(); } if (!copyFrames) { if (m_data) { m_data->prepareClone(rhs->currentNonLodData(), true); } else { m_data = toQShared(new KisPaintDeviceData(rhs->currentNonLodData(), true)); } } else { if (m_data && !rhs->m_data) { m_data.clear(); } else if (!m_data && rhs->m_data) { m_data = toQShared(new KisPaintDeviceData(rhs->m_data.data(), true)); } else if (m_data && rhs->m_data) { m_data->prepareClone(rhs->m_data.data(), true); } if (!rhs->m_frames.isEmpty()) { FramesHash::const_iterator it = rhs->m_frames.constBegin(); FramesHash::const_iterator end = rhs->m_frames.constEnd(); for (; it != end; ++it) { DataSP data = toQShared(new KisPaintDeviceData(it.value().data(), true)); m_frames.insert(it.key(), data); } } m_nextFreeFrameId = rhs->m_nextFreeFrameId; } if (rhs->m_lodData) { m_lodData.reset(new KisPaintDeviceData(rhs->m_lodData.data(), true)); } } void prepareClone(KisPaintDeviceSP src) { prepareCloneImpl(src, src->m_d->currentData()); Q_ASSERT(fastBitBltPossible(src)); } bool fastBitBltPossible(KisPaintDeviceSP src) { return fastBitBltPossibleImpl(src->m_d->currentData()); } int currentFrameId() const { KIS_ASSERT_RECOVER(contentChannel) { return -1; } return !defaultBounds->currentLevelOfDetail() ? contentChannel->frameIdAt(defaultBounds->currentTime()) : -1; } KisDataManagerSP frameDataManager(int frameId) const { DataSP data = m_frames[frameId]; return data->dataManager(); } void invalidateFrameCache(int frameId) { DataSP data = m_frames[frameId]; return data->cache()->invalidate(); } private: typedef KisPaintDeviceData Data; typedef QSharedPointer DataSP; typedef QHash FramesHash; class FrameInsertionCommand : public KUndo2Command { public: FrameInsertionCommand(FramesHash *hash, DataSP data, int frameId, bool insert, KUndo2Command *parentCommand) : KUndo2Command(parentCommand), m_hash(hash), m_data(data), m_frameId(frameId), m_insert(insert) { } void redo() override { doSwap(m_insert); } void undo() override { doSwap(!m_insert); } private: void doSwap(bool insert) { if (insert) { m_hash->insert(m_frameId, m_data); } else { DataSP deletedData = m_hash->take(m_frameId); } } private: FramesHash *m_hash; DataSP m_data; int m_frameId; bool m_insert; }; public: int getNextFrameId() { int frameId = 0; while (m_frames.contains(frameId = m_nextFreeFrameId++)); KIS_SAFE_ASSERT_RECOVER_NOOP(!m_frames.contains(frameId)); return frameId; } int createFrame(bool copy, int copySrc, const QPoint &offset, KUndo2Command *parentCommand) { KIS_ASSERT_RECOVER(parentCommand) { return -1; } DataSP data; bool initialFrame = false; if (m_frames.isEmpty()) { /** * Here we move the contents of the paint device to the * new frame and clear m_data to make the "background" for * the areas where there is no frame at all. */ data = toQShared(new Data(m_data.data(), true)); m_data->dataManager()->clear(); m_data->cache()->invalidate(); initialFrame = true; } else if (copy) { DataSP srcData = m_frames[copySrc]; data = toQShared(new Data(srcData.data(), true)); } else { DataSP srcData = m_frames.begin().value(); data = toQShared(new Data(srcData.data(), false)); } if (!initialFrame && !copy) { data->setX(offset.x()); data->setY(offset.y()); } int frameId = getNextFrameId(); KUndo2Command *cmd = new FrameInsertionCommand(&m_frames, data, frameId, true, parentCommand); cmd->redo(); return frameId; } void deleteFrame(int frame, KUndo2Command *parentCommand) { KIS_ASSERT_RECOVER_RETURN(m_frames.contains(frame)); KIS_ASSERT_RECOVER_RETURN(parentCommand); DataSP deletedData = m_frames[frame]; KUndo2Command *cmd = new FrameInsertionCommand(&m_frames, deletedData, frame, false, parentCommand); cmd->redo(); } QRect frameBounds(int frameId) { DataSP data = m_frames[frameId]; QRect extent = data->dataManager()->extent(); extent.translate(data->x(), data->y()); return extent; } QPoint frameOffset(int frameId) const { DataSP data = m_frames[frameId]; return QPoint(data->x(), data->y()); } void setFrameOffset(int frameId, const QPoint &offset) { DataSP data = m_frames[frameId]; data->setX(offset.x()); data->setY(offset.y()); } const QList frameIds() const { return m_frames.keys(); } bool readFrame(QIODevice *stream, int frameId) { bool retval = false; DataSP data = m_frames[frameId]; retval = data->dataManager()->read(stream); data->cache()->invalidate(); return retval; } bool writeFrame(KisPaintDeviceWriter &store, int frameId) { DataSP data = m_frames[frameId]; return data->dataManager()->write(store); } void setFrameDefaultPixel(const KoColor &defPixel, int frameId) { DataSP data = m_frames[frameId]; KoColor color(defPixel); color.convertTo(data->colorSpace()); data->dataManager()->setDefaultPixel(color.data()); } KoColor frameDefaultPixel(int frameId) const { DataSP data = m_frames[frameId]; return KoColor(data->dataManager()->defaultPixel(), data->colorSpace()); } void fetchFrame(int frameId, KisPaintDeviceSP targetDevice); void uploadFrame(int srcFrameId, int dstFrameId, KisPaintDeviceSP srcDevice); void uploadFrame(int dstFrameId, KisPaintDeviceSP srcDevice); void uploadFrameData(DataSP srcData, DataSP dstData); struct LodDataStructImpl; LodDataStruct* createLodDataStruct(int lod); void updateLodDataStruct(LodDataStruct *dst, const QRect &srcRect); void uploadLodDataStruct(LodDataStruct *dst); QRegion regionForLodSyncing() const; + void updateLodDataManager(KisDataManager *srcDataManager, + KisDataManager *dstDataManager, const QPoint &srcOffset, const QPoint &dstOffset, + const QRect &originalRect, int lod); + + void generateLodCloneDevice(KisPaintDeviceSP dst, const QRect &originalRect, int lod); + void tesingFetchLodDevice(KisPaintDeviceSP targetDevice); private: qint64 estimateDataSize(Data *data) const { const QRect &rc = data->dataManager()->extent(); return rc.width() * rc.height() * data->colorSpace()->pixelSize(); } public: void estimateMemoryStats(qint64 &imageData, qint64 &temporaryData, qint64 &lodData) const { imageData = 0; temporaryData = 0; lodData = 0; if (m_data) { imageData += estimateDataSize(m_data.data()); } if (m_lodData) { lodData += estimateDataSize(m_lodData.data()); } if (m_externalFrameData) { temporaryData += estimateDataSize(m_externalFrameData.data()); } Q_FOREACH (DataSP value, m_frames.values()) { imageData += estimateDataSize(value.data()); } } private: QRegion syncWholeDevice(Data *srcData); inline DataSP currentFrameData() const { DataSP data; const int numberOfFrames = contentChannel->keyframeCount(); if (numberOfFrames > 1) { int frameId = contentChannel->frameIdAt(defaultBounds->currentTime()); if (frameId == -1) { data = m_data; } else { KIS_ASSERT_RECOVER(m_frames.contains(frameId)) { return m_frames.begin().value(); } data = m_frames[frameId]; } } else if (numberOfFrames == 1) { data = m_frames.begin().value(); } else { data = m_data; } return data; } inline Data* currentNonLodData() const { Data *data = m_data.data(); if (contentChannel) { data = currentFrameData().data(); } else if (isProjectionDevice && defaultBounds->externalFrameActive()) { if (!m_externalFrameData) { QMutexLocker l(&m_dataSwitchLock); if (!m_externalFrameData) { m_externalFrameData.reset(new Data(m_data.data(), false)); } } data = m_externalFrameData.data(); } return data; } inline void ensureLodDataPresent() const { if (!m_lodData) { Data *srcData = currentNonLodData(); QMutexLocker l(&m_dataSwitchLock); if (!m_lodData) { m_lodData.reset(new Data(srcData, false)); } } } inline Data* currentData() const { Data *data; if (defaultBounds->currentLevelOfDetail()) { ensureLodDataPresent(); data = m_lodData.data(); } else { data = currentNonLodData(); } return data; } void prepareCloneImpl(KisPaintDeviceSP src, Data *srcData) { currentData()->prepareClone(srcData); q->setDefaultPixel(KoColor(srcData->dataManager()->defaultPixel(), colorSpace())); q->setDefaultBounds(src->defaultBounds()); } bool fastBitBltPossibleImpl(Data *srcData) { return x() == srcData->x() && y() == srcData->y() && *colorSpace() == *srcData->colorSpace(); } QList allDataObjects() const { QList dataObjects; if (m_frames.isEmpty()) { dataObjects << m_data.data(); } dataObjects << m_lodData.data(); dataObjects << m_externalFrameData.data(); Q_FOREACH (DataSP value, m_frames.values()) { dataObjects << value.data(); } return dataObjects; } void transferFromData(Data *data, KisPaintDeviceSP targetDevice); struct Q_DECL_HIDDEN StrategyPolicy; typedef KisSequentialIteratorBase, StrategyPolicy> InternalSequentialConstIterator; typedef KisSequentialIteratorBase, StrategyPolicy> InternalSequentialIterator; private: friend class KisPaintDeviceFramesInterface; private: DataSP m_data; mutable QScopedPointer m_lodData; mutable QScopedPointer m_externalFrameData; mutable QMutex m_dataSwitchLock; FramesHash m_frames; int m_nextFreeFrameId; }; const KisDefaultBoundsSP KisPaintDevice::Private::transitionalDefaultBounds = new KisDefaultBounds(); #include "kis_paint_device_strategies.h" KisPaintDevice::Private::Private(KisPaintDevice *paintDevice) : q(paintDevice), basicStrategy(new KisPaintDeviceStrategy(paintDevice, this)), isProjectionDevice(false), m_data(new Data(paintDevice)), m_nextFreeFrameId(0) { } KisPaintDevice::Private::~Private() { m_frames.clear(); } KisPaintDevice::Private::KisPaintDeviceStrategy* KisPaintDevice::Private::currentStrategy() { if (!defaultBounds->wrapAroundMode()) { return basicStrategy.data(); } const QRect wrapRect = defaultBounds->bounds(); if (!wrappedStrategy || wrappedStrategy->wrapRect() != wrapRect) { QMutexLocker locker(&m_wrappedStrategyMutex); if (!wrappedStrategy) { wrappedStrategy.reset(new KisPaintDeviceWrappedStrategy(wrapRect, q, this)); } else if (wrappedStrategy->wrapRect() != wrapRect) { wrappedStrategy->setWrapRect(wrapRect); } } return wrappedStrategy.data(); } struct KisPaintDevice::Private::StrategyPolicy { StrategyPolicy(KisPaintDevice::Private::KisPaintDeviceStrategy *strategy, KisDataManager *dataManager, qint32 offsetX, qint32 offsetY) : m_strategy(strategy), m_dataManager(dataManager), m_offsetX(offsetX), m_offsetY(offsetY) { } KisHLineConstIteratorSP createConstIterator(const QRect &rect) { return m_strategy->createHLineConstIteratorNG(m_dataManager, rect.x(), rect.y(), rect.width(), m_offsetX, m_offsetY); } KisHLineIteratorSP createIterator(const QRect &rect) { return m_strategy->createHLineIteratorNG(m_dataManager, rect.x(), rect.y(), rect.width(), m_offsetX, m_offsetY); } int pixelSize() const { return m_dataManager->pixelSize(); } KisPaintDeviceStrategy *m_strategy; KisDataManager *m_dataManager; int m_offsetX; int m_offsetY; }; struct KisPaintDevice::Private::LodDataStructImpl : public KisPaintDevice::LodDataStruct { LodDataStructImpl(Data *_lodData) : lodData(_lodData) {} QScopedPointer lodData; }; QRegion KisPaintDevice::Private::regionForLodSyncing() const { Data *srcData = currentNonLodData(); return srcData->dataManager()->region().translated(srcData->x(), srcData->y()); } KisPaintDevice::LodDataStruct* KisPaintDevice::Private::createLodDataStruct(int newLod) { + KIS_SAFE_ASSERT_RECOVER_NOOP(newLod > 0); + Data *srcData = currentNonLodData(); Data *lodData = new Data(srcData, false); LodDataStruct *lodStruct = new LodDataStructImpl(lodData); int expectedX = KisLodTransform::coordToLodCoord(srcData->x(), newLod); int expectedY = KisLodTransform::coordToLodCoord(srcData->y(), newLod); /** * We compare color spaces as pure pointers, because they must be * exactly the same, since they come from the common source. */ if (lodData->levelOfDetail() != newLod || lodData->colorSpace() != srcData->colorSpace() || lodData->x() != expectedX || lodData->y() != expectedY) { lodData->prepareClone(srcData); lodData->setLevelOfDetail(newLod); lodData->setX(expectedX); lodData->setY(expectedY); // FIXME: different kind of synchronization } //QRegion dirtyRegion = syncWholeDevice(srcData); lodData->cache()->invalidate(); return lodStruct; } -void KisPaintDevice::Private::updateLodDataStruct(LodDataStruct *_dst, const QRect &originalRect) +void KisPaintDevice::Private::updateLodDataManager(KisDataManager *srcDataManager, + KisDataManager *dstDataManager, + const QPoint &srcOffset, + const QPoint &dstOffset, + const QRect &originalRect, + int lod) { - LodDataStructImpl *dst = dynamic_cast(_dst); - KIS_SAFE_ASSERT_RECOVER_RETURN(dst); - - Data *lodData = dst->lodData.data(); - Data *srcData = currentNonLodData(); - - const int lod = lodData->levelOfDetail(); const int srcStepSize = 1 << lod; KIS_ASSERT_RECOVER_RETURN(lod > 0); const QRect srcRect = KisLodTransform::alignedRect(originalRect, lod); const QRect dstRect = KisLodTransform::scaledRect(srcRect, lod); if (!srcRect.isValid() || !dstRect.isValid()) return; KIS_ASSERT_RECOVER_NOOP(srcRect.width() / srcStepSize == dstRect.width()); - const int pixelSize = srcData->dataManager()->pixelSize(); + const int pixelSize = srcDataManager->pixelSize(); int rowsAccumulated = 0; int columnsAccumulated = 0; KoMixColorsOp *mixOp = colorSpace()->mixColorsOp(); QScopedArrayPointer blendData(new quint8[srcStepSize * srcRect.width() * pixelSize]); quint8 *blendDataPtr = blendData.data(); int blendDataOffset = 0; const int srcCellSize = srcStepSize * srcStepSize; const int srcCellStride = srcCellSize * pixelSize; const int srcStepStride = srcStepSize * pixelSize; const int srcColumnStride = (srcStepSize - 1) * srcStepStride; QScopedArrayPointer weights(new qint16[srcCellSize]); { const qint16 averageWeight = qCeil(255.0 / srcCellSize); const qint16 extraWeight = averageWeight * srcCellSize - 255; KIS_ASSERT_RECOVER_NOOP(extraWeight == 1); for (int i = 0; i < srcCellSize - 1; i++) { weights[i] = averageWeight; } weights[srcCellSize - 1] = averageWeight - extraWeight; } - InternalSequentialConstIterator srcIntIt(StrategyPolicy(currentStrategy(), srcData->dataManager().data(), srcData->x(), srcData->y()), srcRect); - InternalSequentialIterator dstIntIt(StrategyPolicy(currentStrategy(), lodData->dataManager().data(), lodData->x(), lodData->y()), dstRect); + InternalSequentialConstIterator srcIntIt(StrategyPolicy(currentStrategy(), srcDataManager, srcOffset.x(), srcOffset.y()), srcRect); + InternalSequentialIterator dstIntIt(StrategyPolicy(currentStrategy(), dstDataManager, dstOffset.x(), dstOffset.y()), dstRect); int rowsRemaining = srcRect.height(); while (rowsRemaining > 0) { int colsRemaining = srcRect.width(); while (colsRemaining > 0 && srcIntIt.nextPixel()) { memcpy(blendDataPtr, srcIntIt.rawDataConst(), pixelSize); blendDataPtr += pixelSize; columnsAccumulated++; if (columnsAccumulated >= srcStepSize) { blendDataPtr += srcColumnStride; columnsAccumulated = 0; } colsRemaining--; } rowsAccumulated++; if (rowsAccumulated >= srcStepSize) { // blend and write the final data blendDataPtr = blendData.data(); int colsRemaining = dstRect.width(); while (colsRemaining > 0 && dstIntIt.nextPixel()) { mixOp->mixColors(blendDataPtr, weights.data(), srcCellSize, dstIntIt.rawData()); blendDataPtr += srcCellStride; colsRemaining--; } // reset counters rowsAccumulated = 0; blendDataPtr = blendData.data(); blendDataOffset = 0; } else { blendDataOffset += srcStepStride; blendDataPtr = blendData.data() + blendDataOffset; } rowsRemaining--; } } +void KisPaintDevice::Private::updateLodDataStruct(LodDataStruct *_dst, const QRect &originalRect) +{ + LodDataStructImpl *dst = dynamic_cast(_dst); + KIS_SAFE_ASSERT_RECOVER_RETURN(dst); + + Data *lodData = dst->lodData.data(); + Data *srcData = currentNonLodData(); + + const int lod = lodData->levelOfDetail(); + + updateLodDataManager(srcData->dataManager().data(), lodData->dataManager().data(), + QPoint(srcData->x(), srcData->y()), + QPoint(lodData->x(), lodData->y()), + originalRect, lod); +} + +void KisPaintDevice::Private::generateLodCloneDevice(KisPaintDeviceSP dst, const QRect &originalRect, int lod) +{ + KIS_SAFE_ASSERT_RECOVER_RETURN(fastBitBltPossible(dst)); + + Data *srcData = currentNonLodData(); + updateLodDataManager(srcData->dataManager().data(), dst->dataManager().data(), + QPoint(srcData->x(), srcData->y()), + QPoint(dst->x(), dst->y()), + originalRect, lod); +} + void KisPaintDevice::Private::uploadLodDataStruct(LodDataStruct *_dst) { LodDataStructImpl *dst = dynamic_cast(_dst); KIS_SAFE_ASSERT_RECOVER_RETURN(dst); KIS_SAFE_ASSERT_RECOVER_RETURN( dst->lodData->levelOfDetail() == defaultBounds->currentLevelOfDetail()); ensureLodDataPresent(); m_lodData->prepareClone(dst->lodData.data()); m_lodData->dataManager()->bitBltRough(dst->lodData->dataManager(), dst->lodData->dataManager()->extent()); } void KisPaintDevice::Private::transferFromData(Data *data, KisPaintDeviceSP targetDevice) { QRect extent = data->dataManager()->extent(); extent.translate(data->x(), data->y()); targetDevice->m_d->prepareCloneImpl(q, data); targetDevice->m_d->currentStrategy()->fastBitBltRough(data->dataManager(), extent); } void KisPaintDevice::Private::fetchFrame(int frameId, KisPaintDeviceSP targetDevice) { DataSP data = m_frames[frameId]; transferFromData(data.data(), targetDevice); } void KisPaintDevice::Private::uploadFrame(int srcFrameId, int dstFrameId, KisPaintDeviceSP srcDevice) { DataSP dstData = m_frames[dstFrameId]; KIS_ASSERT_RECOVER_RETURN(dstData); DataSP srcData = srcDevice->m_d->m_frames[srcFrameId]; KIS_ASSERT_RECOVER_RETURN(srcData); uploadFrameData(srcData, dstData); } void KisPaintDevice::Private::uploadFrame(int dstFrameId, KisPaintDeviceSP srcDevice) { DataSP dstData = m_frames[dstFrameId]; KIS_ASSERT_RECOVER_RETURN(dstData); DataSP srcData = srcDevice->m_d->m_data; KIS_ASSERT_RECOVER_RETURN(srcData); uploadFrameData(srcData, dstData); } void KisPaintDevice::Private::uploadFrameData(DataSP srcData, DataSP dstData) { if (srcData->colorSpace() != dstData->colorSpace() && *srcData->colorSpace() != *dstData->colorSpace()) { KUndo2Command tempCommand; srcData = toQShared(new Data(srcData.data(), true)); srcData->convertDataColorSpace(dstData->colorSpace(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags(), &tempCommand); } dstData->dataManager()->clear(); dstData->cache()->invalidate(); const QRect rect = srcData->dataManager()->extent(); dstData->dataManager()->bitBltRough(srcData->dataManager(), rect); dstData->setX(srcData->x()); dstData->setY(srcData->y()); } void KisPaintDevice::Private::tesingFetchLodDevice(KisPaintDeviceSP targetDevice) { Data *data = m_lodData.data(); Q_ASSERT(data); transferFromData(data, targetDevice); } KUndo2Command* KisPaintDevice::Private::convertColorSpace(const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { class DeviceChangeColorSpaceCommand : public KUndo2Command { public: DeviceChangeColorSpaceCommand(KisPaintDeviceSP device) : m_firstRun(true), m_device(device) { } void emitNotifications() { m_device->emitColorSpaceChanged(); m_device->setDirty(); } void redo() override { KUndo2Command::redo(); if (!m_firstRun) { m_firstRun = false; return; } emitNotifications(); } void undo() override { KUndo2Command::undo(); emitNotifications(); } private: bool m_firstRun; KisPaintDeviceSP m_device; }; KUndo2Command *parentCommand = new DeviceChangeColorSpaceCommand(q); QList dataObjects = allDataObjects(); Q_FOREACH (Data *data, dataObjects) { if (!data) continue; data->convertDataColorSpace(dstColorSpace, renderingIntent, conversionFlags, parentCommand); } if (!parentCommand->childCount()) { delete parentCommand; parentCommand = 0; } else { q->emitColorSpaceChanged(); } return parentCommand; } bool KisPaintDevice::Private::assignProfile(const KoColorProfile * profile) { if (!profile) return false; const KoColorSpace *dstColorSpace = KoColorSpaceRegistry::instance()->colorSpace(colorSpace()->colorModelId().id(), colorSpace()->colorDepthId().id(), profile); if (!dstColorSpace) return false; QList dataObjects = allDataObjects(); Q_FOREACH (Data *data, dataObjects) { if (!data) continue; data->assignColorSpace(dstColorSpace); } q->emitProfileChanged(); // no undo information is provided here return true; } void KisPaintDevice::Private::init(const KoColorSpace *cs, const quint8 *defaultPixel) { QList dataObjects = allDataObjects(); Q_FOREACH (Data *data, dataObjects) { if (!data) continue; KisDataManagerSP dataManager = new KisDataManager(cs->pixelSize(), defaultPixel); data->init(cs, dataManager); } } KisPaintDevice::KisPaintDevice(const KoColorSpace * colorSpace, const QString& name) : QObject(0) , m_d(new Private(this)) { init(colorSpace, new KisDefaultBounds(), 0, name); } KisPaintDevice::KisPaintDevice(KisNodeWSP parent, const KoColorSpace * colorSpace, KisDefaultBoundsBaseSP defaultBounds, const QString& name) : QObject(0) , m_d(new Private(this)) { init(colorSpace, defaultBounds, parent, name); } void KisPaintDevice::init(const KoColorSpace *colorSpace, KisDefaultBoundsBaseSP defaultBounds, KisNodeWSP parent, const QString& name) { Q_ASSERT(colorSpace); setObjectName(name); // temporary def. bounds object for the initialization phase only m_d->defaultBounds = m_d->transitionalDefaultBounds; if (!defaultBounds) { // Reuse transitionalDefaultBounds here. Change if you change // semantics of transitionalDefaultBounds defaultBounds = m_d->transitionalDefaultBounds; } QScopedArrayPointer defaultPixel(new quint8[colorSpace->pixelSize()]); colorSpace->fromQColor(Qt::transparent, defaultPixel.data()); m_d->init(colorSpace, defaultPixel.data()); Q_ASSERT(m_d->colorSpace()); setDefaultBounds(defaultBounds); setParentNode(parent); } KisPaintDevice::KisPaintDevice(const KisPaintDevice& rhs, KritaUtils::DeviceCopyMode copyMode, KisNode *newParentNode) : QObject() , KisShared() , m_d(new Private(this)) { if (this != &rhs) { // temporary def. bounds object for the initialization phase only m_d->defaultBounds = m_d->transitionalDefaultBounds; // copy data objects with or without frames m_d->cloneAllDataObjects(rhs.m_d, copyMode == KritaUtils::CopyAllFrames); if (copyMode == KritaUtils::CopyAllFrames && rhs.m_d->framesInterface) { KIS_ASSERT_RECOVER_RETURN(rhs.m_d->framesInterface); KIS_ASSERT_RECOVER_RETURN(rhs.m_d->contentChannel); m_d->framesInterface.reset(new KisPaintDeviceFramesInterface(this)); m_d->contentChannel.reset(new KisRasterKeyframeChannel(*rhs.m_d->contentChannel.data(), newParentNode, this)); } setDefaultBounds(rhs.m_d->defaultBounds); setParentNode(newParentNode); } } KisPaintDevice::~KisPaintDevice() { delete m_d; } void KisPaintDevice::setProjectionDevice(bool value) { m_d->isProjectionDevice = value; } void KisPaintDevice::prepareClone(KisPaintDeviceSP src) { m_d->prepareClone(src); Q_ASSERT(fastBitBltPossible(src)); } void KisPaintDevice::makeCloneFrom(KisPaintDeviceSP src, const QRect &rect) { prepareClone(src); // we guarantee that *this is totally empty, so copy pixels that // are areally present on the source image only const QRect optimizedRect = rect & src->extent(); fastBitBlt(src, optimizedRect); } void KisPaintDevice::makeCloneFromRough(KisPaintDeviceSP src, const QRect &minimalRect) { prepareClone(src); // we guarantee that *this is totally empty, so copy pixels that // are areally present on the source image only const QRect optimizedRect = minimalRect & src->extent(); fastBitBltRough(src, optimizedRect); } void KisPaintDevice::setDirty() { m_d->cache()->invalidate(); if (m_d->parent.isValid()) m_d->parent->setDirty(); } void KisPaintDevice::setDirty(const QRect & rc) { m_d->cache()->invalidate(); if (m_d->parent.isValid()) m_d->parent->setDirty(rc); } void KisPaintDevice::setDirty(const QRegion & region) { m_d->cache()->invalidate(); if (m_d->parent.isValid()) m_d->parent->setDirty(region); } void KisPaintDevice::setDirty(const QVector rects) { m_d->cache()->invalidate(); if (m_d->parent.isValid()) m_d->parent->setDirty(rects); } void KisPaintDevice::requestTimeSwitch(int time) { if (m_d->parent.isValid()) { m_d->parent->requestTimeSwitch(time); } } int KisPaintDevice::sequenceNumber() const { return m_d->cache()->sequenceNumber(); } void KisPaintDevice::estimateMemoryStats(qint64 &imageData, qint64 &temporaryData, qint64 &lodData) const { m_d->estimateMemoryStats(imageData, temporaryData, lodData); } void KisPaintDevice::setParentNode(KisNodeWSP parent) { m_d->parent = parent; } // for testing purposes only KisNodeWSP KisPaintDevice::parentNode() const { return m_d->parent; } void KisPaintDevice::setDefaultBounds(KisDefaultBoundsBaseSP defaultBounds) { m_d->defaultBounds = defaultBounds; m_d->cache()->invalidate(); } KisDefaultBoundsBaseSP KisPaintDevice::defaultBounds() const { return m_d->defaultBounds; } void KisPaintDevice::moveTo(const QPoint &pt) { m_d->currentStrategy()->move(pt); m_d->cache()->invalidate(); } QPoint KisPaintDevice::offset() const { return QPoint(x(), y()); } void KisPaintDevice::moveTo(qint32 x, qint32 y) { moveTo(QPoint(x, y)); } void KisPaintDevice::setX(qint32 x) { moveTo(QPoint(x, m_d->y())); } void KisPaintDevice::setY(qint32 y) { moveTo(QPoint(m_d->x(), y)); } qint32 KisPaintDevice::x() const { return m_d->x(); } qint32 KisPaintDevice::y() const { return m_d->y(); } void KisPaintDevice::extent(qint32 &x, qint32 &y, qint32 &w, qint32 &h) const { QRect rc = extent(); x = rc.x(); y = rc.y(); w = rc.width(); h = rc.height(); } QRect KisPaintDevice::extent() const { return m_d->currentStrategy()->extent(); } QRegion KisPaintDevice::region() const { return m_d->currentStrategy()->region(); } QRect KisPaintDevice::nonDefaultPixelArea() const { return m_d->cache()->nonDefaultPixelArea(); } QRect KisPaintDevice::exactBounds() const { return m_d->cache()->exactBounds(); } QRect KisPaintDevice::exactBoundsAmortized() const { return m_d->cache()->exactBoundsAmortized(); } namespace Impl { struct CheckFullyTransparent { CheckFullyTransparent(const KoColorSpace *colorSpace) : m_colorSpace(colorSpace) { } bool isPixelEmpty(const quint8 *pixelData) { return m_colorSpace->opacityU8(pixelData) == OPACITY_TRANSPARENT_U8; } private: const KoColorSpace *m_colorSpace; }; struct CheckNonDefault { CheckNonDefault(int pixelSize, const quint8 *defaultPixel) : m_pixelSize(pixelSize), m_defaultPixel(defaultPixel) { } bool isPixelEmpty(const quint8 *pixelData) { return memcmp(m_defaultPixel, pixelData, m_pixelSize) == 0; } private: int m_pixelSize; const quint8 *m_defaultPixel; }; template QRect calculateExactBoundsImpl(const KisPaintDevice *device, const QRect &startRect, const QRect &endRect, ComparePixelOp compareOp) { if (startRect == endRect) return startRect; // the passed extent might have weird invalid structure that // can overflow integer precision when calling startRect.right() if (!startRect.isValid()) return QRect(); // Solution n°2 int x, y, w, h; int boundLeft, boundTop, boundRight, boundBottom; int endDirN, endDirE, endDirS, endDirW; startRect.getRect(&x, &y, &w, &h); if (endRect.isEmpty()) { endDirS = startRect.bottom(); endDirN = startRect.top(); endDirE = startRect.right(); endDirW = startRect.left(); startRect.getCoords(&boundLeft, &boundTop, &boundRight, &boundBottom); } else { endDirS = endRect.top() - 1; endDirN = endRect.bottom() + 1; endDirE = endRect.left() - 1; endDirW = endRect.right() + 1; endRect.getCoords(&boundLeft, &boundTop, &boundRight, &boundBottom); } // XXX: a small optimization is possible by using H/V line iterators in the first // and third cases, at the cost of making the code a bit more complex KisRandomConstAccessorSP accessor = device->createRandomConstAccessorNG(x, y); bool found = false; { for (qint32 y2 = y; y2 <= endDirS; ++y2) { for (qint32 x2 = x; x2 < x + w || found; ++ x2) { accessor->moveTo(x2, y2); if (!compareOp.isPixelEmpty(accessor->rawDataConst())) { boundTop = y2; found = true; break; } } if (found) break; } } /** * If the first pass hasn't found any opaque pixel, there is no * reason to check that 3 more times. They will not appear in the * meantime. Just return an empty bounding rect. */ if (!found && endRect.isEmpty()) { return QRect(); } found = false; for (qint32 y2 = y + h - 1; y2 >= endDirN ; --y2) { for (qint32 x2 = x + w - 1; x2 >= x || found; --x2) { accessor->moveTo(x2, y2); if (!compareOp.isPixelEmpty(accessor->rawDataConst())) { boundBottom = y2; found = true; break; } } if (found) break; } found = false; { for (qint32 x2 = x; x2 <= endDirE ; ++x2) { for (qint32 y2 = y; y2 < y + h || found; ++y2) { accessor->moveTo(x2, y2); if (!compareOp.isPixelEmpty(accessor->rawDataConst())) { boundLeft = x2; found = true; break; } } if (found) break; } } found = false; // Look for right edge ) { for (qint32 x2 = x + w - 1; x2 >= endDirW; --x2) { for (qint32 y2 = y + h - 1; y2 >= y || found; --y2) { accessor->moveTo(x2, y2); if (!compareOp.isPixelEmpty(accessor->rawDataConst())) { boundRight = x2; found = true; break; } } if (found) break; } } return QRect(boundLeft, boundTop, boundRight - boundLeft + 1, boundBottom - boundTop + 1); } } QRect KisPaintDevice::calculateExactBounds(bool nonDefaultOnly) const { QRect startRect = extent(); QRect endRect; quint8 defaultOpacity = defaultPixel().opacityU8(); if (defaultOpacity != OPACITY_TRANSPARENT_U8) { if (!nonDefaultOnly) { /** * We will calculate exact bounds only outside of the * image bounds, and that'll be nondefault area only. */ endRect = defaultBounds()->bounds(); nonDefaultOnly = true; } else { startRect = region().boundingRect(); } } if (nonDefaultOnly) { const KoColor defaultPixel = this->defaultPixel(); Impl::CheckNonDefault compareOp(pixelSize(), defaultPixel.data()); endRect = Impl::calculateExactBoundsImpl(this, startRect, endRect, compareOp); } else { Impl::CheckFullyTransparent compareOp(m_d->colorSpace()); endRect = Impl::calculateExactBoundsImpl(this, startRect, endRect, compareOp); } return endRect; } QRegion KisPaintDevice::regionExact() const { QRegion resultRegion; QVector rects = region().rects(); const KoColor defaultPixel = this->defaultPixel(); Impl::CheckNonDefault compareOp(pixelSize(), defaultPixel.data()); Q_FOREACH (const QRect &rc1, rects) { const int patchSize = 64; QVector smallerRects = KritaUtils::splitRectIntoPatches(rc1, QSize(patchSize, patchSize)); Q_FOREACH (const QRect &rc2, smallerRects) { const QRect result = Impl::calculateExactBoundsImpl(this, rc2, QRect(), compareOp); if (!result.isEmpty()) { resultRegion += result; } } } return resultRegion; } void KisPaintDevice::crop(qint32 x, qint32 y, qint32 w, qint32 h) { crop(QRect(x, y, w, h)); } void KisPaintDevice::crop(const QRect &rect) { m_d->currentStrategy()->crop(rect); } void KisPaintDevice::purgeDefaultPixels() { KisDataManagerSP dm = m_d->dataManager(); dm->purge(dm->extent()); } void KisPaintDevice::setDefaultPixel(const KoColor &defPixel) { KoColor color(defPixel); color.convertTo(colorSpace()); m_d->dataManager()->setDefaultPixel(color.data()); m_d->cache()->invalidate(); } KoColor KisPaintDevice::defaultPixel() const { return KoColor(m_d->dataManager()->defaultPixel(), colorSpace()); } void KisPaintDevice::clear() { m_d->dataManager()->clear(); m_d->cache()->invalidate(); } void KisPaintDevice::clear(const QRect & rc) { m_d->currentStrategy()->clear(rc); } void KisPaintDevice::fill(const QRect & rc, const KoColor &color) { KIS_ASSERT_RECOVER_RETURN(*color.colorSpace() == *colorSpace()); m_d->currentStrategy()->fill(rc, color.data()); } void KisPaintDevice::fill(qint32 x, qint32 y, qint32 w, qint32 h, const quint8 *fillPixel) { m_d->currentStrategy()->fill(QRect(x, y, w, h), fillPixel); } bool KisPaintDevice::write(KisPaintDeviceWriter &store) { return m_d->dataManager()->write(store); } bool KisPaintDevice::read(QIODevice *stream) { bool retval; retval = m_d->dataManager()->read(stream); m_d->cache()->invalidate(); return retval; } void KisPaintDevice::emitColorSpaceChanged() { emit colorSpaceChanged(m_d->colorSpace()); } void KisPaintDevice::emitProfileChanged() { emit profileChanged(m_d->colorSpace()->profile()); } KUndo2Command* KisPaintDevice::convertTo(const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { KUndo2Command *command = m_d->convertColorSpace(dstColorSpace, renderingIntent, conversionFlags); return command; } bool KisPaintDevice::setProfile(const KoColorProfile * profile) { return m_d->assignProfile(profile); } KisDataManagerSP KisPaintDevice::dataManager() const { return m_d->dataManager(); } void KisPaintDevice::convertFromQImage(const QImage& _image, const KoColorProfile *profile, qint32 offsetX, qint32 offsetY) { QImage image = _image; if (image.format() != QImage::Format_ARGB32) { image = image.convertToFormat(QImage::Format_ARGB32); } // Don't convert if not no profile is given and both paint dev and qimage are rgba. if (!profile && colorSpace()->id() == "RGBA") { writeBytes(image.constBits(), offsetX, offsetY, image.width(), image.height()); } else { try { quint8 * dstData = new quint8[image.width() * image.height() * pixelSize()]; KoColorSpaceRegistry::instance() ->colorSpace(RGBAColorModelID.id(), Integer8BitsColorDepthID.id(), profile) ->convertPixelsTo(image.constBits(), dstData, colorSpace(), image.width() * image.height(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); writeBytes(dstData, offsetX, offsetY, image.width(), image.height()); delete[] dstData; } catch (std::bad_alloc) { warnKrita << "KisPaintDevice::convertFromQImage: Could not allocate" << image.width() * image.height() * pixelSize() << "bytes"; return; } } m_d->cache()->invalidate(); } QImage KisPaintDevice::convertToQImage(const KoColorProfile *dstProfile, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const { qint32 x1; qint32 y1; qint32 w; qint32 h; QRect rc = exactBounds(); x1 = rc.x(); y1 = rc.y(); w = rc.width(); h = rc.height(); return convertToQImage(dstProfile, x1, y1, w, h, renderingIntent, conversionFlags); } QImage KisPaintDevice::convertToQImage(const KoColorProfile *dstProfile, const QRect &rc, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const { return convertToQImage(dstProfile, rc.x(), rc.y(), rc.width(), rc.height(), renderingIntent, conversionFlags); } QImage KisPaintDevice::convertToQImage(const KoColorProfile *dstProfile, qint32 x1, qint32 y1, qint32 w, qint32 h, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const { if (w < 0) return QImage(); if (h < 0) return QImage(); quint8 *data = 0; try { data = new quint8 [w * h * pixelSize()]; } catch (std::bad_alloc) { warnKrita << "KisPaintDevice::convertToQImage std::bad_alloc for " << w << " * " << h << " * " << pixelSize(); //delete[] data; // data is not allocated, so don't free it return QImage(); } Q_CHECK_PTR(data); // XXX: Is this really faster than converting line by line and building the QImage directly? // This copies potentially a lot of data. readBytes(data, x1, y1, w, h); QImage image = colorSpace()->convertToQImage(data, w, h, dstProfile, renderingIntent, conversionFlags); delete[] data; return image; } inline bool moveBy(KisSequentialConstIterator& iter, int numPixels) { int pos = 0; while (pos < numPixels) { int step = std::min(iter.nConseqPixels(), numPixels - pos); if (!iter.nextPixels(step)) return false; pos += step; } return true; } static KisPaintDeviceSP createThumbnailDeviceInternal(const KisPaintDevice* srcDev, qint32 srcX0, qint32 srcY0, qint32 srcWidth, qint32 srcHeight, qint32 w, qint32 h, QRect outputRect) { KisPaintDeviceSP thumbnail = new KisPaintDevice(srcDev->colorSpace()); qint32 pixelSize = srcDev->pixelSize(); KisRandomConstAccessorSP srcIter = srcDev->createRandomConstAccessorNG(0, 0); KisRandomAccessorSP dstIter = thumbnail->createRandomAccessorNG(0, 0); for (qint32 y = outputRect.y(); y < outputRect.y() + outputRect.height(); ++y) { qint32 iY = srcY0 + (y * srcHeight) / h; for (qint32 x = outputRect.x(); x < outputRect.x() + outputRect.width(); ++x) { qint32 iX = srcX0 + (x * srcWidth) / w; srcIter->moveTo(iX, iY); dstIter->moveTo(x, y); memcpy(dstIter->rawData(), srcIter->rawDataConst(), pixelSize); } } return thumbnail; } QSize fixThumbnailSize(QSize size) { if (!size.width() && size.height()) { size.setWidth(1); } if (size.width() && !size.height()) { size.setHeight(1); } return size; } KisPaintDeviceSP KisPaintDevice::createThumbnailDevice(qint32 w, qint32 h, QRect rect, QRect outputRect) const { QSize thumbnailSize(w, h); QRect imageRect = rect.isValid() ? rect : extent(); if ((thumbnailSize.width() > imageRect.width()) || (thumbnailSize.height() > imageRect.height())) { thumbnailSize.scale(imageRect.size(), Qt::KeepAspectRatio); } thumbnailSize = fixThumbnailSize(thumbnailSize); //can't create thumbnail for an empty device, e.g. layer thumbnail for empty image if (imageRect.isEmpty() || thumbnailSize.isEmpty()) { return new KisPaintDevice(colorSpace()); } int srcWidth, srcHeight; int srcX0, srcY0; imageRect.getRect(&srcX0, &srcY0, &srcWidth, &srcHeight); if (!outputRect.isValid()) { outputRect = QRect(0, 0, w, h); } KisPaintDeviceSP thumbnail = createThumbnailDeviceInternal(this, imageRect.x(), imageRect.y(), imageRect.width(), imageRect.height(), thumbnailSize.width(), thumbnailSize.height(), outputRect); return thumbnail; } KisPaintDeviceSP KisPaintDevice::createThumbnailDeviceOversampled(qint32 w, qint32 h, qreal oversample, QRect rect, QRect outputTileRect) const { QSize thumbnailSize(w, h); qreal oversampleAdjusted = qMax(oversample, 1.); QSize thumbnailOversampledSize = oversampleAdjusted * thumbnailSize; QRect outputRect; QRect imageRect = rect.isValid() ? rect : extent(); qint32 hstart = thumbnailOversampledSize.height(); if ((thumbnailOversampledSize.width() > imageRect.width()) || (thumbnailOversampledSize.height() > imageRect.height())) { thumbnailOversampledSize.scale(imageRect.size(), Qt::KeepAspectRatio); } thumbnailOversampledSize = fixThumbnailSize(thumbnailOversampledSize); //can't create thumbnail for an empty device, e.g. layer thumbnail for empty image if (imageRect.isEmpty() || thumbnailSize.isEmpty() || thumbnailOversampledSize.isEmpty()) { return new KisPaintDevice(colorSpace()); } oversampleAdjusted *= (hstart > 0) ? ((qreal)thumbnailOversampledSize.height() / hstart) : 1.; //readjusting oversample ratio, given that we had to adjust thumbnail size outputRect = QRect(0, 0, thumbnailOversampledSize.width(), thumbnailOversampledSize.height()); if (outputTileRect.isValid()) { //compensating output rectangle for oversampling outputTileRect = QRect(oversampleAdjusted * outputTileRect.topLeft(), oversampleAdjusted * outputTileRect.bottomRight()); outputRect = outputRect.intersected(outputTileRect); } KisPaintDeviceSP thumbnail = createThumbnailDeviceInternal(this, imageRect.x(), imageRect.y(), imageRect.width(), imageRect.height(), thumbnailOversampledSize.width(), thumbnailOversampledSize.height(), outputRect); if (oversample != 1. && oversampleAdjusted != 1.) { KoDummyUpdater updater; KisTransformWorker worker(thumbnail, 1 / oversampleAdjusted, 1 / oversampleAdjusted, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, &updater, KisFilterStrategyRegistry::instance()->value("Bilinear")); worker.run(); } return thumbnail; } QImage KisPaintDevice::createThumbnail(qint32 w, qint32 h, QRect rect, qreal oversample, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { QSize size = fixThumbnailSize(QSize(w, h)); KisPaintDeviceSP dev = createThumbnailDeviceOversampled(size.width(), size.height(), oversample, rect); QImage thumbnail = dev->convertToQImage(KoColorSpaceRegistry::instance()->rgb8()->profile(), 0, 0, w, h, renderingIntent, conversionFlags); return thumbnail; } QImage KisPaintDevice::createThumbnail(qint32 w, qint32 h, qreal oversample, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { QSize size = fixThumbnailSize(QSize(w, h)); return m_d->cache()->createThumbnail(size.width(), size.height(), oversample, renderingIntent, conversionFlags); } KisHLineIteratorSP KisPaintDevice::createHLineIteratorNG(qint32 x, qint32 y, qint32 w) { m_d->cache()->invalidate(); return m_d->currentStrategy()->createHLineIteratorNG(m_d->dataManager().data(), x, y, w, m_d->x(), m_d->y()); } KisHLineConstIteratorSP KisPaintDevice::createHLineConstIteratorNG(qint32 x, qint32 y, qint32 w) const { return m_d->currentStrategy()->createHLineConstIteratorNG(m_d->dataManager().data(), x, y, w, m_d->x(), m_d->y()); } KisVLineIteratorSP KisPaintDevice::createVLineIteratorNG(qint32 x, qint32 y, qint32 w) { m_d->cache()->invalidate(); return m_d->currentStrategy()->createVLineIteratorNG(x, y, w); } KisVLineConstIteratorSP KisPaintDevice::createVLineConstIteratorNG(qint32 x, qint32 y, qint32 w) const { return m_d->currentStrategy()->createVLineConstIteratorNG(x, y, w); } KisRepeatHLineConstIteratorSP KisPaintDevice::createRepeatHLineConstIterator(qint32 x, qint32 y, qint32 w, const QRect& _dataWidth) const { return new KisRepeatHLineConstIteratorNG(m_d->dataManager().data(), x, y, w, m_d->x(), m_d->y(), _dataWidth, m_d->cacheInvalidator()); } KisRepeatVLineConstIteratorSP KisPaintDevice::createRepeatVLineConstIterator(qint32 x, qint32 y, qint32 h, const QRect& _dataWidth) const { return new KisRepeatVLineConstIteratorNG(m_d->dataManager().data(), x, y, h, m_d->x(), m_d->y(), _dataWidth, m_d->cacheInvalidator()); } KisRandomAccessorSP KisPaintDevice::createRandomAccessorNG(qint32 x, qint32 y) { m_d->cache()->invalidate(); return m_d->currentStrategy()->createRandomAccessorNG(x, y); } KisRandomConstAccessorSP KisPaintDevice::createRandomConstAccessorNG(qint32 x, qint32 y) const { return m_d->currentStrategy()->createRandomConstAccessorNG(x, y); } KisRandomSubAccessorSP KisPaintDevice::createRandomSubAccessor() const { KisPaintDevice* pd = const_cast(this); return new KisRandomSubAccessor(pd); } void KisPaintDevice::clearSelection(KisSelectionSP selection) { const KoColorSpace *colorSpace = m_d->colorSpace(); QRect r = selection->selectedExactRect() & m_d->defaultBounds->bounds(); if (r.isValid()) { KisHLineIteratorSP devIt = createHLineIteratorNG(r.x(), r.y(), r.width()); KisHLineConstIteratorSP selectionIt = selection->projection()->createHLineConstIteratorNG(r.x(), r.y(), r.width()); const KoColor defaultPixel = this->defaultPixel(); bool transparentDefault = (defaultPixel.opacityU8() == OPACITY_TRANSPARENT_U8); for (qint32 y = 0; y < r.height(); y++) { do { // XXX: Optimize by using stretches colorSpace->applyInverseAlphaU8Mask(devIt->rawData(), selectionIt->rawDataConst(), 1); if (transparentDefault && colorSpace->opacityU8(devIt->rawData()) == OPACITY_TRANSPARENT_U8) { memcpy(devIt->rawData(), defaultPixel.data(), colorSpace->pixelSize()); } } while (devIt->nextPixel() && selectionIt->nextPixel()); devIt->nextRow(); selectionIt->nextRow(); } m_d->dataManager()->purge(r.translated(-m_d->x(), -m_d->y())); setDirty(r); } } bool KisPaintDevice::pixel(qint32 x, qint32 y, QColor *c) const { KisHLineConstIteratorSP iter = createHLineConstIteratorNG(x, y, 1); const quint8 *pix = iter->rawDataConst(); if (!pix) return false; colorSpace()->toQColor(pix, c); return true; } bool KisPaintDevice::pixel(qint32 x, qint32 y, KoColor * kc) const { KisHLineConstIteratorSP iter = createHLineConstIteratorNG(x, y, 1); const quint8 *pix = iter->rawDataConst(); if (!pix) return false; kc->setColor(pix, m_d->colorSpace()); return true; } bool KisPaintDevice::setPixel(qint32 x, qint32 y, const QColor& c) { KisHLineIteratorSP iter = createHLineIteratorNG(x, y, 1); colorSpace()->fromQColor(c, iter->rawData()); m_d->cache()->invalidate(); return true; } bool KisPaintDevice::setPixel(qint32 x, qint32 y, const KoColor& kc) { const quint8 * pix; KisHLineIteratorSP iter = createHLineIteratorNG(x, y, 1); if (kc.colorSpace() != m_d->colorSpace()) { KoColor kc2(kc, m_d->colorSpace()); pix = kc2.data(); memcpy(iter->rawData(), pix, m_d->colorSpace()->pixelSize()); } else { pix = kc.data(); memcpy(iter->rawData(), pix, m_d->colorSpace()->pixelSize()); } m_d->cache()->invalidate(); return true; } bool KisPaintDevice::fastBitBltPossible(KisPaintDeviceSP src) { return m_d->fastBitBltPossible(src); } void KisPaintDevice::fastBitBlt(KisPaintDeviceSP src, const QRect &rect) { m_d->currentStrategy()->fastBitBlt(src, rect); } void KisPaintDevice::fastBitBltOldData(KisPaintDeviceSP src, const QRect &rect) { m_d->currentStrategy()->fastBitBltOldData(src, rect); } void KisPaintDevice::fastBitBltRough(KisPaintDeviceSP src, const QRect &rect) { m_d->currentStrategy()->fastBitBltRough(src, rect); } void KisPaintDevice::fastBitBltRoughOldData(KisPaintDeviceSP src, const QRect &rect) { m_d->currentStrategy()->fastBitBltRoughOldData(src, rect); } void KisPaintDevice::readBytes(quint8 * data, qint32 x, qint32 y, qint32 w, qint32 h) const { readBytes(data, QRect(x, y, w, h)); } void KisPaintDevice::readBytes(quint8 *data, const QRect &rect) const { m_d->currentStrategy()->readBytes(data, rect); } void KisPaintDevice::writeBytes(const quint8 *data, qint32 x, qint32 y, qint32 w, qint32 h) { writeBytes(data, QRect(x, y, w, h)); } void KisPaintDevice::writeBytes(const quint8 *data, const QRect &rect) { m_d->currentStrategy()->writeBytes(data, rect); } QVector KisPaintDevice::readPlanarBytes(qint32 x, qint32 y, qint32 w, qint32 h) const { return m_d->currentStrategy()->readPlanarBytes(x, y, w, h); } void KisPaintDevice::writePlanarBytes(QVector planes, qint32 x, qint32 y, qint32 w, qint32 h) { m_d->currentStrategy()->writePlanarBytes(planes, x, y, w, h); } quint32 KisPaintDevice::pixelSize() const { quint32 _pixelSize = m_d->colorSpace()->pixelSize(); Q_ASSERT(_pixelSize > 0); return _pixelSize; } quint32 KisPaintDevice::channelCount() const { quint32 _channelCount = m_d->colorSpace()->channelCount(); Q_ASSERT(_channelCount > 0); return _channelCount; } KisRasterKeyframeChannel *KisPaintDevice::createKeyframeChannel(const KoID &id) { Q_ASSERT(!m_d->framesInterface); m_d->framesInterface.reset(new KisPaintDeviceFramesInterface(this)); Q_ASSERT(!m_d->contentChannel); m_d->contentChannel.reset(new KisRasterKeyframeChannel(id, this, m_d->defaultBounds)); // Raster channels always have at least one frame (representing a static image) KUndo2Command tempParentCommand; m_d->contentChannel->addKeyframe(0, &tempParentCommand); return m_d->contentChannel.data(); } KisRasterKeyframeChannel* KisPaintDevice::keyframeChannel() const { Q_ASSERT(m_d->contentChannel); return m_d->contentChannel.data(); } const KoColorSpace* KisPaintDevice::colorSpace() const { Q_ASSERT(m_d->colorSpace() != 0); return m_d->colorSpace(); } KisPaintDeviceSP KisPaintDevice::createCompositionSourceDevice() const { KisPaintDeviceSP device = new KisPaintDevice(compositionSourceColorSpace()); device->setDefaultBounds(defaultBounds()); return device; } KisPaintDeviceSP KisPaintDevice::createCompositionSourceDevice(KisPaintDeviceSP cloneSource) const { KisPaintDeviceSP clone = new KisPaintDevice(*cloneSource); clone->setDefaultBounds(defaultBounds()); clone->convertTo(compositionSourceColorSpace(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); return clone; } KisPaintDeviceSP KisPaintDevice::createCompositionSourceDevice(KisPaintDeviceSP cloneSource, const QRect roughRect) const { KisPaintDeviceSP clone = new KisPaintDevice(colorSpace()); clone->setDefaultBounds(defaultBounds()); clone->makeCloneFromRough(cloneSource, roughRect); clone->convertTo(compositionSourceColorSpace(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); return clone; } KisFixedPaintDeviceSP KisPaintDevice::createCompositionSourceDeviceFixed() const { return new KisFixedPaintDevice(compositionSourceColorSpace()); } const KoColorSpace* KisPaintDevice::compositionSourceColorSpace() const { return colorSpace(); } QVector KisPaintDevice::channelSizes() const { QVector sizes; QList channels = colorSpace()->channels(); std::sort(channels.begin(), channels.end()); Q_FOREACH (KoChannelInfo * channelInfo, channels) { sizes.append(channelInfo->size()); } return sizes; } KisPaintDevice::MemoryReleaseObject::~MemoryReleaseObject() { KisDataManager::releaseInternalPools(); } KisPaintDevice::MemoryReleaseObject* KisPaintDevice::createMemoryReleaseObject() { return new MemoryReleaseObject(); } KisPaintDevice::LodDataStruct::~LodDataStruct() { } QRegion KisPaintDevice::regionForLodSyncing() const { return m_d->regionForLodSyncing(); } KisPaintDevice::LodDataStruct* KisPaintDevice::createLodDataStruct(int lod) { return m_d->createLodDataStruct(lod); } void KisPaintDevice::updateLodDataStruct(LodDataStruct *dst, const QRect &srcRect) { m_d->updateLodDataStruct(dst, srcRect); } void KisPaintDevice::uploadLodDataStruct(LodDataStruct *dst) { m_d->uploadLodDataStruct(dst); } +void KisPaintDevice::generateLodCloneDevice(KisPaintDeviceSP dst, const QRect &originalRect, int lod) +{ + m_d->generateLodCloneDevice(dst, originalRect, lod); +} + KisPaintDeviceFramesInterface* KisPaintDevice::framesInterface() { return m_d->framesInterface.data(); } /******************************************************************/ /* KisPaintDeviceFramesInterface */ /******************************************************************/ KisPaintDeviceFramesInterface::KisPaintDeviceFramesInterface(KisPaintDevice *parentDevice) : q(parentDevice) { } QList KisPaintDeviceFramesInterface::frames() { return q->m_d->frameIds(); } int KisPaintDeviceFramesInterface::createFrame(bool copy, int copySrc, const QPoint &offset, KUndo2Command *parentCommand) { return q->m_d->createFrame(copy, copySrc, offset, parentCommand); } void KisPaintDeviceFramesInterface::deleteFrame(int frame, KUndo2Command *parentCommand) { return q->m_d->deleteFrame(frame, parentCommand); } void KisPaintDeviceFramesInterface::fetchFrame(int frameId, KisPaintDeviceSP targetDevice) { q->m_d->fetchFrame(frameId, targetDevice); } void KisPaintDeviceFramesInterface::uploadFrame(int srcFrameId, int dstFrameId, KisPaintDeviceSP srcDevice) { q->m_d->uploadFrame(srcFrameId, dstFrameId, srcDevice); } void KisPaintDeviceFramesInterface::uploadFrame(int dstFrameId, KisPaintDeviceSP srcDevice) { q->m_d->uploadFrame(dstFrameId, srcDevice); } QRect KisPaintDeviceFramesInterface::frameBounds(int frameId) { return q->m_d->frameBounds(frameId); } QPoint KisPaintDeviceFramesInterface::frameOffset(int frameId) const { return q->m_d->frameOffset(frameId); } void KisPaintDeviceFramesInterface::setFrameDefaultPixel(const KoColor &defPixel, int frameId) { KIS_ASSERT_RECOVER_RETURN(frameId >= 0); q->m_d->setFrameDefaultPixel(defPixel, frameId); } KoColor KisPaintDeviceFramesInterface::frameDefaultPixel(int frameId) const { KIS_ASSERT_RECOVER(frameId >= 0) { return KoColor(Qt::red, q->m_d->colorSpace()); } return q->m_d->frameDefaultPixel(frameId); } bool KisPaintDeviceFramesInterface::writeFrame(KisPaintDeviceWriter &store, int frameId) { KIS_ASSERT_RECOVER(frameId >= 0) { return false; } return q->m_d->writeFrame(store, frameId); } bool KisPaintDeviceFramesInterface::readFrame(QIODevice *stream, int frameId) { KIS_ASSERT_RECOVER(frameId >= 0) { return false; } return q->m_d->readFrame(stream, frameId); } int KisPaintDeviceFramesInterface::currentFrameId() const { return q->m_d->currentFrameId(); } KisDataManagerSP KisPaintDeviceFramesInterface::frameDataManager(int frameId) const { KIS_ASSERT_RECOVER(frameId >= 0) { return q->m_d->dataManager(); } return q->m_d->frameDataManager(frameId); } void KisPaintDeviceFramesInterface::invalidateFrameCache(int frameId) { KIS_ASSERT_RECOVER_RETURN(frameId >= 0); return q->m_d->invalidateFrameCache(frameId); } void KisPaintDeviceFramesInterface::setFrameOffset(int frameId, const QPoint &offset) { KIS_ASSERT_RECOVER_RETURN(frameId >= 0); return q->m_d->setFrameOffset(frameId, offset); } KisPaintDeviceFramesInterface::TestingDataObjects KisPaintDeviceFramesInterface::testingGetDataObjects() const { TestingDataObjects objects; objects.m_data = q->m_d->m_data.data(); objects.m_lodData = q->m_d->m_lodData.data(); objects.m_externalFrameData = q->m_d->m_externalFrameData.data(); typedef KisPaintDevice::Private::FramesHash FramesHash; FramesHash::const_iterator it = q->m_d->m_frames.constBegin(); FramesHash::const_iterator end = q->m_d->m_frames.constEnd(); for (; it != end; ++it) { objects.m_frames.insert(it.key(), it.value().data()); } objects.m_currentData = q->m_d->currentData(); return objects; } QList KisPaintDeviceFramesInterface::testingGetDataObjectsList() const { return q->m_d->allDataObjects(); } void KisPaintDevice::tesingFetchLodDevice(KisPaintDeviceSP targetDevice) { m_d->tesingFetchLodDevice(targetDevice); } diff --git a/libs/image/kis_paint_device.h b/libs/image/kis_paint_device.h index 674ee3138d..9a4c833391 100644 --- a/libs/image/kis_paint_device.h +++ b/libs/image/kis_paint_device.h @@ -1,878 +1,880 @@ /* * Copyright (c) 2002 patrick julien * Copyright (c) 2006 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_PAINT_DEVICE_IMPL_H_ #define KIS_PAINT_DEVICE_IMPL_H_ #include #include #include #include "kis_debug.h" #include #include "kis_types.h" #include "kis_shared.h" #include "kis_default_bounds_base.h" #include class KUndo2Command; class QRect; class QImage; class QPoint; class QString; class QColor; class QIODevice; class KoColor; class KoColorSpace; class KoColorProfile; class KisDataManager; class KisPaintDeviceWriter; class KisKeyframe; class KisRasterKeyframeChannel; class KisPaintDeviceFramesInterface; typedef KisSharedPtr KisDataManagerSP; namespace KritaUtils { enum DeviceCopyMode { CopySnapshot = 0, CopyAllFrames }; } /** * A paint device contains the actual pixel data and offers methods * to read and write pixels. A paint device has an integer x, y position * (it is not positioned on the image with sub-pixel accuracy). * A KisPaintDevice doesn't have any fixed size, the size changes dynamically * when pixels are accessed by an iterator. */ class KRITAIMAGE_EXPORT KisPaintDevice : public QObject , public KisShared { Q_OBJECT public: /** * Create a new paint device with the specified colorspace. * * @param colorSpace the colorspace of this paint device * @param name for debugging purposes */ explicit KisPaintDevice(const KoColorSpace * colorSpace, const QString& name = QString()); /** * Create a new paint device with the specified colorspace. The * parent node will be notified of changes to this paint device. * * @param parent the node that contains this paint device * @param colorSpace the colorspace of this paint device * @param defaultBounds boundaries of the device in case it is empty * @param name for debugging purposes */ KisPaintDevice(KisNodeWSP parent, const KoColorSpace * colorSpace, KisDefaultBoundsBaseSP defaultBounds = KisDefaultBoundsBaseSP(), const QString& name = QString()); /** * Creates a copy of this device. * * If \p copyMode is CopySnapshot, the newly created device clones the * current frame of \p rhs only (default and efficient * behavior). If \p copyFrames is CopyAllFrames, the new device is a deep * copy of the source with all the frames included. */ KisPaintDevice(const KisPaintDevice& rhs, KritaUtils::DeviceCopyMode copyMode = KritaUtils::CopySnapshot, KisNode *newParentNode = 0); ~KisPaintDevice() override; protected: /** * A special constructor for usage in KisPixelSelection. It allows * two paint devices to share a data manager. * * @param explicitDataManager data manager to use inside paint device * @param src source paint device to copy parameters from * @param name for debugging purposes */ KisPaintDevice(KisDataManagerSP explicitDataManager, KisPaintDeviceSP src, const QString& name = QString()); public: /** * Write the pixels of this paint device into the specified file store. */ bool write(KisPaintDeviceWriter &store); /** * Fill this paint device with the pixels from the specified file store. */ bool read(QIODevice *stream); public: /** * set the parent node of the paint device */ void setParentNode(KisNodeWSP parent); /** * set the default bounds for the paint device when * the default pixel is not completely transparent */ void setDefaultBounds(KisDefaultBoundsBaseSP bounds); /** * the default bounds rect of the paint device */ KisDefaultBoundsBaseSP defaultBounds() const; /** * Moves the device to these new coordinates (no incremental move) */ void moveTo(qint32 x, qint32 y); /** * Convenience method for the above. */ virtual void moveTo(const QPoint& pt); /** * Return an X,Y offset of the device in a convenient form */ QPoint offset() const; /** * The X offset of the paint device */ qint32 x() const; /** * The Y offset of the paint device */ qint32 y() const; /** * set the X offset of the paint device */ void setX(qint32 x); /** * set the Y offset of the paint device */ void setY(qint32 y); /** * Retrieve the bounds of the paint device. The size is not exact, * but may be larger if the underlying datamanager works that way. * For instance, the tiled datamanager keeps the extent to the nearest * multiple of 64. * * If default pixel is not transparent, then the actual extent * rect is united with the defaultBounds()->bounds() value * (the size of the image, usually). */ QRect extent() const; /// Convenience method for the above void extent(qint32 &x, qint32 &y, qint32 &w, qint32 &h) const; /** * Get the exact bounds of this paint device. The real solution is * very slow because it does a linear scanline search, but it * uses caching, so calling to this function without changing * the device is quite cheap. * * Exactbounds follows these rules: * *

    *
  • if default pixel is transparent, then exact bounds * of actual pixel data are returned *
  • if default pixel is not transparent, then the union * (defaultBounds()->bounds() | nonDefaultPixelArea()) is * returned *
* \see calculateExactBounds() */ QRect exactBounds() const; /** * Relaxed version of the exactBounds() that can be used in tight * loops. If the exact bounds value is present in the paint * device cache, returns this value. If the cache is invalidated, * returns extent() and tries to recalculate the exact bounds not * faster than once in 1000 ms. */ QRect exactBoundsAmortized() const; /** * Returns exact rectangle of the paint device that contains * non-default pixels. For paint devices with fully transparent * default pixel is equivalent to exactBounds(). * * nonDefaultPixelArea() follows these rules: * *
    *
  • if default pixel is transparent, then exact bounds * of actual pixel data are returned. The same as exactBounds() *
  • if default pixel is not transparent, then calculates the * rectangle of non-default pixels. May be smaller or greater * than image bounds *
* \see calculateExactBounds() */ QRect nonDefaultPixelArea() const; /** * Returns a rough approximation of region covered by device. * For tiled data manager, it region will consist of a number * of rects each corresponding to a tile. */ QRegion region() const; /** * The slow version of region() that searches for exact bounds of * each rectangle in the region */ QRegion regionExact() const; /** * Cut the paint device down to the specified rect. If the crop * area is bigger than the paint device, nothing will happen. */ void crop(qint32 x, qint32 y, qint32 w, qint32 h); /// Convenience method for the above void crop(const QRect & r); /** * Complete erase the current paint device. Its size will become 0. This * does not take the selection into account. */ virtual void clear(); /** * Clear the given rectangle to transparent black. The paint device will expand to * contain the given rect. */ void clear(const QRect & rc); /** * Frees the memory occupied by the pixels containing default * values. The extents() and exactBounds() of the image will * probably also shrink */ void purgeDefaultPixels(); /** * Sets the default pixel. New data will be initialised with this pixel. The pixel is copied: the * caller still owns the pointer and needs to delete it to avoid memory leaks. * If frame ID is given, set default pixel for that frame. Otherwise use active frame. */ void setDefaultPixel(const KoColor &defPixel); /** * Get a pointer to the default pixel. * If the frame parameter is given, get the default pixel of * specified frame. Otherwise use currently active frame. */ KoColor defaultPixel() const; /** * Fill the given rectangle with the given pixel. The paint device will expand to * contain the given rect. */ void fill(const QRect & rc, const KoColor &color); /** * Overloaded function. For legacy purposes only. * Please use fill(const QRect & rc, const KoColor &color) instead */ void fill(qint32 x, qint32 y, qint32 w, qint32 h, const quint8 *fillPixel); public: /** * Prepares the device for fastBitBlt opreration. It clears * the device, switches x,y shifts and colorspace if needed. * After this call fastBitBltPossible will return true. * May be used for initialization of temporary devices. */ void prepareClone(KisPaintDeviceSP src); /** * Make this device to become a clone of \a src. It will have the same * x,y shifts, colorspace and will share pixels inside \a rect. * After calling this function: * (this->extent() >= this->exactBounds() == rect). * * Rule of thumb: * * "Use makeCloneFrom() or makeCloneFromRough() if and only if you * are the only owner of the destination paint device and you are * 100% sure no other thread has access to it" */ void makeCloneFrom(KisPaintDeviceSP src, const QRect &rect); /** * Make this device to become a clone of \a src. It will have the same * x,y shifts, colorspace and will share pixels inside \a rect. * Be careful, this function will copy *at least* \a rect * of pixels. Actual copy area will be a bigger - it will * be aligned by tiles borders. So after calling this function: * (this->extent() == this->exactBounds() >= rect). * * Rule of thumb: * * "Use makeCloneFrom() or makeCloneFromRough() if and only if you * are the only owner of the destination paint device and you are * 100% sure no other thread has access to it" */ void makeCloneFromRough(KisPaintDeviceSP src, const QRect &minimalRect); protected: friend class KisPaintDeviceTest; friend class DataReaderThread; /** * Checks whether a src paint device can be used as source * of fast bitBlt operation. The result of the check may * depend on whether color spaces coinside, whether there is * any shift of tiles between the devices and etc. * * WARNING: This check must be done before performing any * fast bitBlt operation! * * \see fastBitBlt * \see fastBitBltRough */ bool fastBitBltPossible(KisPaintDeviceSP src); /** * Clones rect from another paint device. The cloned area will be * shared between both paint devices as much as possible using * copy-on-write. Parts of the rect that cannot be shared * (cross tiles) are deep-copied, * * \see fastBitBltPossible * \see fastBitBltRough */ void fastBitBlt(KisPaintDeviceSP src, const QRect &rect); /** * The same as \ref fastBitBlt() but reads old data */ void fastBitBltOldData(KisPaintDeviceSP src, const QRect &rect); /** * Clones rect from another paint device in a rough and fast way. * All the tiles touched by rect will be shared, between both * devices, that means it will copy a bigger area than was * requested. This method is supposed to be used for bitBlt'ing * into temporary paint devices. * * \see fastBitBltPossible * \see fastBitBlt */ void fastBitBltRough(KisPaintDeviceSP src, const QRect &rect); /** * The same as \ref fastBitBltRough() but reads old data */ void fastBitBltRoughOldData(KisPaintDeviceSP src, const QRect &rect); public: /** * Read the bytes representing the rectangle described by x, y, w, h into * data. If data is not big enough, Krita will gladly overwrite the rest * of your precious memory. * * Since this is a copy, you need to make sure you have enough memory. * * Reading from areas not previously initialized will read the default * pixel value into data but not initialize that region. */ void readBytes(quint8 * data, qint32 x, qint32 y, qint32 w, qint32 h) const; /** * Read the bytes representing the rectangle rect into * data. If data is not big enough, Krita will gladly overwrite the rest * of your precious memory. * * Since this is a copy, you need to make sure you have enough memory. * * Reading from areas not previously initialized will read the default * pixel value into data but not initialize that region. * @param data The address of the memory to receive the bytes read * @param rect The rectangle in the paint device to read from */ void readBytes(quint8 * data, const QRect &rect) const; /** * Copy the bytes in data into the rect specified by x, y, w, h. If the * data is too small or uninitialized, Krita will happily read parts of * memory you never wanted to be read. * * If the data is written to areas of the paint device not previously initialized, * the paint device will grow. */ void writeBytes(const quint8 * data, qint32 x, qint32 y, qint32 w, qint32 h); /** * Copy the bytes in data into the rectangle rect. If the * data is too small or uninitialized, Krita will happily read parts of * memory you never wanted to be read. * * If the data is written to areas of the paint device not previously initialized, * the paint device will grow. * @param data The address of the memory to write bytes from * @param rect The rectangle in the paint device to write to */ void writeBytes(const quint8 * data, const QRect &rect); /** * Copy the bytes in the paint device into a vector of arrays of bytes, * where the number of arrays is the number of channels in the * paint device. If the specified area is larger than the paint * device's extent, the default pixel will be read. */ QVector readPlanarBytes(qint32 x, qint32 y, qint32 w, qint32 h) const; /** * Write the data in the separate arrays to the channes. If there * are less vectors than channels, the remaining channels will not * be copied. If any of the arrays points to 0, the channel in * that location will not be touched. If the specified area is * larger than the paint device, the paint device will be * extended. There are no guards: if the area covers more pixels * than there are bytes in the arrays, krita will happily fill * your paint device with areas of memory you never wanted to be * read. Krita may also crash. * * XXX: what about undo? */ void writePlanarBytes(QVector planes, qint32 x, qint32 y, qint32 w, qint32 h); /** * Converts the paint device to a different colorspace * * @return a command that can be used to undo the conversion. */ KUndo2Command* convertTo(const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::internalConversionFlags()); /** * Changes the profile of the colorspace of this paint device to the given * profile. If the given profile is 0, nothing happens. */ bool setProfile(const KoColorProfile * profile); /** * Fill this paint device with the data from image; starting at (offsetX, offsetY) * @param srcProfileName name of the RGB profile to interpret the image as. 0 is interpreted as sRGB */ void convertFromQImage(const QImage& image, const KoColorProfile *profile, qint32 offsetX = 0, qint32 offsetY = 0); /** * Create an RGBA QImage from a rectangle in the paint device. * * @param x Left coordinate of the rectangle * @param y Top coordinate of the rectangle * @param w Width of the rectangle in pixels * @param h Height of the rectangle in pixels * @param dstProfile RGB profile to use in conversion. May be 0, in which * case it's up to the color strategy to choose a profile (most * like sRGB). */ QImage convertToQImage(const KoColorProfile *dstProfile, qint32 x, qint32 y, qint32 w, qint32 h, KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::internalConversionFlags()) const; /** * Overridden method for convenience */ QImage convertToQImage(const KoColorProfile *dstProfile, const QRect &rc, KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::internalConversionFlags()) const; /** * Create an RGBA QImage from a rectangle in the paint device. The * rectangle is defined by the parent image's bounds. * * @param dstProfile RGB profile to use in conversion. May be 0, in which * case it's up to the color strategy to choose a profile (most * like sRGB). */ QImage convertToQImage(const KoColorProfile * dstProfile, KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::internalConversionFlags()) const; /** * Creates a paint device thumbnail of the paint device, retaining * the aspect ratio. The width and height of the returned device * won't exceed \p maxw and \p maxw, but they may be smaller. * * @param maxw: maximum width * @param maxh: maximum height * @param rect: only this rect will be used for the thumbnail * */ KisPaintDeviceSP createThumbnailDevice(qint32 w, qint32 h, QRect rect = QRect(), QRect outputRect = QRect()) const; KisPaintDeviceSP createThumbnailDeviceOversampled(qint32 w, qint32 h, qreal oversample, QRect rect = QRect(), QRect outputRect = QRect()) const; /** * Creates a thumbnail of the paint device, retaining the aspect ratio. * The width and height of the returned QImage won't exceed \p maxw and \p maxw, but they may be smaller. * The colors are not corrected for display! * * @param maxw: maximum width * @param maxh: maximum height * @param rect: only this rect will be used for the thumbnail * @param oversample: ratio used for antialiasing */ QImage createThumbnail(qint32 maxw, qint32 maxh, QRect rect, qreal oversample = 1, KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::internalConversionFlags()); /** * Cached version of createThumbnail(qint32 maxw, qint32 maxh, const KisSelection *selection, QRect rect) */ QImage createThumbnail(qint32 maxw, qint32 maxh, qreal oversample = 1, KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::internalConversionFlags()); /** * Fill c and opacity with the values found at x and y. * * The color values will be transformed from the profile of * this paint device to the display profile. * * @return true if the operation was successful. */ bool pixel(qint32 x, qint32 y, QColor *c) const; /** * Fill kc with the values found at x and y. This method differs * from the above in using KoColor, which can be of any colorspace * * The color values will be transformed from the profile of * this paint device to the display profile. * * @return true if the operation was successful. */ bool pixel(qint32 x, qint32 y, KoColor * kc) const; /** * Set the specified pixel to the specified color. Note that this * bypasses KisPainter. the PaintDevice is here used as an equivalent * to QImage, not QPixmap. This means that this is not undoable; also, * there is no compositing with an existing value at this location. * * The color values will be transformed from the display profile to * the paint device profile. * * Note that this will use 8-bit values and may cause a significant * degradation when used on 16-bit or hdr quality images. * * @return true if the operation was successful */ bool setPixel(qint32 x, qint32 y, const QColor& c); /// Convenience method for the above bool setPixel(qint32 x, qint32 y, const KoColor& kc); /** * @return the colorspace of the pixels in this paint device */ const KoColorSpace* colorSpace() const; /** * There is quite a common technique in Krita. It is used in * cases, when we want to paint something over a paint device * using the composition, opacity or selection. E.g. painting a * dab in a paint op, filling the selection in the Fill Tool. * Such work is usually done in the following way: * * 1) Create a paint device * * 2) Fill it with the desired color or data * * 3) Create a KisPainter and set all the properties of the * transaction: selection, compositeOp, opacity and etc. * * 4) Paint a newly created paint device over the destination * device. * * The following two methods (createCompositionSourceDevice() or * createCompositionSourceDeviceFixed())should be used for the * accomplishing the step 1). The point is that the desired color * space of the temporary device may not coincide with the color * space of the destination. That is the case, for example, for * the alpha8() colorspace used in the selections. So for such * devices the temporary target would have a different (grayscale) * color space. * * So there are two rules of thumb: * * 1) If you need a temporary device which you are going to fill * with some data and then paint over the paint device, create * it with either createCompositionSourceDevice() or * createCompositionSourceDeviceFixed(). * * 2) Do *not* expect that the color spaces of the destination and * the temporary device would coincide. If you need to copy a * single pixel from one device to another, you can use * KisCrossDeviceColorPicker class, that will handle all the * necessary conversions for you. * * \see createCompositionSourceDeviceFixed() * \see compositionSourceColorSpace() * \see KisCrossDeviceColorPicker * \see KisCrossDeviceColorPickerInt */ KisPaintDeviceSP createCompositionSourceDevice() const; /** * The same as createCompositionSourceDevice(), but initializes the * newly created device with the content of \p cloneSource * * \see createCompositionSourceDevice() */ KisPaintDeviceSP createCompositionSourceDevice(KisPaintDeviceSP cloneSource) const; /** * The same as createCompositionSourceDevice(), but initializes * the newly created device with the *rough* \p roughRect of * \p cloneSource. * * "Rough rect" means that it may copy a bit more than * requested. It is expected that the caller will not use the area * outside \p roughRect. * * \see createCompositionSourceDevice() */ KisPaintDeviceSP createCompositionSourceDevice(KisPaintDeviceSP cloneSource, const QRect roughRect) const; /** * This is a convenience method for createCompositionSourceDevice() * * \see createCompositionSourceDevice() */ KisFixedPaintDeviceSP createCompositionSourceDeviceFixed() const; /** * This is a lowlevel method for the principle used in * createCompositionSourceDevice(). In most of the cases the paint * device creation methods should be used instead of this function. * * \see createCompositionSourceDevice() * \see createCompositionSourceDeviceFixed() */ virtual const KoColorSpace* compositionSourceColorSpace() const; /** * @return the internal datamanager that keeps the pixels. */ KisDataManagerSP dataManager() const; /** * Replace the pixel data, color strategy, and profile. */ void setDataManager(KisDataManagerSP data, const KoColorSpace * colorSpace = 0); /** * Return the number of bytes a pixel takes. */ quint32 pixelSize() const; /** * Return the number of channels a pixel takes */ quint32 channelCount() const; /** * Create a keyframe channel for the content on this device. * @param id identifier for the channel * @param node the parent node for the channel * @return keyframe channel */ KisRasterKeyframeChannel *createKeyframeChannel(const KoID &id); KisRasterKeyframeChannel* keyframeChannel() const; /** * An interface to modify/load/save frames stored inside this device */ KisPaintDeviceFramesInterface* framesInterface(); public: /** * Add the specified rect to the parent layer's set of dirty rects * (if there is a parent layer) */ void setDirty(const QRect & rc); /** * Add the specified region to the parent layer's dirty region * (if there is a parent layer) */ void setDirty(const QRegion & region); /** * Set the parent layer completely dirty, if this paint device has * as parent layer. */ void setDirty(); void setDirty(const QVector rects); /** * Called by KisTransactionData when it thinks current time should * be changed. And the requests is forwarded to the image if * needed. */ void requestTimeSwitch(int time); /** * \return a sequence number corresponding to the current paint * device state. Every time the paint device is changed, * the sequence number is increased */ int sequenceNumber() const; void estimateMemoryStats(qint64 &imageData, qint64 &temporaryData, qint64 &lodData) const; public: KisHLineIteratorSP createHLineIteratorNG(qint32 x, qint32 y, qint32 w); KisHLineConstIteratorSP createHLineConstIteratorNG(qint32 x, qint32 y, qint32 w) const; KisVLineIteratorSP createVLineIteratorNG(qint32 x, qint32 y, qint32 h); KisVLineConstIteratorSP createVLineConstIteratorNG(qint32 x, qint32 y, qint32 h) const; KisRandomAccessorSP createRandomAccessorNG(qint32 x, qint32 y); KisRandomConstAccessorSP createRandomConstAccessorNG(qint32 x, qint32 y) const; /** * Create an iterator that will "artificially" extend the paint device with the * value of the border when trying to access values outside the range of data. * * @param rc indicates the rectangle that truly contains data */ KisRepeatHLineConstIteratorSP createRepeatHLineConstIterator(qint32 x, qint32 y, qint32 w, const QRect& _dataWidth) const; /** * Create an iterator that will "artificially" extend the paint device with the * value of the border when trying to access values outside the range of data. * * @param rc indicates the rectangle that trully contains data */ KisRepeatVLineConstIteratorSP createRepeatVLineConstIterator(qint32 x, qint32 y, qint32 h, const QRect& _dataWidth) const; /** * This function create a random accessor which can easily access to sub pixel values. * @param selection an up-to-date selection that has the same origin as the paint device */ KisRandomSubAccessorSP createRandomSubAccessor() const; /** Clear the selected pixels from the paint device */ void clearSelection(KisSelectionSP selection); Q_SIGNALS: void profileChanged(const KoColorProfile * profile); void colorSpaceChanged(const KoColorSpace *colorspace); public: friend class PaintDeviceCache; /** * Caclculates exact bounds of the device. Used internally * by a transparent caching system. The solution is very slow * because it does a linear scanline search. So the complexity * is n*n at worst. * * \see exactBounds(), nonDefaultPixelArea() */ QRect calculateExactBounds(bool nonDefaultOnly) const; public: struct MemoryReleaseObject : public QObject { ~MemoryReleaseObject() override; }; static MemoryReleaseObject* createMemoryReleaseObject(); public: struct LodDataStruct { virtual ~LodDataStruct(); }; QRegion regionForLodSyncing() const; LodDataStruct* createLodDataStruct(int lod); void updateLodDataStruct(LodDataStruct *dst, const QRect &srcRect); void uploadLodDataStruct(LodDataStruct *dst); + void generateLodCloneDevice(KisPaintDeviceSP dst, const QRect &originalRect, int lod); + void setProjectionDevice(bool value); void tesingFetchLodDevice(KisPaintDeviceSP targetDevice); private: KisPaintDevice& operator=(const KisPaintDevice&); void init(const KoColorSpace *colorSpace, KisDefaultBoundsBaseSP defaultBounds, KisNodeWSP parent, const QString& name); // Only KisPainter is allowed to have access to these low-level methods friend class KisPainter; /** * Return a vector with in order the size in bytes of the channels * in the colorspace of this paint device. */ QVector channelSizes() const; void emitColorSpaceChanged(); void emitProfileChanged(); private: friend class KisPaintDeviceFramesInterface; protected: friend class KisSelectionTest; KisNodeWSP parentNode() const; private: struct Private; Private * const m_d; }; #endif // KIS_PAINT_DEVICE_IMPL_H_ diff --git a/libs/image/lazybrush/kis_colorize_mask.cpp b/libs/image/lazybrush/kis_colorize_mask.cpp index 6e79443ad8..079f4b1585 100644 --- a/libs/image/lazybrush/kis_colorize_mask.cpp +++ b/libs/image/lazybrush/kis_colorize_mask.cpp @@ -1,1167 +1,1167 @@ /* * 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_colorize_mask.h" #include #include #include #include "kis_pixel_selection.h" #include "kis_icon_utils.h" #include "kis_node_visitor.h" #include "kis_processing_visitor.h" #include "kis_painter.h" #include "kis_fill_painter.h" #include "kis_lazy_fill_tools.h" #include "kis_cached_paint_device.h" #include "kis_paint_device_debug_utils.h" #include "kis_layer_properties_icons.h" -#include "kis_signal_compressor.h" +#include "kis_thread_safe_signal_compressor.h" #include "kis_colorize_stroke_strategy.h" #include "kis_multiway_cut.h" #include "kis_image.h" #include "kis_layer.h" #include "kis_macro_based_undo_store.h" #include "kis_post_execution_undo_adapter.h" #include "kis_command_utils.h" #include "kis_processing_applicator.h" #include "krita_utils.h" using namespace KisLazyFillTools; struct KisColorizeMask::Private { Private(KisColorizeMask *_q) : q(_q), coloringProjection(new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8())), fakePaintDevice(new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8())), filteredSource(new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8())), needAddCurrentKeyStroke(false), showKeyStrokes(true), showColoring(true), needsUpdate(true), originalSequenceNumber(-1), - updateCompressor(1000, KisSignalCompressor::FIRST_ACTIVE_POSTPONE_NEXT, _q), - dirtyParentUpdateCompressor(200, KisSignalCompressor::FIRST_ACTIVE_POSTPONE_NEXT, _q), - prefilterRecalculationCompressor(1000, KisSignalCompressor::POSTPONE, _q), + updateCompressor(1000, KisSignalCompressor::FIRST_ACTIVE_POSTPONE_NEXT), + dirtyParentUpdateCompressor(200, KisSignalCompressor::FIRST_ACTIVE_POSTPONE_NEXT), + prefilterRecalculationCompressor(1000, KisSignalCompressor::POSTPONE), updateIsRunning(false), filteringOptions(false, 4.0, 15, 0.7), limitToDeviceBounds(false) { } Private(const Private &rhs, KisColorizeMask *_q) : q(_q), coloringProjection(new KisPaintDevice(*rhs.coloringProjection)), fakePaintDevice(new KisPaintDevice(*rhs.fakePaintDevice)), filteredSource(new KisPaintDevice(*rhs.filteredSource)), filteredDeviceBounds(rhs.filteredDeviceBounds), needAddCurrentKeyStroke(rhs.needAddCurrentKeyStroke), showKeyStrokes(rhs.showKeyStrokes), showColoring(rhs.showColoring), needsUpdate(false), originalSequenceNumber(-1), - updateCompressor(1000, KisSignalCompressor::FIRST_ACTIVE_POSTPONE_NEXT, _q), - dirtyParentUpdateCompressor(200, KisSignalCompressor::FIRST_ACTIVE_POSTPONE_NEXT, _q), - prefilterRecalculationCompressor(1000, KisSignalCompressor::POSTPONE, _q), + updateCompressor(1000, KisSignalCompressor::FIRST_ACTIVE_POSTPONE_NEXT), + dirtyParentUpdateCompressor(200, KisSignalCompressor::FIRST_ACTIVE_POSTPONE_NEXT), + prefilterRecalculationCompressor(1000, KisSignalCompressor::POSTPONE), offset(rhs.offset), updateIsRunning(false), filteringOptions(rhs.filteringOptions), limitToDeviceBounds(rhs.limitToDeviceBounds) { Q_FOREACH (const KeyStroke &stroke, rhs.keyStrokes) { keyStrokes << KeyStroke(KisPaintDeviceSP(new KisPaintDevice(*stroke.dev)), stroke.color, stroke.isTransparent); } } KisColorizeMask *q = 0; QList keyStrokes; KisPaintDeviceSP coloringProjection; KisPaintDeviceSP fakePaintDevice; KisPaintDeviceSP filteredSource; QRect filteredDeviceBounds; KoColor currentColor; KisPaintDeviceSP currentKeyStrokeDevice; bool needAddCurrentKeyStroke; bool showKeyStrokes; bool showColoring; KisCachedSelection cachedSelection; KisCachedSelection cachedConversionSelection; bool needsUpdate; int originalSequenceNumber; - KisSignalCompressor updateCompressor; - KisSignalCompressor dirtyParentUpdateCompressor; - KisSignalCompressor prefilterRecalculationCompressor; + KisThreadSafeSignalCompressor updateCompressor; + KisThreadSafeSignalCompressor dirtyParentUpdateCompressor; + KisThreadSafeSignalCompressor prefilterRecalculationCompressor; QPoint offset; bool updateIsRunning; QStack extentBeforeUpdateStart; FilteringOptions filteringOptions; bool filteringDirty = true; bool limitToDeviceBounds = false; bool filteredSourceValid(KisPaintDeviceSP parentDevice) { return !filteringDirty && originalSequenceNumber == parentDevice->sequenceNumber(); } void setNeedsUpdateImpl(bool value, bool requestedByUser); bool shouldShowFilteredSource() const; bool shouldShowColoring() const; }; KisColorizeMask::KisColorizeMask() : m_d(new Private(this)) { connect(&m_d->updateCompressor, SIGNAL(timeout()), SLOT(slotUpdateRegenerateFilling())); connect(this, SIGNAL(sigUpdateOnDirtyParent()), &m_d->dirtyParentUpdateCompressor, SLOT(start())); connect(&m_d->dirtyParentUpdateCompressor, SIGNAL(timeout()), SLOT(slotUpdateOnDirtyParent())); connect(&m_d->prefilterRecalculationCompressor, SIGNAL(timeout()), SLOT(slotRecalculatePrefilteredImage())); m_d->updateCompressor.moveToThread(qApp->thread()); } KisColorizeMask::~KisColorizeMask() { } KisColorizeMask::KisColorizeMask(const KisColorizeMask& rhs) : KisEffectMask(rhs), m_d(new Private(*rhs.m_d, this)) { connect(&m_d->updateCompressor, SIGNAL(timeout()), SLOT(slotUpdateRegenerateFilling())); connect(this, SIGNAL(sigUpdateOnDirtyParent()), &m_d->dirtyParentUpdateCompressor, SLOT(start())); connect(&m_d->dirtyParentUpdateCompressor, SIGNAL(timeout()), SLOT(slotUpdateOnDirtyParent())); m_d->updateCompressor.moveToThread(qApp->thread()); } void KisColorizeMask::initializeCompositeOp() { KisLayerSP parentLayer(qobject_cast(parent().data())); if (!parentLayer || !parentLayer->original()) return; KisImageSP image = parentLayer->image(); if (!image) return; const qreal samplePortion = 0.1; const qreal alphaPortion = KritaUtils::estimatePortionOfTransparentPixels(parentLayer->original(), image->bounds(), samplePortion); setCompositeOpId(alphaPortion > 0.3 ? COMPOSITE_BEHIND : COMPOSITE_MULT); } const KoColorSpace* KisColorizeMask::colorSpace() const { return m_d->fakePaintDevice->colorSpace(); } struct SetKeyStrokesColorSpaceCommand : public KUndo2Command { SetKeyStrokesColorSpaceCommand(const KoColorSpace *dstCS, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags, QList *list, KisColorizeMaskSP node) : m_dstCS(dstCS), m_renderingIntent(renderingIntent), m_conversionFlags(conversionFlags), m_list(list), m_node(node) {} void undo() override { KIS_ASSERT_RECOVER_RETURN(m_list->size() == m_oldColors.size()); for (int i = 0; i < m_list->size(); i++) { (*m_list)[i].color = m_oldColors[i]; } m_node->setNeedsUpdate(true); } void redo() override { if (m_oldColors.isEmpty()) { Q_FOREACH(const KeyStroke &stroke, *m_list) { m_oldColors << stroke.color; m_newColors << stroke.color; m_newColors.last().convertTo(m_dstCS, m_renderingIntent, m_conversionFlags); } } KIS_ASSERT_RECOVER_RETURN(m_list->size() == m_newColors.size()); for (int i = 0; i < m_list->size(); i++) { (*m_list)[i].color = m_newColors[i]; } m_node->setNeedsUpdate(true); } private: QVector m_oldColors; QVector m_newColors; const KoColorSpace *m_dstCS; KoColorConversionTransformation::Intent m_renderingIntent; KoColorConversionTransformation::ConversionFlags m_conversionFlags; QList *m_list; KisColorizeMaskSP m_node; }; void KisColorizeMask::setProfile(const KoColorProfile *profile) { // WARNING: there is no undo information, used only while loading! m_d->fakePaintDevice->setProfile(profile); m_d->coloringProjection->setProfile(profile); for (auto stroke : m_d->keyStrokes) { stroke.color.setProfile(profile); } } KUndo2Command* KisColorizeMask::setColorSpace(const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { using namespace KisCommandUtils; CompositeCommand *composite = new CompositeCommand(); composite->addCommand(m_d->fakePaintDevice->convertTo(dstColorSpace, renderingIntent, conversionFlags)); composite->addCommand(m_d->coloringProjection->convertTo(dstColorSpace, renderingIntent, conversionFlags)); KUndo2Command *strokesConversionCommand = new SetKeyStrokesColorSpaceCommand( dstColorSpace, renderingIntent, conversionFlags, &m_d->keyStrokes, KisColorizeMaskSP(this)); strokesConversionCommand->redo(); composite->addCommand(new SkipFirstRedoWrapper(strokesConversionCommand)); return composite; } bool KisColorizeMask::needsUpdate() const { return m_d->needsUpdate; } void KisColorizeMask::setNeedsUpdate(bool value) { m_d->setNeedsUpdateImpl(value, true); } void KisColorizeMask::Private::setNeedsUpdateImpl(bool value, bool requestedByUser) { if (value != needsUpdate) { needsUpdate = value; q->baseNodeChangedCallback(); if (!value && requestedByUser) { updateCompressor.start(); } } } void KisColorizeMask::slotUpdateRegenerateFilling(bool prefilterOnly) { KisPaintDeviceSP src = parent()->original(); KIS_ASSERT_RECOVER_RETURN(src); const bool filteredSourceValid = m_d->filteredSourceValid(src); m_d->originalSequenceNumber = src->sequenceNumber(); m_d->filteringDirty = false; if (!prefilterOnly) { m_d->coloringProjection->clear(); } KisLayerSP parentLayer(qobject_cast(parent().data())); if (!parentLayer) return; KisImageSP image = parentLayer->image(); if (image) { m_d->updateIsRunning = true; QRect fillBounds; if (m_d->limitToDeviceBounds) { fillBounds |= src->exactBounds(); Q_FOREACH (const KeyStroke &stroke, m_d->keyStrokes) { fillBounds |= stroke.dev->exactBounds(); } fillBounds &= image->bounds(); } else { fillBounds = image->bounds(); } m_d->filteredDeviceBounds = fillBounds; KisColorizeStrokeStrategy *strategy = new KisColorizeStrokeStrategy(src, m_d->coloringProjection, m_d->filteredSource, filteredSourceValid, fillBounds, this, prefilterOnly); strategy->setFilteringOptions(m_d->filteringOptions); Q_FOREACH (const KeyStroke &stroke, m_d->keyStrokes) { const KoColor color = !stroke.isTransparent ? stroke.color : KoColor(Qt::transparent, stroke.color.colorSpace()); strategy->addKeyStroke(stroke.dev, color); } m_d->extentBeforeUpdateStart.push(extent()); connect(strategy, SIGNAL(sigFinished(bool)), SLOT(slotRegenerationFinished(bool))); connect(strategy, SIGNAL(sigCancelled()), SLOT(slotRegenerationCancelled())); KisStrokeId id = image->startStroke(strategy); image->endStroke(id); } } void KisColorizeMask::slotUpdateOnDirtyParent() { KIS_ASSERT_RECOVER_RETURN(parent()); KisPaintDeviceSP src = parent()->original(); KIS_ASSERT_RECOVER_RETURN(src); if (!m_d->filteredSourceValid(src)) { const QRect &oldExtent = extent(); m_d->setNeedsUpdateImpl(true, false); m_d->filteringDirty = true; setDirty(oldExtent | extent()); } } void KisColorizeMask::slotRecalculatePrefilteredImage() { slotUpdateRegenerateFilling(true); } void KisColorizeMask::slotRegenerationFinished(bool prefilterOnly) { m_d->updateIsRunning = false; if (!prefilterOnly) { m_d->setNeedsUpdateImpl(false, false); } QRect oldExtent; if (!m_d->extentBeforeUpdateStart.isEmpty()) { oldExtent = m_d->extentBeforeUpdateStart.pop(); } else { KIS_SAFE_ASSERT_RECOVER_NOOP(!m_d->extentBeforeUpdateStart.isEmpty()); // always fail! } setDirty(oldExtent | extent()); } void KisColorizeMask::slotRegenerationCancelled() { slotRegenerationFinished(true); } KisBaseNode::PropertyList KisColorizeMask::sectionModelProperties() const { KisBaseNode::PropertyList l = KisMask::sectionModelProperties(); l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::colorizeNeedsUpdate, needsUpdate()); l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::colorizeEditKeyStrokes, showKeyStrokes()); l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::colorizeShowColoring, showColoring()); return l; } void KisColorizeMask::setSectionModelProperties(const KisBaseNode::PropertyList &properties) { KisMask::setSectionModelProperties(properties); Q_FOREACH (const KisBaseNode::Property &property, properties) { if (property.id == KisLayerPropertiesIcons::colorizeNeedsUpdate.id()) { if (m_d->needsUpdate && m_d->needsUpdate != property.state.toBool()) { setNeedsUpdate(property.state.toBool()); } } if (property.id == KisLayerPropertiesIcons::colorizeEditKeyStrokes.id()) { if (m_d->showKeyStrokes != property.state.toBool()) { setShowKeyStrokes(property.state.toBool()); } } if (property.id == KisLayerPropertiesIcons::colorizeShowColoring.id()) { if (m_d->showColoring != property.state.toBool()) { setShowColoring(property.state.toBool()); } } } } KisPaintDeviceSP KisColorizeMask::paintDevice() const { return m_d->showKeyStrokes && !m_d->updateIsRunning ? m_d->fakePaintDevice : KisPaintDeviceSP(); } KisPaintDeviceSP KisColorizeMask::coloringProjection() const { return m_d->coloringProjection; } KisPaintDeviceSP KisColorizeMask::colorPickSourceDevice() const { return m_d->shouldShowColoring() && !m_d->coloringProjection->extent().isEmpty() ? m_d->coloringProjection : projection(); } QIcon KisColorizeMask::icon() const { return KisIconUtils::loadIcon("colorizeMask"); } bool KisColorizeMask::accept(KisNodeVisitor &v) { return v.visit(this); } void KisColorizeMask::accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter) { return visitor.visit(this, undoAdapter); } bool KisColorizeMask::Private::shouldShowFilteredSource() const { return !updateIsRunning && showKeyStrokes && !filteringDirty && filteredSource && !filteredSource->extent().isEmpty(); } bool KisColorizeMask::Private::shouldShowColoring() const { return !updateIsRunning && showColoring && coloringProjection; } QRect KisColorizeMask::decorateRect(KisPaintDeviceSP &src, KisPaintDeviceSP &dst, const QRect &rect, PositionToFilthy maskPos) const { Q_UNUSED(maskPos); if (maskPos == N_ABOVE_FILTHY) { // the source layer has changed, we should update the filtered cache! if (!m_d->filteringDirty) { emit sigUpdateOnDirtyParent(); } } KIS_ASSERT(dst != src); // Draw the filling and the original layer { KisPainter gc(dst); if (m_d->shouldShowFilteredSource()) { const QRect drawRect = m_d->limitToDeviceBounds ? rect & m_d->filteredDeviceBounds : rect; gc.setOpacity(128); gc.bitBlt(drawRect.topLeft(), m_d->filteredSource, drawRect); } else { gc.setOpacity(255); gc.bitBlt(rect.topLeft(), src, rect); } if (m_d->shouldShowColoring()) { gc.setOpacity(opacity()); gc.setCompositeOp(compositeOpId()); gc.bitBlt(rect.topLeft(), m_d->coloringProjection, rect); } } // Draw the key strokes if (m_d->showKeyStrokes) { KisIndirectPaintingSupport::ReadLocker locker(this); KisSelectionSP selection = m_d->cachedSelection.getSelection(); KisSelectionSP conversionSelection = m_d->cachedConversionSelection.getSelection(); KisPixelSelectionSP tempSelection = conversionSelection->pixelSelection(); KisPaintDeviceSP temporaryTarget = this->temporaryTarget(); const bool isTemporaryTargetErasing = temporaryCompositeOp() == COMPOSITE_ERASE; const QRect temporaryExtent = temporaryTarget ? temporaryTarget->extent() : QRect(); KisFillPainter gc(dst); QList extendedStrokes = m_d->keyStrokes; if (m_d->currentKeyStrokeDevice && m_d->needAddCurrentKeyStroke && !isTemporaryTargetErasing) { extendedStrokes << KeyStroke(m_d->currentKeyStrokeDevice, m_d->currentColor); } Q_FOREACH (const KeyStroke &stroke, extendedStrokes) { selection->pixelSelection()->makeCloneFromRough(stroke.dev, rect); gc.setSelection(selection); if (stroke.color == m_d->currentColor || (isTemporaryTargetErasing && temporaryExtent.intersects(selection->pixelSelection()->selectedRect()))) { if (temporaryTarget) { tempSelection->copyAlphaFrom(temporaryTarget, rect); KisPainter selectionPainter(selection->pixelSelection()); setupTemporaryPainter(&selectionPainter); selectionPainter.bitBlt(rect.topLeft(), tempSelection, rect); } } gc.fillSelection(rect, stroke.color); } m_d->cachedSelection.putSelection(selection); m_d->cachedSelection.putSelection(conversionSelection); } return rect; } struct DeviceExtentPolicy { inline QRect operator() (const KisPaintDevice *dev) { return dev->extent(); } }; struct DeviceExactBoundsPolicy { inline QRect operator() (const KisPaintDevice *dev) { return dev->exactBounds(); } }; template QRect KisColorizeMask::calculateMaskBounds(DeviceMetricPolicy boundsPolicy) const { QRect rc; if (m_d->shouldShowFilteredSource()) { rc |= boundsPolicy(m_d->filteredSource); } if (m_d->shouldShowColoring()) { rc |= boundsPolicy(m_d->coloringProjection); } if (m_d->showKeyStrokes) { Q_FOREACH (const KeyStroke &stroke, m_d->keyStrokes) { rc |= boundsPolicy(stroke.dev); } KisIndirectPaintingSupport::ReadLocker locker(this); KisPaintDeviceSP temporaryTarget = this->temporaryTarget(); if (temporaryTarget) { rc |= boundsPolicy(temporaryTarget); } } return rc; } QRect KisColorizeMask::extent() const { return calculateMaskBounds(DeviceExtentPolicy()); } QRect KisColorizeMask::exactBounds() const { return calculateMaskBounds(DeviceExactBoundsPolicy()); } QRect KisColorizeMask::nonDependentExtent() const { return extent(); } KisImageSP KisColorizeMask::fetchImage() const { KisLayerSP parentLayer(qobject_cast(parent().data())); if (!parentLayer) return KisImageSP(); return parentLayer->image(); } void KisColorizeMask::setImage(KisImageWSP image) { KisDefaultBoundsSP bounds(new KisDefaultBounds(image)); auto it = m_d->keyStrokes.begin(); for(; it != m_d->keyStrokes.end(); ++it) { it->dev->setDefaultBounds(bounds); } m_d->coloringProjection->setDefaultBounds(bounds); m_d->fakePaintDevice->setDefaultBounds(bounds); m_d->filteredSource->setDefaultBounds(bounds); } void KisColorizeMask::setCurrentColor(const KoColor &_color) { KoColor color = _color; color.convertTo(colorSpace()); WriteLocker locker(this); m_d->setNeedsUpdateImpl(true, false); QList::const_iterator it = std::find_if(m_d->keyStrokes.constBegin(), m_d->keyStrokes.constEnd(), [color] (const KeyStroke &s) { return s.color == color; }); KisPaintDeviceSP activeDevice; bool newKeyStroke = false; if (it == m_d->keyStrokes.constEnd()) { activeDevice = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8()); activeDevice->setParentNode(this); activeDevice->setDefaultBounds(KisDefaultBoundsBaseSP(new KisDefaultBounds(fetchImage()))); newKeyStroke = true; } else { activeDevice = it->dev; } m_d->currentColor = color; m_d->currentKeyStrokeDevice = activeDevice; m_d->needAddCurrentKeyStroke = newKeyStroke; } struct KeyStrokeAddRemoveCommand : public KisCommandUtils::FlipFlopCommand { KeyStrokeAddRemoveCommand(bool add, int index, KeyStroke stroke, QList *list, KisColorizeMaskSP node) : FlipFlopCommand(!add), m_index(index), m_stroke(stroke), m_list(list), m_node(node) {} void init() override { m_list->insert(m_index, m_stroke); m_node->setNeedsUpdate(true); emit m_node->sigKeyStrokesListChanged(); } void end() override { KIS_ASSERT_RECOVER_RETURN((*m_list)[m_index] == m_stroke); m_list->removeAt(m_index); m_node->setNeedsUpdate(true); emit m_node->sigKeyStrokesListChanged(); } private: int m_index; KeyStroke m_stroke; QList *m_list; KisColorizeMaskSP m_node; }; void KisColorizeMask::mergeToLayer(KisNodeSP layer, KisPostExecutionUndoAdapter *undoAdapter, const KUndo2MagicString &transactionText,int timedID) { Q_UNUSED(layer); WriteLocker locker(this); KisPaintDeviceSP temporaryTarget = this->temporaryTarget(); const bool isTemporaryTargetErasing = temporaryCompositeOp() == COMPOSITE_ERASE; const QRect temporaryExtent = temporaryTarget ? temporaryTarget->extent() : QRect(); KisSavedMacroCommand *macro = undoAdapter->createMacro(transactionText); KisMacroBasedUndoStore store(macro); KisPostExecutionUndoAdapter fakeUndoAdapter(&store, undoAdapter->strokesFacade()); /** * Add a new key stroke plane */ if (m_d->needAddCurrentKeyStroke && !isTemporaryTargetErasing) { KeyStroke key(m_d->currentKeyStrokeDevice, m_d->currentColor); KUndo2Command *cmd = new KeyStrokeAddRemoveCommand( true, m_d->keyStrokes.size(), key, &m_d->keyStrokes, KisColorizeMaskSP(this)); cmd->redo(); fakeUndoAdapter.addCommand(toQShared(cmd)); } /** * When erasing, the brush affects all the key strokes, not only * the current one. */ if (!isTemporaryTargetErasing) { mergeToLayerImpl(m_d->currentKeyStrokeDevice, &fakeUndoAdapter, transactionText, timedID, false); } else { Q_FOREACH (const KeyStroke &stroke, m_d->keyStrokes) { if (temporaryExtent.intersects(stroke.dev->extent())) { mergeToLayerImpl(stroke.dev, &fakeUndoAdapter, transactionText, timedID, false); } } } mergeToLayerImpl(m_d->fakePaintDevice, &fakeUndoAdapter, transactionText, timedID, false); m_d->currentKeyStrokeDevice = 0; m_d->currentColor = KoColor(); releaseResources(); /** * Try removing the key strokes that has been completely erased */ if (isTemporaryTargetErasing) { for (int index = 0; index < m_d->keyStrokes.size(); /*noop*/) { const KeyStroke &stroke = m_d->keyStrokes[index]; if (stroke.dev->exactBounds().isEmpty()) { KUndo2Command *cmd = new KeyStrokeAddRemoveCommand( false, index, stroke, &m_d->keyStrokes, KisColorizeMaskSP(this)); cmd->redo(); fakeUndoAdapter.addCommand(toQShared(cmd)); } else { index++; } } } undoAdapter->addMacro(macro); } void KisColorizeMask::writeMergeData(KisPainter *painter, KisPaintDeviceSP src) { const KoColorSpace *alpha8 = KoColorSpaceRegistry::instance()->alpha8(); const bool nonAlphaDst = !(*painter->device()->colorSpace() == *alpha8); if (nonAlphaDst) { Q_FOREACH (const QRect &rc, src->region().rects()) { painter->bitBlt(rc.topLeft(), src, rc); } } else { KisSelectionSP conversionSelection = m_d->cachedConversionSelection.getSelection(); KisPixelSelectionSP tempSelection = conversionSelection->pixelSelection(); Q_FOREACH (const QRect &rc, src->region().rects()) { tempSelection->copyAlphaFrom(src, rc); painter->bitBlt(rc.topLeft(), tempSelection, rc); } m_d->cachedSelection.putSelection(conversionSelection); } } bool KisColorizeMask::supportsNonIndirectPainting() const { return false; } bool KisColorizeMask::showColoring() const { return m_d->showColoring; } void KisColorizeMask::setShowColoring(bool value) { QRect savedExtent; if (m_d->showColoring && !value) { savedExtent = extent(); } m_d->showColoring = value; baseNodeChangedCallback(); if (!savedExtent.isEmpty()) { setDirty(savedExtent); } } bool KisColorizeMask::showKeyStrokes() const { return m_d->showKeyStrokes; } void KisColorizeMask::setShowKeyStrokes(bool value) { QRect savedExtent; if (m_d->showKeyStrokes && !value) { savedExtent = extent(); } m_d->showKeyStrokes = value; baseNodeChangedCallback(); if (!savedExtent.isEmpty()) { setDirty(savedExtent); } regeneratePrefilteredDeviceIfNeeded(); } KisColorizeMask::KeyStrokeColors KisColorizeMask::keyStrokesColors() const { KeyStrokeColors colors; // TODO: thread safety! for (int i = 0; i < m_d->keyStrokes.size(); i++) { colors.colors << m_d->keyStrokes[i].color; if (m_d->keyStrokes[i].isTransparent) { colors.transparentIndex = i; } } return colors; } struct SetKeyStrokeColorsCommand : public KUndo2Command { SetKeyStrokeColorsCommand(const QList newList, QList *list, KisColorizeMaskSP node) : m_newList(newList), m_oldList(*list), m_list(list), m_node(node) {} void redo() override { *m_list = m_newList; m_node->setNeedsUpdate(true); emit m_node->sigKeyStrokesListChanged(); m_node->setDirty(); } void undo() override { *m_list = m_oldList; m_node->setNeedsUpdate(true); emit m_node->sigKeyStrokesListChanged(); m_node->setDirty(); } private: QList m_newList; QList m_oldList; QList *m_list; KisColorizeMaskSP m_node; }; void KisColorizeMask::setKeyStrokesColors(KeyStrokeColors colors) { KIS_ASSERT_RECOVER_RETURN(colors.colors.size() == m_d->keyStrokes.size()); QList newList = m_d->keyStrokes; for (int i = 0; i < newList.size(); i++) { newList[i].color = colors.colors[i]; newList[i].color.convertTo(colorSpace()); newList[i].isTransparent = colors.transparentIndex == i; } KisProcessingApplicator applicator(fetchImage(), KisNodeSP(this), KisProcessingApplicator::NONE, KisImageSignalVector(), kundo2_i18n("Change Key Stroke Color")); applicator.applyCommand( new SetKeyStrokeColorsCommand( newList, &m_d->keyStrokes, KisColorizeMaskSP(this))); applicator.end(); } void KisColorizeMask::removeKeyStroke(const KoColor &_color) { KoColor color = _color; color.convertTo(colorSpace()); QList::iterator it = std::find_if(m_d->keyStrokes.begin(), m_d->keyStrokes.end(), [color] (const KeyStroke &s) { return s.color == color; }); KIS_SAFE_ASSERT_RECOVER_RETURN(it != m_d->keyStrokes.end()); const int index = it - m_d->keyStrokes.begin(); KisProcessingApplicator applicator(KisImageWSP(fetchImage()), KisNodeSP(this), KisProcessingApplicator::NONE, KisImageSignalVector(), kundo2_i18n("Remove Key Stroke")); applicator.applyCommand( new KeyStrokeAddRemoveCommand( false, index, *it, &m_d->keyStrokes, KisColorizeMaskSP(this))); applicator.end(); } QVector KisColorizeMask::allPaintDevices() const { QVector devices; Q_FOREACH (const KeyStroke &stroke, m_d->keyStrokes) { devices << stroke.dev; } devices << m_d->coloringProjection; devices << m_d->fakePaintDevice; return devices; } void KisColorizeMask::resetCache() { m_d->filteredSource->clear(); m_d->originalSequenceNumber = -1; m_d->filteringDirty = true; rerenderFakePaintDevice(); slotUpdateRegenerateFilling(true); } void KisColorizeMask::setUseEdgeDetection(bool value) { m_d->filteringOptions.useEdgeDetection = value; m_d->filteringDirty = true; setNeedsUpdate(true); } bool KisColorizeMask::useEdgeDetection() const { return m_d->filteringOptions.useEdgeDetection; } void KisColorizeMask::setEdgeDetectionSize(qreal value) { m_d->filteringOptions.edgeDetectionSize = value; m_d->filteringDirty = true; setNeedsUpdate(true); } qreal KisColorizeMask::edgeDetectionSize() const { return m_d->filteringOptions.edgeDetectionSize; } void KisColorizeMask::setFuzzyRadius(qreal value) { m_d->filteringOptions.fuzzyRadius = value; m_d->filteringDirty = true; setNeedsUpdate(true); } qreal KisColorizeMask::fuzzyRadius() const { return m_d->filteringOptions.fuzzyRadius; } void KisColorizeMask::setCleanUpAmount(qreal value) { m_d->filteringOptions.cleanUpAmount = value; setNeedsUpdate(true); } qreal KisColorizeMask::cleanUpAmount() const { return m_d->filteringOptions.cleanUpAmount; } void KisColorizeMask::setLimitToDeviceBounds(bool value) { m_d->limitToDeviceBounds = value; m_d->filteringDirty = true; setNeedsUpdate(true); } bool KisColorizeMask::limitToDeviceBounds() const { return m_d->limitToDeviceBounds; } void KisColorizeMask::rerenderFakePaintDevice() { m_d->fakePaintDevice->clear(); KisFillPainter gc(m_d->fakePaintDevice); KisSelectionSP selection = m_d->cachedSelection.getSelection(); Q_FOREACH (const KeyStroke &stroke, m_d->keyStrokes) { const QRect rect = stroke.dev->extent(); selection->pixelSelection()->makeCloneFromRough(stroke.dev, rect); gc.setSelection(selection); gc.fillSelection(rect, stroke.color); } m_d->cachedSelection.putSelection(selection); } void KisColorizeMask::testingAddKeyStroke(KisPaintDeviceSP dev, const KoColor &color, bool isTransparent) { m_d->keyStrokes << KeyStroke(dev, color, isTransparent); } void KisColorizeMask::testingRegenerateMask() { slotUpdateRegenerateFilling(); } KisPaintDeviceSP KisColorizeMask::testingFilteredSource() const { return m_d->filteredSource; } QList KisColorizeMask::fetchKeyStrokesDirect() const { return m_d->keyStrokes; } void KisColorizeMask::setKeyStrokesDirect(const QList &strokes) { m_d->keyStrokes = strokes; for (auto it = m_d->keyStrokes.begin(); it != m_d->keyStrokes.end(); ++it) { it->dev->setParentNode(this); } KisImageSP image = fetchImage(); KIS_SAFE_ASSERT_RECOVER_RETURN(image); setImage(image); } qint32 KisColorizeMask::x() const { return m_d->offset.x(); } qint32 KisColorizeMask::y() const { return m_d->offset.y(); } void KisColorizeMask::setX(qint32 x) { const QPoint oldOffset = m_d->offset; m_d->offset.rx() = x; moveAllInternalDevices(m_d->offset - oldOffset); } void KisColorizeMask::setY(qint32 y) { const QPoint oldOffset = m_d->offset; m_d->offset.ry() = y; moveAllInternalDevices(m_d->offset - oldOffset); } KisPaintDeviceList KisColorizeMask::getLodCapableDevices() const { KisPaintDeviceList list; auto it = m_d->keyStrokes.begin(); for(; it != m_d->keyStrokes.end(); ++it) { list << it->dev; } list << m_d->coloringProjection; list << m_d->fakePaintDevice; list << m_d->filteredSource; return list; } void KisColorizeMask::regeneratePrefilteredDeviceIfNeeded() { if (!parent()) return; KisPaintDeviceSP src = parent()->original(); KIS_ASSERT_RECOVER_RETURN(src); if (!m_d->filteredSourceValid(src)) { // update the prefiltered source if needed slotUpdateRegenerateFilling(true); } } void KisColorizeMask::moveAllInternalDevices(const QPoint &diff) { QVector devices = allPaintDevices(); Q_FOREACH (KisPaintDeviceSP dev, devices) { dev->moveTo(dev->offset() + diff); } } diff --git a/libs/image/metadata/kis_exif_info_visitor.h b/libs/image/metadata/kis_exif_info_visitor.h index 4f9cee3ed4..998f7a1353 100644 --- a/libs/image/metadata/kis_exif_info_visitor.h +++ b/libs/image/metadata/kis_exif_info_visitor.h @@ -1,95 +1,105 @@ /* * Copyright (c) 2005 Cyrille Berger * * 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_EXIF_INFO_VISITOR_H #define KIS_EXIF_INFO_VISITOR_H #include #include #include #include #include +/** + * @brief The KisExifInfoVisitor class looks for a layer with metadata. + * + * If there is more than one layer with metadata, the metadata provided + * by the visitor is the metadata associated with the last layer that + * had metadata on it. Only use the metadata if only one layer with + * metadata was found. + * + * The metadata pointer is OWNED by the layer. + * + */ class KisExifInfoVisitor : public KisNodeVisitor { public: - KisExifInfoVisitor() : - m_exifInfo(0), - m_countPaintLayer(0) { } -public: - + KisExifInfoVisitor() { } bool visit(KisNode*) override { return true; } bool visit(KisCloneLayer*) override { return true; } bool visit(KisFilterMask*) override { return true; } bool visit(KisTransformMask*) override { return true; } bool visit(KisTransparencyMask*) override { return true; } bool visit(KisSelectionMask*) override { return true; } bool visit(KisColorizeMask*) override { return true; } bool visit(KisExternalLayer*) override { return true; } bool visit(KisGeneratorLayer*) override { return true; } bool visit(KisAdjustmentLayer*) override { return true; } bool visit(KisPaintLayer* layer) override { - m_countPaintLayer++; if (!layer->metaData()->empty()) { + m_metaDataObjectsEncountered++; m_exifInfo = layer->metaData(); } return true; } bool visit(KisGroupLayer* layer) override { dbgMetaData << "Visiting on grouplayer" << layer->name() << ""; return visitAll(layer, true); } - public: - inline uint countPaintLayer() { - return m_countPaintLayer; + inline uint metaDataCount() + { + qDebug() << "number of layers with metadata" << m_metaDataObjectsEncountered; + return m_metaDataObjectsEncountered; } - inline KisMetaData::Store* exifInfo() { + + inline KisMetaData::Store* exifInfo() + { return m_exifInfo; } private: - KisMetaData::Store* m_exifInfo; - uint m_countPaintLayer; + KisMetaData::Store *m_exifInfo {0}; + int m_metaDataObjectsEncountered {0}; }; #endif diff --git a/libs/image/tests/CMakeLists.txt b/libs/image/tests/CMakeLists.txt index 90a5e6289e..979f93f1dd 100644 --- a/libs/image/tests/CMakeLists.txt +++ b/libs/image/tests/CMakeLists.txt @@ -1,246 +1,248 @@ # cmake in some versions for some not yet known reasons fails to run automoc # on random targets (changing target names already has an effect) # As temporary workaround skipping build of tests on these versions for now # See https://mail.kde.org/pipermail/kde-buildsystem/2015-June/010819.html # extend range of affected cmake versions as needed if(NOT ${CMAKE_VERSION} VERSION_LESS 3.1.3 AND NOT ${CMAKE_VERSION} VERSION_GREATER 3.2.3) message(WARNING "Skipping krita/image/tests, CMake in at least versions 3.1.3 - 3.2.3 seems to have a problem with automoc. \n(FRIENDLY REMINDER: PLEASE DON'T BREAK THE TESTS!)") set (HAVE_FAILING_CMAKE TRUE) else() set (HAVE_FAILING_CMAKE FALSE) endif() include_directories( ${CMAKE_SOURCE_DIR}/libs/image/metadata ${CMAKE_BINARY_DIR}/libs/image/ ${CMAKE_SOURCE_DIR}/libs/image/ ${CMAKE_SOURCE_DIR}/libs/image/brushengine ${CMAKE_SOURCE_DIR}/libs/image/tiles3 ${CMAKE_SOURCE_DIR}/libs/image/tiles3/swap ${CMAKE_SOURCE_DIR}/sdk/tests ) include_Directories(SYSTEM ${EIGEN3_INCLUDE_DIR} ) if(HAVE_VC) include_directories(${Vc_INCLUDE_DIR}) endif() include(ECMAddTests) include(KritaAddBrokenUnitTest) macro_add_unittest_definitions() set(KisRandomGeneratorDemoSources kis_random_generator_demo.cpp kimageframe.cpp) ki18n_wrap_ui(KisRandomGeneratorDemoSources kis_random_generator_demo.ui) add_executable(KisRandomGeneratorDemo ${KisRandomGeneratorDemoSources}) target_link_libraries(KisRandomGeneratorDemo kritaimage) ecm_mark_as_test(KisRandomGeneratorDemo) ecm_add_tests( kis_base_node_test.cpp kis_fast_math_test.cpp kis_node_test.cpp kis_node_facade_test.cpp kis_fixed_paint_device_test.cpp kis_layer_test.cpp kis_effect_mask_test.cpp kis_iterator_test.cpp kis_painter_test.cpp kis_selection_test.cpp kis_count_visitor_test.cpp kis_projection_test.cpp kis_properties_configuration_test.cpp kis_transaction_test.cpp kis_pixel_selection_test.cpp kis_group_layer_test.cpp kis_paint_layer_test.cpp kis_adjustment_layer_test.cpp kis_annotation_test.cpp kis_change_profile_visitor_test.cpp kis_clone_layer_test.cpp kis_colorspace_convert_visitor_test.cpp kis_convolution_painter_test.cpp kis_crop_processing_visitor_test.cpp kis_processing_applicator_test.cpp kis_datamanager_test.cpp kis_fill_painter_test.cpp kis_filter_configuration_test.cpp kis_filter_test.cpp kis_filter_processing_information_test.cpp kis_filter_registry_test.cpp kis_filter_strategy_test.cpp kis_gradient_painter_test.cpp kis_image_commands_test.cpp kis_image_test.cpp kis_image_signal_router_test.cpp kis_iterators_ng_test.cpp kis_iterator_benchmark.cpp kis_updater_context_test.cpp kis_simple_update_queue_test.cpp kis_stroke_test.cpp kis_simple_stroke_strategy_test.cpp kis_stroke_strategy_undo_command_based_test.cpp kis_strokes_queue_test.cpp kis_macro_test.cpp kis_mask_test.cpp kis_math_toolbox_test.cpp kis_name_server_test.cpp kis_node_commands_test.cpp kis_node_graph_listener_test.cpp kis_node_visitor_test.cpp kis_paint_information_test.cpp kis_distance_information_test.cpp kis_paintop_test.cpp kis_pattern_test.cpp kis_recorded_action_factory_registry_test.cpp kis_recorded_action_test.cpp kis_recorded_filter_action_test.cpp kis_selection_mask_test.cpp kis_shared_ptr_test.cpp kis_bsplines_test.cpp kis_warp_transform_worker_test.cpp kis_liquify_transform_worker_test.cpp kis_transparency_mask_test.cpp kis_types_test.cpp kis_vec_test.cpp kis_filter_config_widget_test.cpp kis_mask_generator_test.cpp kis_cubic_curve_test.cpp kis_node_query_path_test.cpp kis_fixed_point_maths_test.cpp kis_filter_weights_buffer_test.cpp kis_filter_weights_applicator_test.cpp kis_fill_interval_test.cpp kis_fill_interval_map_test.cpp kis_scanline_fill_test.cpp kis_psd_layer_style_test.cpp kis_layer_style_projection_plane_test.cpp kis_lod_capable_layer_offset_test.cpp kis_algebra_2d_test.cpp kis_marker_painter_test.cpp kis_lazy_brush_test.cpp kis_colorize_mask_test.cpp + kis_mask_similarity_test.cpp + KisMaskGeneratorBenchmark.cpp NAME_PREFIX "krita-image-" LINK_LIBRARIES kritaimage Qt5::Test) ecm_add_test(kis_layer_style_filter_environment_test.cpp TEST_NAME kritaimage-layer_style_filter_environment_test LINK_LIBRARIES ${KDE4_KDEUI_LIBS} kritaimage Qt5::Test) ecm_add_test(kis_asl_parser_test.cpp TEST_NAME kritalibpsd-asl_parser_test LINK_LIBRARIES kritapsd kritapigment kritawidgetutils kritacommand Qt5::Xml Qt5::Test) ecm_add_test(KisPerStrokeRandomSourceTest.cpp TEST_NAME KisPerStrokeRandomSourceTest LINK_LIBRARIES kritaimage Qt5::Test) ecm_add_test(KisWatershedWorkerTest.cpp TEST_NAME KisWatershedWorkerTest LINK_LIBRARIES kritaimage Qt5::Test) # ecm_add_test(kis_dom_utils_test.cpp # TEST_NAME krita-image-DomUtils-Test # LINK_LIBRARIES kritaimage Qt5::Test) # kisdoc dep # kis_transform_worker_test.cpp # TEST_NAME krita-image-KisTransformWorkerTest #LINK_LIBRARIES kritaimage Qt5::Test) # kisdoc # kis_perspective_transform_worker_test.cpp # TEST_NAME krita-image-KisPerspectiveTransformWorkerTest #LINK_LIBRARIES kritaimage Qt5::Test) # kis_cs_conversion_test.cpp # TEST_NAME krita-image-KisCsConversionTest # LINK_LIBRARIES kritaimage Qt5::Test) # kisdoc # kis_processings_test.cpp # TEST_NAME krita-image-KisProcessingsTest #LINK_LIBRARIES kritaimage Qt5::Test) # image/tests cannot use stuff that needs kisdocument # kis_projection_leaf_test.cpp # TEST_NAME kritaimage-projection_leaf_test # LINK_LIBRARIES kritaimage Qt5::Test) if (NOT HAVE_FAILING_CMAKE) krita_add_broken_unit_test(kis_paint_device_test.cpp TEST_NAME krita-image-KisPaintDeviceTest LINK_LIBRARIES kritaimage kritaodf Qt5::Test) else() message(WARNING "Skipping KisPaintDeviceTest!!!!!!!!!!!!!!") endif() if (NOT HAVE_FAILING_CMAKE) krita_add_broken_unit_test(kis_filter_mask_test.cpp TEST_NAME krita-image-KisFilterMaskTest LINK_LIBRARIES kritaimage Qt5::Test) else() message(WARNING "Skipping KisFilterMaskTest!!!!!!!!!!!!!!") endif() krita_add_broken_unit_test(kis_transform_mask_test.cpp TEST_NAME krita-image-KisTransformMaskTest LINK_LIBRARIES kritaimage Qt5::Test) krita_add_broken_unit_test(kis_histogram_test.cpp TEST_NAME krita-image-KisHistogramTest LINK_LIBRARIES kritaimage Qt5::Test) krita_add_broken_unit_test(kis_walkers_test.cpp TEST_NAME krita-image-KisWalkersTest LINK_LIBRARIES kritaimage Qt5::Test) #krita_add_broken_unit_test(kis_async_merger_test.cpp # TEST_NAME krita-image-KisAsyncMergerTest # LINK_LIBRARIES kritaimage Qt5::Test) krita_add_broken_unit_test(kis_update_scheduler_test.cpp TEST_NAME krita-image-KisUpdateSchedulerTest LINK_LIBRARIES kritaimage Qt5::Test) krita_add_broken_unit_test(kis_queues_progress_updater_test.cpp TEST_NAME krita-image-KisQueuesProgressUpdaterTest LINK_LIBRARIES kritaimage Qt5::Test) krita_add_broken_unit_test(kis_cage_transform_worker_test.cpp TEST_NAME krita-image-KisCageTransformWorkerTest LINK_LIBRARIES kritaimage Qt5::Test) krita_add_broken_unit_test(kis_meta_data_test.cpp TEST_NAME krita-image-KisMetaDataTest LINK_LIBRARIES kritaimage Qt5::Test) krita_add_broken_unit_test(kis_random_generator_test.cpp TEST_NAME krita-image-KisRandomGeneratorTest LINK_LIBRARIES kritaimage Qt5::Test) krita_add_broken_unit_test(kis_keyframing_test.cpp TEST_NAME krita-image-Keyframing-Test LINK_LIBRARIES kritaimage Qt5::Test) krita_add_broken_unit_test(kis_image_animation_interface_test.cpp TEST_NAME krita-image-ImageAnimationInterface-Test LINK_LIBRARIES ${KDE4_KDEUI_LIBS} kritaimage Qt5::Test) krita_add_broken_unit_test(kis_onion_skin_compositor_test.cpp TEST_NAME krita-image-OnionSkinCompositor-Test LINK_LIBRARIES ${KDE4_KDEUI_LIBS} kritaimage Qt5::Test) krita_add_broken_unit_test(kis_layer_styles_test.cpp TEST_NAME krita-image-LayerStylesTest LINK_LIBRARIES kritaimage Qt5::Test) diff --git a/libs/image/tests/KisMaskGeneratorBenchmark.cpp b/libs/image/tests/KisMaskGeneratorBenchmark.cpp new file mode 100644 index 0000000000..6875ef9b99 --- /dev/null +++ b/libs/image/tests/KisMaskGeneratorBenchmark.cpp @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2018 Iván Santa María + * + * 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 "KisMaskGeneratorBenchmark.h" + +#include "kis_mask_similarity_test.h" + +#include "kis_brush_mask_applicator_base.h" +#include "kis_mask_generator.h" +#include "krita_utils.h" + +#include "testutil.h" + +class KisMaskGeneratorBenchmarkTester +{ +public: + KisMaskGeneratorBenchmarkTester(KisBrushMaskApplicatorBase *_applicatorBase, QRect _bounds) + : applicatorBase(_applicatorBase) + , m_bounds(_bounds) + { + KisFixedPaintDeviceSP m_paintDev = new KisFixedPaintDevice(m_colorSpace); + m_paintDev->setRect(m_bounds); + m_paintDev->initialize(255); + + MaskProcessingData data(m_paintDev, m_colorSpace, + 0.0, 1.0, + m_bounds.width() / 2.0, m_bounds.height() / 2.0,0); + + // Start Benchmark + applicatorBase->initializeData(&data); + + QElapsedTimer maskGenerationTime; + maskGenerationTime.start(); + + QBENCHMARK { + + applicatorBase->process(m_bounds); + } + } + +protected: + const KoColorSpace *m_colorSpace = KoColorSpaceRegistry::instance()->rgb8(); + QRect m_bounds; + + KisBrushMaskApplicatorBase *applicatorBase; + KisFixedPaintDeviceSP m_paintDev; +}; + +void KisMaskGeneratorBenchmark::testDefaultScalarMask() +{ + QRect bounds(0,0,1000,1000); + { + KisCircleMaskGenerator circScalar(1000, 1.0, 0.5, 0.5, 2, true); + circScalar.resetMaskApplicator(true); // Force usage of scalar backend + + KisMaskGeneratorBenchmarkTester(circScalar.applicator(), bounds); + } +} + +void KisMaskGeneratorBenchmark::testDefaultVectorMask() +{ + QRect bounds(0,0,1000,1000); + { + KisCircleMaskGenerator circVectr(1000, 1.0, 0.5, 0.5, 2, true); + KisMaskGeneratorBenchmarkTester(circVectr.applicator(), bounds); + } +} + +void KisMaskGeneratorBenchmark::testCircularGaussScalarMask() +{ + QRect bounds(0,0,1000,1000); + { + KisGaussCircleMaskGenerator circScalar(1000, 1.0, 0.5, 0.5, 2, true); + circScalar.setDiameter(1000); + circScalar.resetMaskApplicator(true); // Force usage of scalar backend + + KisMaskGeneratorBenchmarkTester(circScalar.applicator(), bounds); + } +} + +void KisMaskGeneratorBenchmark::testCircularGaussVectorMask() +{ + QRect bounds(0,0,1000,1000); + { + KisGaussCircleMaskGenerator circVectr(1000, 1.0, 0.5, 0.5, 2, true); + circVectr.setDiameter(1000); + KisMaskGeneratorBenchmarkTester(circVectr.applicator(), bounds); + } +} + +QTEST_MAIN(KisMaskGeneratorBenchmark) diff --git a/libs/ui/dialogs/kis_about_application.h b/libs/image/tests/KisMaskGeneratorBenchmark.h similarity index 65% copy from libs/ui/dialogs/kis_about_application.h copy to libs/image/tests/KisMaskGeneratorBenchmark.h index 23a94d2dcb..da1e2efb51 100644 --- a/libs/ui/dialogs/kis_about_application.h +++ b/libs/image/tests/KisMaskGeneratorBenchmark.h @@ -1,35 +1,36 @@ /* - * Copyright (c) 2014 Boudewijn Rempt + * Copyright (c) 2018 Iván Santa María * * 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_ABOUT_APPLICATION_H -#define KIS_ABOUT_APPLICATION_H -#include +#ifndef KISMASKGENERATORBENCHMARK_H +#define KISMASKGENERATORBENCHMARK_H -class KisAboutApplication : public QDialog +#include + +class KisMaskGeneratorBenchmark : public QObject { Q_OBJECT -public: - explicit KisAboutApplication(QWidget *parent = 0); - -Q_SIGNALS: +private Q_SLOTS: -public Q_SLOTS: + void testDefaultScalarMask(); + void testDefaultVectorMask(); + void testCircularGaussScalarMask(); + void testCircularGaussVectorMask(); }; -#endif // KIS_ABOUT_APPLICATION_H +#endif // KISMASKGENERATORBENCHMARK_H diff --git a/libs/image/tests/kis_cubic_curve_test.cpp b/libs/image/tests/kis_cubic_curve_test.cpp index f33bce18fd..f9944ae33f 100644 --- a/libs/image/tests/kis_cubic_curve_test.cpp +++ b/libs/image/tests/kis_cubic_curve_test.cpp @@ -1,187 +1,187 @@ /* * Copyright (c) 2010 Cyrille Berger * * 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_cubic_curve_test.h" #include #include "kis_cubic_curve.h" KisCubicCurveTest::KisCubicCurveTest() : pt0(0, 0), pt1(0.1, 0.0), pt2(0.5, 0.7), pt3(0.8, 0.6), pt4(0.9, 1.0), pt5(1, 1) { } void KisCubicCurveTest::testCreation() { KisCubicCurve cc1; QCOMPARE(cc1.points()[0], pt0); QCOMPARE(cc1.points()[1], pt5); QList pts; pts.push_back(pt1); pts.push_back(pt3); pts.push_back(pt2); KisCubicCurve cc2(pts); QCOMPARE(cc2.points()[0], pt1); QCOMPARE(cc2.points()[1], pt2); QCOMPARE(cc2.points()[2], pt3); } void KisCubicCurveTest::testCopy() { QList pts; pts.push_back(pt1); pts.push_back(pt2); pts.push_back(pt4); KisCubicCurve cc1(pts); KisCubicCurve cc2(cc1); QVERIFY(cc1 == cc2); QCOMPARE(cc1.points()[0], cc2.points()[0]); QCOMPARE(cc1.points()[1], cc2.points()[1]); QCOMPARE(cc1.points()[2], cc2.points()[2]); cc2.addPoint(pt3); QCOMPARE(cc1.points().size(), 3); QCOMPARE(cc2.points().size(), 4); QCOMPARE(cc1.points()[0], cc2.points()[0]); QCOMPARE(cc1.points()[1], cc2.points()[1]); QCOMPARE(cc1.points()[2], cc2.points()[3]); QCOMPARE(pt3, cc2.points()[2]); } void KisCubicCurveTest::testEdition() { KisCubicCurve cc1; cc1.addPoint(pt3); QCOMPARE(cc1.points().size(), 3); QCOMPARE(cc1.points()[0], pt0); QCOMPARE(cc1.points()[1], pt3); QCOMPARE(cc1.points()[2], pt5); cc1.setPoint(0, pt4); QCOMPARE(cc1.points().size(), 3); QCOMPARE(cc1.points()[0], pt3); QCOMPARE(cc1.points()[1], pt4); QCOMPARE(cc1.points()[2], pt5); int pos = cc1.addPoint(pt2); QCOMPARE(pos, 0); QCOMPARE(cc1.points().size(), 4); QCOMPARE(cc1.points()[0], pt2); QCOMPARE(cc1.points()[1], pt3); QCOMPARE(cc1.points()[2], pt4); QCOMPARE(cc1.points()[3], pt5); cc1.removePoint(2); QCOMPARE(cc1.points().size(), 3); QCOMPARE(cc1.points()[0], pt2); QCOMPARE(cc1.points()[1], pt3); QCOMPARE(cc1.points()[2], pt5); } void KisCubicCurveTest::testComparison() { QList pts; pts.push_back(pt1); pts.push_back(pt2); pts.push_back(pt4); KisCubicCurve cc1(pts); KisCubicCurve cc2(pts); QVERIFY(cc1 == cc2); cc2.setPoint(0, pt1); QVERIFY(cc1 == cc2); cc2.removePoint(2); QVERIFY(!(cc1 == cc2)); cc2.addPoint(pt4); QVERIFY(cc1 == cc2); cc2.addPoint(pt5); QVERIFY(!(cc1 == cc2)); QList pts2; pts2.push_back(pt1); pts2.push_back(pt2); pts2.push_back(pt3); KisCubicCurve cc3(pts2); QVERIFY(!(cc1 == cc3)); } void KisCubicCurveTest::testSerialization() { QList pts; pts.push_back(pt1); pts.push_back(pt2); pts.push_back(pt4); KisCubicCurve cc1(pts); QString s = cc1.toString(); QCOMPARE(s, QString("0.1,0;0.5,0.7;0.9,1;")); KisCubicCurve cc2; cc2.fromString(s); QVERIFY(cc1 == cc2); } void KisCubicCurveTest::testValue() { KisCubicCurve cc; for(int i = 0; i < 256; ++i) { qreal x = i/255.0; QCOMPARE(cc.value(x), x); } } void KisCubicCurveTest::testNull() { KisCubicCurve cc; - QVERIFY(cc.isNull()); + QVERIFY(cc.isIdentity()); cc.addPoint(QPointF(0.2, 0.3)); - QVERIFY(!cc.isNull()); + QVERIFY(!cc.isIdentity()); QList points; points << QPointF(); points << QPointF(0.2,0.2); points << QPointF(1.0,1.0); cc.setPoints(points); - QVERIFY(cc.isNull()); + QVERIFY(cc.isIdentity()); } void KisCubicCurveTest::testTransfer() { KisCubicCurve cc; QCOMPARE(cc.uint16Transfer().size(), 256); qreal denom = 1 / 255.0; for(int i = 0; i < 256; ++i) { QCOMPARE(cc.uint16Transfer()[i], quint16( cc.value(i * denom) * 0xFFFF) ); } QCOMPARE(cc.floatTransfer().size(), 256); for(int i = 0; i < 256; ++i) { QCOMPARE(cc.floatTransfer()[i], i * denom); } QCOMPARE(cc.uint16Transfer(1024).size(), 1024); denom = 1 / 1023.0; for(int i = 0; i < 1024; ++i) { QCOMPARE(cc.uint16Transfer(1024)[i], quint16( cc.value(i * denom) * 0xFFFF) ); } QCOMPARE(cc.floatTransfer(1024).size(), 1024); for(int i = 0; i < 1024; ++i) { QCOMPARE(cc.floatTransfer(1024)[i], i * denom); } } QTEST_MAIN(KisCubicCurveTest) diff --git a/libs/image/tests/kis_mask_similarity_test.cpp b/libs/image/tests/kis_mask_similarity_test.cpp new file mode 100644 index 0000000000..1e6885bbfb --- /dev/null +++ b/libs/image/tests/kis_mask_similarity_test.cpp @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2018 Iván Santa María + * + * 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_similarity_test.h" + +#include +#include +#include + +#include "kis_brush_mask_applicator_base.h" +#include "kis_mask_generator.h" +#include "krita_utils.h" + + +class KisMaskSimilarityTester +{ +public: + KisMaskSimilarityTester(KisBrushMaskApplicatorBase *_legacy, KisBrushMaskApplicatorBase *_vectorized, QRect _bounds) + : legacy(_legacy) + , vectorized(_vectorized) + , m_bounds(_bounds) + { + KisFixedPaintDeviceSP m_paintDev = new KisFixedPaintDevice(m_colorSpace); + m_paintDev->setRect(m_bounds); + m_paintDev->initialize(255); + + MaskProcessingData data(m_paintDev, m_colorSpace, + 0.0, 1.0, + m_bounds.width() / 2.0, m_bounds.height() / 2.0,0); + + // Start legacy scalar processing + legacy->initializeData(&data); + legacy->process(m_bounds); + + QImage scalarImage(m_paintDev->convertToQImage(m_colorSpace->profile())); + scalarImage.invertPixels(); // Make pixel color black + scalarImage.save(QString("scalar_mask.png"),"PNG"); + + // Start vector processing + m_paintDev->initialize(255); + vectorized->initializeData(&data); + vectorized->process(m_bounds); + + QImage vectorImage(m_paintDev->convertToQImage(m_colorSpace->profile())); + vectorImage.invertPixels(); // Make pixel color black + vectorImage.save(QString("vector_mask.png"),"PNG"); + +// Development debug. +// Count number of identical pixels +// int equals = 0; +// for (int i = 0; i < scalarImage.width(); ++i) { +// for (int j = 0; j < scalarImage.height(); ++j) { +// if (scalarImage.pixelColor(i,j) == vectorImage.pixelColor(i,j)){ +// equals++; +// } else { +// qDebug() << scalarImage.pixelColor(i,j) << " " << vectorImage.pixelColor(i,j); +// } +// } +// } +// qDebug() << "Equal Pixels: " << equals; + + // Check for differences, max error .5% of pixel mismatch + int tolerance = m_bounds.width() * m_bounds.height() * .005f; + QPoint tmpPt; + QVERIFY(TestUtil::compareQImages(tmpPt,scalarImage, vectorImage, 0, 1, tolerance)); + } + +private: + +protected: + const KoColorSpace *m_colorSpace = KoColorSpaceRegistry::instance()->rgb8(); + + KisBrushMaskApplicatorBase *legacy; + KisBrushMaskApplicatorBase *vectorized; + QRect m_bounds; + KisFixedPaintDeviceSP m_paintDev; +}; + + +void KisMaskSimilarityTest::testCircleMask() +{ + QRect bounds(0,0,500,500); + { + KisCircleMaskGenerator circVectr(480, 1.0, 0.5, 0.5, 2, true); + KisCircleMaskGenerator circScalar(circVectr); + + circScalar.resetMaskApplicator(true); // Force usage of scalar backend + KisMaskSimilarityTester(circScalar.applicator(), circVectr.applicator(), bounds); + } +} + +void KisMaskSimilarityTest::testGaussCircleMask() +{ + QRect bounds(0,0,500,500); + { + KisGaussCircleMaskGenerator circVectr(480, 1.0, 0.5, 0.5, 2, true); + circVectr.setDiameter(480); + KisGaussCircleMaskGenerator circScalar(circVectr); + + circScalar.resetMaskApplicator(true); // Force usage of scalar backend + KisMaskSimilarityTester(circScalar.applicator(), circVectr.applicator(), bounds); + } +} + +QTEST_MAIN(KisMaskSimilarityTest) diff --git a/libs/ui/dialogs/kis_about_application.h b/libs/image/tests/kis_mask_similarity_test.h similarity index 71% copy from libs/ui/dialogs/kis_about_application.h copy to libs/image/tests/kis_mask_similarity_test.h index 23a94d2dcb..2f3c9f873e 100644 --- a/libs/ui/dialogs/kis_about_application.h +++ b/libs/image/tests/kis_mask_similarity_test.h @@ -1,35 +1,33 @@ /* - * Copyright (c) 2014 Boudewijn Rempt + * Copyright (c) 2018 Iván Santa María * * 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_ABOUT_APPLICATION_H -#define KIS_ABOUT_APPLICATION_H -#include +#ifndef KIS_MASK_SIMILARITY_TEST +#define KIS_MASK_SIMILARITY_TEST -class KisAboutApplication : public QDialog +#include + +class KisMaskSimilarityTest : public QObject { Q_OBJECT -public: - explicit KisAboutApplication(QWidget *parent = 0); - -Q_SIGNALS: - -public Q_SLOTS: +private Q_SLOTS: + void testCircleMask(); + void testGaussCircleMask(); }; -#endif // KIS_ABOUT_APPLICATION_H +#endif diff --git a/libs/koplugin/KisMimeDatabase.cpp b/libs/koplugin/KisMimeDatabase.cpp index ad069549aa..6fcf7f874f 100644 --- a/libs/koplugin/KisMimeDatabase.cpp +++ b/libs/koplugin/KisMimeDatabase.cpp @@ -1,287 +1,292 @@ /* * Copyright (c) 2016 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisMimeDatabase.h" #include #include #include #include #include QList KisMimeDatabase::s_mimeDatabase; QString KisMimeDatabase::mimeTypeForFile(const QString &file, bool checkExistingFiles) { fillMimeData(); QFileInfo fi(file); QString suffix = fi.suffix().toLower(); Q_FOREACH(const KisMimeDatabase::KisMimeType &mimeType, s_mimeDatabase) { if (mimeType.suffixes.contains(suffix)) { debugPlugin << "mimeTypeForFile(). KisMimeDatabase returned" << mimeType.mimeType << "for" << file; return mimeType.mimeType; } } QMimeDatabase db; QMimeType mime; if (checkExistingFiles && fi.size() > 0) { mime = db.mimeTypeForFile(file, QMimeDatabase::MatchContent); if (mime.name() != "application/octet-stream" && mime.name() != "application/zip") { debugPlugin << "mimeTypeForFile(). QMimeDatabase returned" << mime.name() << "for" << file; return mime.name(); } } mime = db.mimeTypeForFile(file); if (mime.name() != "application/octet-stream") { debugPlugin << "mimeTypeForFile(). QMimeDatabase returned" << mime.name() << "for" << file; return mime.name(); } return ""; } QString KisMimeDatabase::mimeTypeForSuffix(const QString &suffix) { fillMimeData(); QMimeDatabase db; QString s = suffix.toLower(); Q_FOREACH(const KisMimeDatabase::KisMimeType &mimeType, s_mimeDatabase) { if (mimeType.suffixes.contains(s)) { debugPlugin << "mimeTypeForSuffix(). KisMimeDatabase returned" << mimeType.mimeType << "for" << s; return mimeType.mimeType; } } // make the file look like a file so Qt would recognize it s = "file." + s; return mimeTypeForFile(s); } QString KisMimeDatabase::mimeTypeForData(const QByteArray ba) { QMimeDatabase db; QMimeType mtp = db.mimeTypeForData(ba); debugPlugin << "mimeTypeForData(). QMimeDatabase returned" << mtp.name(); return mtp.name(); } QString KisMimeDatabase::descriptionForMimeType(const QString &mimeType) { fillMimeData(); Q_FOREACH(const KisMimeDatabase::KisMimeType &m, s_mimeDatabase) { if (m.mimeType == mimeType) { debugPlugin << "descriptionForMimeType. KisMimeDatabase returned" << m.description << "for" << mimeType; return m.description; } } QMimeDatabase db; QMimeType mime = db.mimeTypeForName(mimeType); if (mime.name() != "application/octet-stream") { debugPlugin << "descriptionForMimeType. QMimeDatabase returned" << mime.comment() << "for" << mimeType; return mime.comment(); } return mimeType; } QStringList KisMimeDatabase::suffixesForMimeType(const QString &mimeType) { fillMimeData(); Q_FOREACH(const KisMimeDatabase::KisMimeType &m, s_mimeDatabase) { if (m.mimeType == mimeType) { debugPlugin << "suffixesForMimeType. KisMimeDatabase returned" << m.suffixes; return m.suffixes; } } QMimeDatabase db; QMimeType mime = db.mimeTypeForName(mimeType); if (mime.name() != "application/octet-stream" && !mime.suffixes().isEmpty()) { QString preferredSuffix = mime.preferredSuffix(); if (mimeType == "image/x-tga") { preferredSuffix = "tga"; } if (mimeType == "image/jpeg") { preferredSuffix = "jpg"; } QStringList suffixes = mime.suffixes(); if (preferredSuffix != suffixes.first()) { suffixes.removeAll(preferredSuffix); suffixes.prepend(preferredSuffix); } debugPlugin << "suffixesForMimeType. QMimeDatabase returned" << suffixes; return suffixes; } return QStringList(); } QString KisMimeDatabase::iconNameForMimeType(const QString &mimeType) { QMimeDatabase db; QMimeType mime = db.mimeTypeForName(mimeType); debugPlugin << "iconNameForMimeType" << mime.iconName(); return mime.iconName(); } void KisMimeDatabase::fillMimeData() { // This should come from the import/export plugins, but the json files aren't translated, // which is bad for the description field if (s_mimeDatabase.isEmpty()) { KisMimeType mimeType; mimeType.mimeType = "image/x-gimp-brush"; mimeType.description = i18nc("description of a file type", "Gimp Brush"); mimeType.suffixes = QStringList() << "gbr" << "vbr"; s_mimeDatabase << mimeType; mimeType.mimeType = "image/x-gimp-brush-animated"; mimeType.description = i18nc("description of a file type", "Gimp Image Hose Brush"); mimeType.suffixes = QStringList() << "gih"; s_mimeDatabase << mimeType; mimeType.mimeType = "image/x-adobe-brushlibrary"; mimeType.description = i18nc("description of a file type", "Adobe Brush Library"); mimeType.suffixes = QStringList() << "abr"; s_mimeDatabase << mimeType; mimeType.mimeType = "application/x-krita-paintoppreset"; mimeType.description = i18nc("description of a file type", "Krita Brush Preset"); mimeType.suffixes = QStringList() << "kpp"; s_mimeDatabase << mimeType; mimeType.mimeType = "application/x-krita-assistant"; mimeType.description = i18nc("description of a file type", "Krita Assistant"); mimeType.suffixes = QStringList() << "paintingassistant"; s_mimeDatabase << mimeType; mimeType.mimeType = "image/x-r32"; mimeType.description = i18nc("description of a file type", "R32 Heightmap"); mimeType.suffixes = QStringList() << "r32"; s_mimeDatabase << mimeType; mimeType.mimeType = "image/x-r16"; mimeType.description = i18nc("description of a file type", "R16 Heightmap"); mimeType.suffixes = QStringList() << "r16"; s_mimeDatabase << mimeType; mimeType.mimeType = "image/x-r8"; mimeType.description = i18nc("description of a file type", "R8 Heightmap"); mimeType.suffixes = QStringList() << "r8"; s_mimeDatabase << mimeType; mimeType.mimeType = "application/x-spriter"; mimeType.description = i18nc("description of a file type", "Spriter SCML"); mimeType.suffixes = QStringList() << "scml"; s_mimeDatabase << mimeType; mimeType.mimeType = "image/x-svm"; mimeType.description = i18nc("description of a file type", "Starview Metafile"); mimeType.suffixes = QStringList() << "svm"; s_mimeDatabase << mimeType; mimeType.mimeType = "image/openraster"; mimeType.description = i18nc("description of a file type", "OpenRaster Image"); mimeType.suffixes = QStringList() << "ora"; s_mimeDatabase << mimeType; mimeType.mimeType = "application/x-photoshop-style-library"; mimeType.description = i18nc("description of a file type", "Photoshop Layer Style Library"); mimeType.suffixes = QStringList() << "asl"; s_mimeDatabase << mimeType; mimeType.mimeType = "application/x-gimp-color-palette"; mimeType.description = i18nc("description of a file type", "Color Palette"); mimeType.suffixes = QStringList() << "gpl" << "pal" << "act" << "aco" << "colors" << "xml" << "sbz"; s_mimeDatabase << mimeType; mimeType.mimeType = "application/x-opencolorio-configuration"; mimeType.description = i18nc("description of a file type", "OpenColorIO Configuration"); mimeType.suffixes = QStringList() << "ocio"; s_mimeDatabase << mimeType; mimeType.mimeType = "application/x-krita-recorded-macro"; mimeType.description = i18nc("description of a file type", "Krita Recorded Action"); mimeType.suffixes = QStringList() << "krarec"; s_mimeDatabase << mimeType; mimeType.mimeType = "application/x-gimp-gradient"; mimeType.description = i18nc("description of a file type", "GIMP Gradients"); mimeType.suffixes = QStringList() << "ggr"; s_mimeDatabase << mimeType; mimeType.mimeType = "application/x-gimp-pattern"; mimeType.description = i18nc("description of a file type", "GIMP Patterns"); mimeType.suffixes = QStringList() << "pat"; s_mimeDatabase << mimeType; mimeType.mimeType = "application/x-karbon-gradient"; mimeType.description = i18nc("description of a file type", "Karbon Gradients"); mimeType.suffixes = QStringList() << "kgr"; s_mimeDatabase << mimeType; mimeType.mimeType = "application/x-krita-bundle"; mimeType.description = i18nc("description of a file type", "Krita Resource Bundle"); mimeType.suffixes = QStringList() << "bundle"; s_mimeDatabase << mimeType; mimeType.mimeType = "application/x-krita-workspace"; mimeType.description = i18nc("description of a file type", "Krita Workspace"); mimeType.suffixes = QStringList() << "kws"; s_mimeDatabase << mimeType; mimeType.mimeType = "application/x-krita-taskset"; mimeType.description = i18nc("description of a file type", "Krita Taskset"); mimeType.suffixes = QStringList() << "kts"; s_mimeDatabase << mimeType; mimeType.mimeType = "application/x-krita-reference-images"; mimeType.description = i18nc("description of a file type", "Krita Reference Image Collection"); mimeType.suffixes = QStringList() << "krf"; s_mimeDatabase << mimeType; mimeType.mimeType = "image/x-krita-raw"; mimeType.description = i18nc("description of a file type", "Camera Raw Files"); - mimeType.suffixes = QStringList() << "rw2" << "nef" << "cr2" << "sr2" << "crw" << "pef" << "x3f" << "kdc" << "mrw" << "arw" << "k25" << "dcr" << "orf" << "raw" << "raw" << "raf" << "srf" << "dng"; + mimeType.suffixes = QStringList() << "bay" << "bmq" << "cr2" << "crw" << "cs1" << "dc2" << "dcr" << "dng" << "erf" << "fff" << "hdr" << "k25" << "kdc" << "mdc" << "mos" << "mrw" << "nef" << "orf" << "pef" << "pxn" << "raf" << "raw" << "rdc" << "sr2" << "srf" << "x3f" << "arw" << "3fr" << "cine" << "ia" << "kc2" << "mef" << "nrw" << "qtk" << "rw2" << "sti" << "rwl" << "srw"; s_mimeDatabase << mimeType; mimeType.mimeType = "application/x-extension-exr"; mimeType.description = i18nc("description of a file type", "OpenEXR (Extended)"); mimeType.suffixes = QStringList() << "exr"; s_mimeDatabase << mimeType; mimeType.mimeType = "image/x-psb"; mimeType.description = i18nc("description of a file type", "Photoshop Image (Large)"); mimeType.suffixes = QStringList() << "psb"; s_mimeDatabase << mimeType; + mimeType.mimeType = "image/heic"; + mimeType.description = i18nc("description of a file type", "HEIC/HEIF Image"); + mimeType.suffixes = QStringList() << "heic" << "heif"; + s_mimeDatabase << mimeType; + debugPlugin << "Filled mimedatabase with" << s_mimeDatabase.count() << "special mimetypes"; } } diff --git a/libs/libkis/Palette.cpp b/libs/libkis/Palette.cpp index 02270710ae..8ccde32577 100644 --- a/libs/libkis/Palette.cpp +++ b/libs/libkis/Palette.cpp @@ -1,158 +1,158 @@ /* * Copyright (c) 2017 Wolthera van Hövell tot Westerflier * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 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 Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "Palette.h" #include #include struct Palette::Private { KoColorSet *palette {0}; }; Palette::Palette(Resource *resource): d(new Private()) { d->palette = dynamic_cast(resource->resource()); } Palette::~Palette() { delete d; } int Palette::numberOfEntries() const { if (!d->palette) return 0; return d->palette->nColors(); } int Palette::columnCount() { if (!d->palette) return 0; return d->palette->columnCount(); } void Palette::setColumnCount(int columns) { if (d->palette) d->palette->setColumnCount(columns); } QString Palette::comment() { if (!d->palette) return ""; return d->palette->comment(); } void Palette::setComment(QString comment) { if (!d->palette) return; return d->palette->setComment(comment); } QStringList Palette::groupNames() { if (!d->palette) return QStringList(); return d->palette->getGroupNames(); } bool Palette::addGroup(QString name) { if (!d->palette) return false; return d->palette->addGroup(name); } bool Palette::removeGroup(QString name, bool keepColors) { if (!d->palette) return false; return d->palette->removeGroup(name, keepColors); } int Palette::colorsCountTotal() { if (!d->palette) return 0; return d->palette->nColors(); } int Palette::colorsCountGroup(QString name) { if (!d->palette) return 0; return d->palette->nColorsGroup(name); } KoColorSetEntry Palette::colorSetEntryByIndex(int index) { if (!d->palette) return KoColorSetEntry(); return d->palette->getColorGlobal(index); } KoColorSetEntry Palette::colorSetEntryFromGroup(int index, const QString &groupName) { if (!d->palette) return KoColorSetEntry(); return d->palette->getColorGroup(index, groupName); } ManagedColor *Palette::colorForEntry(KoColorSetEntry entry) { if (!d->palette) return 0; - ManagedColor *color = new ManagedColor(entry.color); + ManagedColor *color = new ManagedColor(entry.color()); return color; } void Palette::addEntry(KoColorSetEntry entry, QString groupName) { d->palette->add(entry, groupName); } void Palette::removeEntry(int index, const QString &groupName) { d->palette->removeAt(index, groupName); } void Palette::insertEntry(int index, KoColorSetEntry entry, QString groupName) { d->palette->insertBefore(entry, index, groupName); } bool Palette::editEntry(int index, KoColorSetEntry entry, QString groupName) { return d->palette->changeColorSetEntry(entry, groupName, index); } bool Palette::changeGroupName(QString oldGroupName, QString newGroupName) { return d->palette->changeGroupName(oldGroupName, newGroupName); } bool Palette::moveGroup(const QString &groupName, const QString &groupNameInsertBefore) { return d->palette->moveGroup(groupName, groupNameInsertBefore); } bool Palette::save() { if (d->palette->filename().size()>0) { return d->palette->save(); } //if there's no filename the palette proly doesn't even exist... return false; } KoColorSet *Palette::colorSet() { return d->palette; } diff --git a/libs/libkis/Palette.h b/libs/libkis/Palette.h index 4904f94ac0..901fd60c5f 100644 --- a/libs/libkis/Palette.h +++ b/libs/libkis/Palette.h @@ -1,204 +1,204 @@ /* * Copyright (c) 2017 Wolthera van Hövell tot Westerflier * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 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 Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef LIBKIS_PALETTE_H #define LIBKIS_PALETTE_H #include #include "kritalibkis_export.h" #include "libkis.h" #include "Resource.h" #include "KoColorSet.h" class ManagedColor; /** * @brief The Palette class * Palette is a resource object that stores organised color data. * It's purpose is to allow artists to save colors and store them. * * An example for printing all the palettes and the entries: * * @code import sys from krita import * resources = Application.resources("palette") for (k, v) in resources.items(): print(k) palette = Palette(v) for x in range(palette.numberOfEntries()): entry = palette.colorSetEntryByIndex(x) c = palette.colorForEntry(entry); - print(x, entry.name, entry.id, entry.spotColor, c.toQString()) + print(x, entry.name(), entry.id(), entry.spotColor(), c.toQString()) * @endcode */ class KRITALIBKIS_EXPORT Palette : public QObject { public: Palette(Resource *resource); ~Palette() override; /** * @brief numberOfEntries * @return */ int numberOfEntries() const; /** * @brief columnCount * @return the amount of columns this palette is set to use. */ int columnCount(); /** * @brief setColumnCount * Set the amount of columns this palette should use. */ void setColumnCount(int columns); /** * @brief comment * @return the comment or description associated with the palette. */ QString comment(); /** * @brief setComment * set the comment or description associated with the palette. * @param comment */ void setComment(QString comment); /** * @brief groupNames * @return the list of group names. This is list is in the order these groups are in the file. */ QStringList groupNames(); /** * @brief addGroup * @param name of the new group * @return whether adding the group was successful. */ bool addGroup(QString name); /** * @brief removeGroup * @param name the name of the group to remove. * @param keepColors whether or not to delete all the colors inside, or to move them to the default group. * @return */ bool removeGroup(QString name, bool keepColors = true); /** * @brief colorsCountTotal * @return the total amount of entries in the whole group */ int colorsCountTotal(); /** * @brief colorsCountGroup * @param name of the group to check. Empty is the default group. * @return the amount of colors within that group. */ int colorsCountGroup(QString name); /** * @brief colorSetEntryByIndex * get the colorsetEntry from the global index. * @param index the global index * @return the colorset entry */ KoColorSetEntry colorSetEntryByIndex(int index); /** * @brief colorSetEntryFromGroup * @param index index in the group. * @param groupName the name of the group to get the color from. * @return the colorsetentry. */ KoColorSetEntry colorSetEntryFromGroup(int index, const QString &groupName); /** * @brief colorForEntry * special function to retrieve a ManagedColor object from the colorsetentry. * @param entry the entry * @return the ManagedColorObject */ ManagedColor *colorForEntry(KoColorSetEntry entry); /** * @brief addEntry * add an entry to a group. Gets appended to the end. * @param entry the entry * @param groupName the name of the group to add to. */ void addEntry(KoColorSetEntry entry, QString groupName = QString()); /** * @brief removeEntry * remove the entry at @param index from the group @param groupName. */ void removeEntry(int index, const QString &groupName); /** * @brief insertEntry * like addentry, but allows you to pick the index to insertBefore. * @param index * @param entry * @param groupName */ void insertEntry(int index, KoColorSetEntry entry, QString groupName = QString()); /** * @brief editEntry * Changes the entry at @param index by replacing it with @param entry. * @param groupName the group at which the index is. * @return whether it was successful. */ bool editEntry (int index, KoColorSetEntry entry, QString groupName = QString()); /** * @brief changeGroupName * change the group name. * @param oldGroupName the old groupname to change. * @param newGroupName the new name to change it into. * @return whether successful. Reasons for failure include not knowing have oldGroupName */ bool changeGroupName(QString oldGroupName, QString newGroupName); /** * @brief moveGroup * move the group to before groupNameInsertBefore. * @param groupName group to move. * @param groupNameInsertBefore group to inset before. * @return whether successful. Reasons for failure include either group not existing. */ bool moveGroup(const QString &groupName, const QString &groupNameInsertBefore = QString()); /** * @brief save * save the palette * @return whether it was successful. */ bool save(); private: friend class PaletteView; struct Private; Private *const d; /** * @brief colorSet * @return gives qa KoColorSet object back */ KoColorSet *colorSet(); }; #endif // LIBKIS_PALETTE_H diff --git a/libs/libqml/plugins/kritasketchplugin/models/PaletteColorsModel.cpp b/libs/libqml/plugins/kritasketchplugin/models/PaletteColorsModel.cpp index 90af9b254d..4e5c027001 100644 --- a/libs/libqml/plugins/kritasketchplugin/models/PaletteColorsModel.cpp +++ b/libs/libqml/plugins/kritasketchplugin/models/PaletteColorsModel.cpp @@ -1,147 +1,147 @@ /* This file is part of the KDE project * Copyright (C) 2012 Dan Leinir Turthra Jensen * * 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 "PaletteColorsModel.h" #include #include #include class PaletteColorsModel::Private { public: Private() : colorSet(0) , view(0) {} KoColorSet* colorSet; KisViewManager* view; }; PaletteColorsModel::PaletteColorsModel(QObject *parent) : QAbstractListModel(parent) , d(new Private) { } PaletteColorsModel::~PaletteColorsModel() { delete d; } QHash PaletteColorsModel::roleNames() const { QHash roles; roles[ImageRole] = "image"; roles[TextRole] = "text"; return roles; } int PaletteColorsModel::rowCount(const QModelIndex &parent) const { if (parent.isValid()) return 0; if (!d->colorSet) return 0; return d->colorSet->nColors(); } QVariant PaletteColorsModel::data(const QModelIndex &index, int role) const { QVariant result; QColor color; if (index.isValid() && d->colorSet) { switch(role) { case ImageRole: - color = d->colorSet->getColorGlobal(index.row()).color.toQColor(); + color = d->colorSet->getColorGlobal(index.row()).color().toQColor(); result = QString("image://color/%1,%2,%3,%4").arg(color.redF()).arg(color.greenF()).arg(color.blueF()).arg(color.alphaF()); break; case TextRole: - result = d->colorSet->getColorGlobal(index.row()).name; + result = d->colorSet->getColorGlobal(index.row()).name(); break; default: break; } } return result; } QVariant PaletteColorsModel::headerData(int section, Qt::Orientation orientation, int role) const { Q_UNUSED(orientation); QVariant result; if (section == 0) { switch(role) { case ImageRole: result = QString("Thumbnail"); break; case TextRole: result = QString("Name"); break; default: break; } } return result; } void PaletteColorsModel::setColorSet(QObject *newColorSet) { d->colorSet = qobject_cast(newColorSet); beginResetModel(); endResetModel(); emit colorSetChanged(); } QObject* PaletteColorsModel::colorSet() const { return d->colorSet; } QObject* PaletteColorsModel::view() const { return d->view; } void PaletteColorsModel::setView(QObject* newView) { d->view = qobject_cast( newView ); emit viewChanged(); } void PaletteColorsModel::activateColor(int index, bool setBackgroundColor) { if ( !d->view ) return; if (index >= 0 && index < (int)d->colorSet->nColors()) { if (setBackgroundColor) - d->view->resourceProvider()->setBGColor(d->colorSet->getColorGlobal(index).color); + d->view->resourceProvider()->setBGColor(d->colorSet->getColorGlobal(index).color()); else - d->view->resourceProvider()->setFGColor( d->colorSet->getColorGlobal(index).color); - emit colorChanged(d->colorSet->getColorGlobal(index).color.toQColor(), setBackgroundColor); + d->view->resourceProvider()->setFGColor( d->colorSet->getColorGlobal(index).color()); + emit colorChanged(d->colorSet->getColorGlobal(index).color().toQColor(), setBackgroundColor); } } diff --git a/libs/pigment/CMakeLists.txt b/libs/pigment/CMakeLists.txt index 9e98621272..1d2c1a58f9 100644 --- a/libs/pigment/CMakeLists.txt +++ b/libs/pigment/CMakeLists.txt @@ -1,121 +1,122 @@ project(kritapigment) # we have to repeat platform specifics from top-level if (WIN32) include_directories(${CMAKE_SOURCE_DIR}/winquirks) add_definitions(-D_USE_MATH_DEFINES) add_definitions(-DNOMINMAX) set(WIN32_PLATFORM_NET_LIBS ws2_32.lib netapi32.lib) endif () include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/resources ${CMAKE_CURRENT_SOURCE_DIR}/compositeops) set(FILE_OPENEXR_SOURCES) set(LINK_OPENEXR_LIB) if(OPENEXR_FOUND) include_directories(SYSTEM ${OPENEXR_INCLUDE_DIR}) set(LINK_OPENEXR_LIB ${OPENEXR_LIBRARIES}) add_definitions(${OPENEXR_DEFINITIONS}) endif() set(LINK_VC_LIB) if(HAVE_VC) include_directories(SYSTEM ${Vc_INCLUDE_DIR}) set(LINK_VC_LIB ${Vc_LIBRARIES}) ko_compile_for_all_implementations_no_scalar(__per_arch_factory_objs compositeops/KoOptimizedCompositeOpFactoryPerArch.cpp) message("Following objects are generated from the per-arch lib") message("${__per_arch_factory_objs}") endif() add_subdirectory(tests) add_subdirectory(benchmarks) set(kritapigment_SRCS DebugPigment.cpp KoBasicHistogramProducers.cpp KoColor.cpp KoColorDisplayRendererInterface.cpp KoColorConversionAlphaTransformation.cpp KoColorConversionCache.cpp KoColorConversions.cpp KoColorConversionSystem.cpp KoColorConversionTransformation.cpp KoColorProofingConversionTransformation.cpp KoColorConversionTransformationFactory.cpp KoColorModelStandardIds.cpp KoColorProfile.cpp KoColorSpace.cpp KoColorSpaceEngine.cpp KoColorSpaceFactory.cpp KoColorSpaceMaths.cpp KoColorSpaceRegistry.cpp KoColorProfileStorage.cpp KoColorTransformation.cpp KoColorTransformationFactory.cpp KoColorTransformationFactoryRegistry.cpp KoCompositeColorTransformation.cpp KoCompositeOp.cpp KoCompositeOpRegistry.cpp KoCopyColorConversionTransformation.cpp KoFallBackColorTransformation.cpp KoHistogramProducer.cpp KoMultipleColorConversionTransformation.cpp KoUniqueNumberForIdServer.cpp colorspaces/KoAlphaColorSpace.cpp colorspaces/KoLabColorSpace.cpp colorspaces/KoRgbU16ColorSpace.cpp colorspaces/KoRgbU8ColorSpace.cpp colorspaces/KoSimpleColorSpaceEngine.cpp compositeops/KoOptimizedCompositeOpFactory.cpp compositeops/KoOptimizedCompositeOpFactoryPerArch_Scalar.cpp ${__per_arch_factory_objs} colorprofiles/KoDummyColorProfile.cpp resources/KoAbstractGradient.cpp resources/KoColorSet.cpp + resources/KoColorSetEntry.cpp resources/KoPattern.cpp resources/KoResource.cpp resources/KoMD5Generator.cpp resources/KoHashGeneratorProvider.cpp resources/KoStopGradient.cpp resources/KoSegmentGradient.cpp ) set (EXTRA_LIBRARIES ${LINK_OPENEXR_LIB} ${LINK_VC_LIB}) if(MSVC OR (WIN32 AND "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Intel")) # avoid "cannot open file 'LIBC.lib'" error set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /NODEFAULTLIB:LIBC.LIB") endif() add_library(kritapigment SHARED ${kritapigment_SRCS}) generate_export_header(kritapigment) target_include_directories( kritapigment PUBLIC $ $ ) target_link_libraries( kritapigment PUBLIC kritaplugin kritastore kritaglobal ${EXTRA_LIBRARIES} KF5::I18n KF5::ConfigCore Qt5::Core Qt5::Gui Qt5::Xml ${WIN32_PLATFORM_NET_LIBS} ) set_target_properties(kritapigment PROPERTIES VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION} ) install(TARGETS kritapigment ${INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/libs/pigment/KoCompositeOpRegistry.cpp b/libs/pigment/KoCompositeOpRegistry.cpp index 944d6df04a..22e810e8a6 100644 --- a/libs/pigment/KoCompositeOpRegistry.cpp +++ b/libs/pigment/KoCompositeOpRegistry.cpp @@ -1,216 +1,216 @@ /* * Copyright (c) 2005 Adrian Page * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoCompositeOpRegistry.h" #include #include #include #include #include "KoCompositeOp.h" #include "KoColorSpace.h" Q_GLOBAL_STATIC(KoCompositeOpRegistry, registry) KoCompositeOpRegistry::KoCompositeOpRegistry() { m_categories << KoID("arithmetic", i18n("Arithmetic")) << KoID("dark" , i18n("Darken")) << KoID("light" , i18n("Lighten")) << KoID("negative" , i18n("Negative")) << KoID("mix" , i18n("Mix")) << KoID("misc" , i18n("Misc")) << KoID("hsy" , i18n("HSY")) << KoID("hsi" , i18n("HSI")) << KoID("hsl" , i18n("HSL")) << KoID("hsv" , i18n("HSV")); m_map.insert(m_categories[0], KoID(COMPOSITE_ADD , i18n("Addition"))); m_map.insert(m_categories[0], KoID(COMPOSITE_SUBTRACT , i18n("Subtract"))); m_map.insert(m_categories[0], KoID(COMPOSITE_MULT , i18n("Multiply"))); m_map.insert(m_categories[0], KoID(COMPOSITE_DIVIDE , i18n("Divide"))); m_map.insert(m_categories[0], KoID(COMPOSITE_INVERSE_SUBTRACT, i18n("Inverse Subtract"))); m_map.insert(m_categories[1], KoID(COMPOSITE_BURN , i18n("Burn"))); m_map.insert(m_categories[1], KoID(COMPOSITE_LINEAR_BURN, i18n("Linear Burn"))); m_map.insert(m_categories[1], KoID(COMPOSITE_DARKEN , i18n("Darken"))); m_map.insert(m_categories[1], KoID(COMPOSITE_GAMMA_DARK , i18n("Gamma Dark"))); m_map.insert(m_categories[1], KoID(COMPOSITE_DARKER_COLOR , i18n("Darker Color"))); m_map.insert(m_categories[2], KoID(COMPOSITE_DODGE , i18n("Color Dodge"))); m_map.insert(m_categories[2], KoID(COMPOSITE_LINEAR_DODGE, i18n("Linear Dodge"))); m_map.insert(m_categories[2], KoID(COMPOSITE_LIGHTEN , i18n("Lighten"))); m_map.insert(m_categories[2], KoID(COMPOSITE_LINEAR_LIGHT, i18n("Linear Light"))); m_map.insert(m_categories[2], KoID(COMPOSITE_SCREEN , i18n("Screen"))); m_map.insert(m_categories[2], KoID(COMPOSITE_PIN_LIGHT , i18n("Pin Light"))); m_map.insert(m_categories[2], KoID(COMPOSITE_VIVID_LIGHT , i18n("Vivid Light"))); m_map.insert(m_categories[2], KoID(COMPOSITE_HARD_LIGHT , i18n("Hard Light"))); m_map.insert(m_categories[2], KoID(COMPOSITE_SOFT_LIGHT_PHOTOSHOP, i18n("Soft Light (Photoshop)"))); m_map.insert(m_categories[2], KoID(COMPOSITE_SOFT_LIGHT_SVG, i18n("Soft Light (SVG)"))); m_map.insert(m_categories[2], KoID(COMPOSITE_GAMMA_LIGHT , i18n("Gamma Light"))); m_map.insert(m_categories[2], KoID(COMPOSITE_LIGHTER_COLOR , i18n("Lighter Color"))); m_map.insert(m_categories[3], KoID(COMPOSITE_DIFF , i18n("Difference"))); m_map.insert(m_categories[3], KoID(COMPOSITE_EQUIVALENCE , i18n("Equivalence"))); m_map.insert(m_categories[3], KoID(COMPOSITE_ADDITIVE_SUBTRACTIVE, i18n("Additive Subtractive"))); m_map.insert(m_categories[3], KoID(COMPOSITE_EXCLUSION , i18n("Exclusion"))); m_map.insert(m_categories[3], KoID(COMPOSITE_ARC_TANGENT , i18n("Arcus Tangent"))); m_map.insert(m_categories[4], KoID(COMPOSITE_OVER , i18n("Normal"))); m_map.insert(m_categories[4], KoID(COMPOSITE_BEHIND , i18n("Behind"))); m_map.insert(m_categories[4], KoID(COMPOSITE_GREATER , i18n("Greater"))); m_map.insert(m_categories[4], KoID(COMPOSITE_OVERLAY , i18n("Overlay"))); m_map.insert(m_categories[4], KoID(COMPOSITE_ERASE , i18n("Erase"))); m_map.insert(m_categories[4], KoID(COMPOSITE_ALPHA_DARKEN , i18n("Alpha Darken"))); m_map.insert(m_categories[4], KoID(COMPOSITE_HARD_MIX , i18n("Hard Mix"))); m_map.insert(m_categories[4], KoID(COMPOSITE_HARD_MIX_PHOTOSHOP, i18n("Hard Mix (Photoshop)"))); m_map.insert(m_categories[4], KoID(COMPOSITE_GRAIN_MERGE , i18n("Grain Merge"))); m_map.insert(m_categories[4], KoID(COMPOSITE_GRAIN_EXTRACT , i18n("Grain Extract"))); m_map.insert(m_categories[4], KoID(COMPOSITE_PARALLEL , i18n("Parallel"))); m_map.insert(m_categories[4], KoID(COMPOSITE_ALLANON , i18n("Allanon"))); m_map.insert(m_categories[4], KoID(COMPOSITE_GEOMETRIC_MEAN , i18n("Geometric Mean"))); m_map.insert(m_categories[4], KoID(COMPOSITE_DESTINATION_ATOP, i18n("Destination Atop"))); m_map.insert(m_categories[4], KoID(COMPOSITE_DESTINATION_IN , i18n("Destination In"))); m_map.insert(m_categories[4], KoID(COMPOSITE_HARD_OVERLAY , i18n("Hard Overlay"))); m_map.insert(m_categories[5], KoID(COMPOSITE_BUMPMAP , i18n("Bumpmap"))); m_map.insert(m_categories[5], KoID(COMPOSITE_COMBINE_NORMAL, i18n("Combine Normal Map"))); m_map.insert(m_categories[5], KoID(COMPOSITE_DISSOLVE , i18n("Dissolve"))); m_map.insert(m_categories[5], KoID(COMPOSITE_COPY_RED , i18n("Copy Red"))); m_map.insert(m_categories[5], KoID(COMPOSITE_COPY_GREEN, i18n("Copy Green"))); m_map.insert(m_categories[5], KoID(COMPOSITE_COPY_BLUE , i18n("Copy Blue"))); m_map.insert(m_categories[5], KoID(COMPOSITE_COPY , i18n("Copy"))); m_map.insert(m_categories[5], KoID(COMPOSITE_TANGENT_NORMALMAP, i18n("Tangent Normalmap"))); m_map.insert(m_categories[6], KoID(COMPOSITE_COLOR , i18n("Color"))); m_map.insert(m_categories[6], KoID(COMPOSITE_HUE , i18n("Hue"))); m_map.insert(m_categories[6], KoID(COMPOSITE_SATURATION , i18n("Saturation"))); m_map.insert(m_categories[6], KoID(COMPOSITE_LUMINIZE , i18n("Luminosity"))); m_map.insert(m_categories[6], KoID(COMPOSITE_DEC_SATURATION, i18n("Decrease Saturation"))); m_map.insert(m_categories[6], KoID(COMPOSITE_INC_SATURATION, i18n("Increase Saturation"))); m_map.insert(m_categories[6], KoID(COMPOSITE_DEC_LUMINOSITY, i18n("Decrease Luminosity"))); m_map.insert(m_categories[6], KoID(COMPOSITE_INC_LUMINOSITY, i18n("Increase Luminosity"))); m_map.insert(m_categories[7], KoID(COMPOSITE_COLOR_HSI , i18n("Color HSI"))); m_map.insert(m_categories[7], KoID(COMPOSITE_HUE_HSI , i18n("Hue HSI"))); m_map.insert(m_categories[7], KoID(COMPOSITE_SATURATION_HSI , i18n("Saturation HSI"))); m_map.insert(m_categories[7], KoID(COMPOSITE_INTENSITY , i18n("Intensity"))); m_map.insert(m_categories[7], KoID(COMPOSITE_DEC_SATURATION_HSI, i18n("Decrease Saturation HSI"))); m_map.insert(m_categories[7], KoID(COMPOSITE_INC_SATURATION_HSI, i18n("Increase Saturation HSI"))); m_map.insert(m_categories[7], KoID(COMPOSITE_DEC_INTENSITY , i18n("Decrease Intensity"))); m_map.insert(m_categories[7], KoID(COMPOSITE_INC_INTENSITY , i18n("Increase Intensity"))); m_map.insert(m_categories[8], KoID(COMPOSITE_COLOR_HSL , i18n("Color HSL"))); m_map.insert(m_categories[8], KoID(COMPOSITE_HUE_HSL , i18n("Hue HSL"))); m_map.insert(m_categories[8], KoID(COMPOSITE_SATURATION_HSL , i18n("Saturation HSL"))); m_map.insert(m_categories[8], KoID(COMPOSITE_LIGHTNESS , i18n("Lightness"))); m_map.insert(m_categories[8], KoID(COMPOSITE_DEC_SATURATION_HSL, i18n("Decrease Saturation HSL"))); m_map.insert(m_categories[8], KoID(COMPOSITE_INC_SATURATION_HSL, i18n("Increase Saturation HSL"))); m_map.insert(m_categories[8], KoID(COMPOSITE_DEC_LIGHTNESS , i18n("Decrease Lightness"))); m_map.insert(m_categories[8], KoID(COMPOSITE_INC_LIGHTNESS , i18n("Increase Lightness"))); m_map.insert(m_categories[9], KoID(COMPOSITE_COLOR_HSV , i18n("Color HSV"))); m_map.insert(m_categories[9], KoID(COMPOSITE_HUE_HSV , i18n("Hue HSV"))); m_map.insert(m_categories[9], KoID(COMPOSITE_SATURATION_HSV , i18n("Saturation HSV"))); - m_map.insert(m_categories[9], KoID(COMPOSITE_VALUE , i18n("Value"))); + m_map.insert(m_categories[9], KoID(COMPOSITE_VALUE , i18nc("HSV Value", "Value"))); m_map.insert(m_categories[9], KoID(COMPOSITE_DEC_SATURATION_HSV, i18n("Decrease Saturation HSV"))); m_map.insert(m_categories[9], KoID(COMPOSITE_INC_SATURATION_HSV, i18n("Increase Saturation HSV"))); m_map.insert(m_categories[9], KoID(COMPOSITE_DEC_VALUE , i18n("Decrease Value"))); m_map.insert(m_categories[9], KoID(COMPOSITE_INC_VALUE , i18n("Increase Value"))); } const KoCompositeOpRegistry& KoCompositeOpRegistry::instance() { return *registry; } KoID KoCompositeOpRegistry::getDefaultCompositeOp() const { return KoID(COMPOSITE_OVER, i18n("Normal")); } KoID KoCompositeOpRegistry::getKoID(const QString& compositeOpID) const { KoIDMap::const_iterator itr = std::find(m_map.begin(), m_map.end(), KoID(compositeOpID)); return (itr != m_map.end()) ? *itr : KoID(); } KoCompositeOpRegistry::KoIDMap KoCompositeOpRegistry::getCompositeOps() const { return m_map; } KoCompositeOpRegistry::KoIDList KoCompositeOpRegistry::getCategories() const { return m_categories; } KoCompositeOpRegistry::KoIDList KoCompositeOpRegistry::getCompositeOps(const KoID& category, const KoColorSpace* colorSpace) const { qint32 num = m_map.count(category); KoIDMap::const_iterator beg = m_map.find(category); KoIDMap::const_iterator end = beg + num; KoIDList list; list.reserve(num); if(colorSpace) { for(; beg!=end; ++beg){ if(colorSpace->hasCompositeOp(beg->id())) list.push_back(*beg); } } else { for(; beg!=end; ++beg) list.push_back(*beg); } return list; } KoCompositeOpRegistry::KoIDList KoCompositeOpRegistry::getCompositeOps(const KoColorSpace* colorSpace) const { KoIDMap::const_iterator beg = m_map.begin(); KoIDMap::const_iterator end = m_map.end(); KoIDList list; list.reserve(m_map.size()); if(colorSpace) { for(; beg!=end; ++beg){ if(colorSpace->hasCompositeOp(beg->id())) list.push_back(*beg); } } else { for(; beg!=end; ++beg) list.push_back(*beg); } return list; } bool KoCompositeOpRegistry::colorSpaceHasCompositeOp(const KoColorSpace* colorSpace, const KoID& compositeOp) const { return colorSpace ? colorSpace->hasCompositeOp(compositeOp.id()) : false; } diff --git a/libs/pigment/compositeops/KoCompositeOps.h b/libs/pigment/compositeops/KoCompositeOps.h index ac92cd37e0..e908413e66 100644 --- a/libs/pigment/compositeops/KoCompositeOps.h +++ b/libs/pigment/compositeops/KoCompositeOps.h @@ -1,249 +1,249 @@ /* * Copyright (c) 2007 Cyrille Berger * Copyright (c) 2011 Silvio Heinrich * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef _KOCOMPOSITEOPS_H_ #define _KOCOMPOSITEOPS_H_ #include #include #include #include #include "compositeops/KoCompositeOpGeneric.h" #include "compositeops/KoCompositeOpOver.h" #include "compositeops/KoCompositeOpCopyChannel.h" #include "compositeops/KoCompositeOpAlphaDarken.h" #include "compositeops/KoCompositeOpErase.h" #include "compositeops/KoCompositeOpCopy2.h" #include "compositeops/KoCompositeOpDissolve.h" #include "compositeops/KoCompositeOpBehind.h" #include "compositeops/KoCompositeOpDestinationIn.h" #include "compositeops/KoCompositeOpDestinationAtop.h" #include "compositeops/KoCompositeOpGreater.h" #include "KoOptimizedCompositeOpFactory.h" namespace _Private { template struct AddGeneralOps { static void add(KoColorSpace* cs) { Q_UNUSED(cs); } }; template struct OptimizedOpsSelector { static KoCompositeOp* createAlphaDarkenOp(const KoColorSpace *cs) { return new KoCompositeOpAlphaDarken(cs); } static KoCompositeOp* createOverOp(const KoColorSpace *cs) { return new KoCompositeOpOver(cs); } }; template<> struct OptimizedOpsSelector { static KoCompositeOp* createAlphaDarkenOp(const KoColorSpace *cs) { return KoOptimizedCompositeOpFactory::createAlphaDarkenOp32(cs); } static KoCompositeOp* createOverOp(const KoColorSpace *cs) { return KoOptimizedCompositeOpFactory::createOverOp32(cs); } }; template<> struct OptimizedOpsSelector { static KoCompositeOp* createAlphaDarkenOp(const KoColorSpace *cs) { return KoOptimizedCompositeOpFactory::createAlphaDarkenOp32(cs); } static KoCompositeOp* createOverOp(const KoColorSpace *cs) { return KoOptimizedCompositeOpFactory::createOverOp32(cs); } }; template<> struct OptimizedOpsSelector { static KoCompositeOp* createAlphaDarkenOp(const KoColorSpace *cs) { return new KoCompositeOpAlphaDarken(cs); } static KoCompositeOp* createOverOp(const KoColorSpace *cs) { return KoOptimizedCompositeOpFactory::createOverOp128(cs); } }; template struct AddGeneralOps { typedef typename Traits::channels_type Arg; typedef Arg (*CompositeFunc)(Arg, Arg); static const qint32 alpha_pos = Traits::alpha_pos; template static void add(KoColorSpace* cs, const QString& id, const QString& description, const QString& category) { cs->addCompositeOp(new KoCompositeOpGenericSC(cs, id, description, category)); } static void add(KoColorSpace* cs) { cs->addCompositeOp(OptimizedOpsSelector::createOverOp(cs)); cs->addCompositeOp(OptimizedOpsSelector::createAlphaDarkenOp(cs)); cs->addCompositeOp(new KoCompositeOpCopy2(cs)); cs->addCompositeOp(new KoCompositeOpErase(cs)); cs->addCompositeOp(new KoCompositeOpBehind(cs)); cs->addCompositeOp(new KoCompositeOpDestinationIn(cs)); cs->addCompositeOp(new KoCompositeOpDestinationAtop(cs)); cs->addCompositeOp(new KoCompositeOpGreater(cs)); add<&cfOverlay >(cs, COMPOSITE_OVERLAY , i18n("Overlay") , KoCompositeOp::categoryMix()); add<&cfGrainMerge >(cs, COMPOSITE_GRAIN_MERGE , i18n("Grain Merge") , KoCompositeOp::categoryMix()); add<&cfGrainExtract >(cs, COMPOSITE_GRAIN_EXTRACT , i18n("Grain Extract") , KoCompositeOp::categoryMix()); add<&cfHardMix >(cs, COMPOSITE_HARD_MIX , i18n("Hard Mix") , KoCompositeOp::categoryMix()); add<&cfHardMixPhotoshop>(cs, COMPOSITE_HARD_MIX_PHOTOSHOP, i18n("Hard Mix (Photoshop)") , KoCompositeOp::categoryMix()); add<&cfGeometricMean >(cs, COMPOSITE_GEOMETRIC_MEAN, i18n("Geometric Mean"), KoCompositeOp::categoryMix()); add<&cfParallel >(cs, COMPOSITE_PARALLEL , i18n("Parallel") , KoCompositeOp::categoryMix()); add<&cfAllanon >(cs, COMPOSITE_ALLANON , i18n("Allanon") , KoCompositeOp::categoryMix()); add<&cfHardOverlay >(cs, COMPOSITE_HARD_OVERLAY , i18n("Hard Overlay") , KoCompositeOp::categoryMix()); add<&cfScreen >(cs, COMPOSITE_SCREEN , i18n("Screen") , KoCompositeOp::categoryLight()); add<&cfColorDodge >(cs, COMPOSITE_DODGE , i18n("Color Dodge") , KoCompositeOp::categoryLight()); add<&cfAddition >(cs, COMPOSITE_LINEAR_DODGE, i18n("Linear Dodge"), KoCompositeOp::categoryLight()); add<&cfLightenOnly >(cs, COMPOSITE_LIGHTEN , i18n("Lighten") , KoCompositeOp::categoryLight()); add<&cfHardLight >(cs, COMPOSITE_HARD_LIGHT , i18n("Hard Light") , KoCompositeOp::categoryLight()); add<&cfSoftLightSvg >(cs, COMPOSITE_SOFT_LIGHT_SVG, i18n("Soft Light (SVG)") , KoCompositeOp::categoryLight()); add<&cfSoftLight >(cs, COMPOSITE_SOFT_LIGHT_PHOTOSHOP, i18n("Soft Light (Photoshop)") , KoCompositeOp::categoryLight()); add<&cfGammaLight >(cs, COMPOSITE_GAMMA_LIGHT , i18n("Gamma Light") , KoCompositeOp::categoryLight()); add<&cfVividLight >(cs, COMPOSITE_VIVID_LIGHT , i18n("Vivid Light") , KoCompositeOp::categoryLight()); add<&cfPinLight >(cs, COMPOSITE_PIN_LIGHT , i18n("Pin Light") , KoCompositeOp::categoryLight()); add<&cfLinearLight >(cs, COMPOSITE_LINEAR_LIGHT, i18n("Linear Light"), KoCompositeOp::categoryLight()); add<&cfColorBurn >(cs, COMPOSITE_BURN , i18n("Color Burn") , KoCompositeOp::categoryDark()); add<&cfLinearBurn >(cs, COMPOSITE_LINEAR_BURN , i18n("Linear Burn"), KoCompositeOp::categoryDark()); add<&cfDarkenOnly >(cs, COMPOSITE_DARKEN , i18n("Darken") , KoCompositeOp::categoryDark()); add<&cfGammaDark >(cs, COMPOSITE_GAMMA_DARK , i18n("Gamma Dark") , KoCompositeOp::categoryDark()); add<&cfAddition >(cs, COMPOSITE_ADD , i18n("Addition") , KoCompositeOp::categoryArithmetic()); add<&cfSubtract >(cs, COMPOSITE_SUBTRACT , i18n("Subtract") , KoCompositeOp::categoryArithmetic()); add<&cfInverseSubtract >(cs, COMPOSITE_INVERSE_SUBTRACT, i18n("Inversed-Subtract"), KoCompositeOp::categoryArithmetic()); add<&cfMultiply >(cs, COMPOSITE_MULT , i18n("Multiply") , KoCompositeOp::categoryArithmetic()); add<&cfDivide >(cs, COMPOSITE_DIVIDE , i18n("Divide") , KoCompositeOp::categoryArithmetic()); add<&cfArcTangent >(cs, COMPOSITE_ARC_TANGENT , i18n("Arcus Tangent") , KoCompositeOp::categoryNegative()); add<&cfDifference >(cs, COMPOSITE_DIFF , i18n("Difference") , KoCompositeOp::categoryNegative()); add<&cfExclusion >(cs, COMPOSITE_EXCLUSION , i18n("Exclusion") , KoCompositeOp::categoryNegative()); add<&cfEquivalence >(cs, COMPOSITE_EQUIVALENCE , i18n("Equivalence") , KoCompositeOp::categoryNegative()); add<&cfAdditiveSubtractive >(cs, COMPOSITE_ADDITIVE_SUBTRACTIVE , i18n("Additive-Subtractive") , KoCompositeOp::categoryNegative()); cs->addCompositeOp(new KoCompositeOpDissolve(cs, KoCompositeOp::categoryMisc())); } }; template struct AddRGBOps { static void add(KoColorSpace* cs) { Q_UNUSED(cs); } }; template struct AddRGBOps { typedef float Arg; - + static const qint32 red_pos = Traits::red_pos; static const qint32 green_pos = Traits::green_pos; static const qint32 blue_pos = Traits::blue_pos; - + template static void add(KoColorSpace* cs, const QString& id, const QString& description, const QString& category) { cs->addCompositeOp(new KoCompositeOpGenericHSL(cs, id, description, category)); } - + static void add(KoColorSpace* cs) { cs->addCompositeOp(new KoCompositeOpCopyChannel(cs, COMPOSITE_COPY_RED , i18n("Copy Red") , KoCompositeOp::categoryMisc())); cs->addCompositeOp(new KoCompositeOpCopyChannel(cs, COMPOSITE_COPY_GREEN, i18n("Copy Green"), KoCompositeOp::categoryMisc())); cs->addCompositeOp(new KoCompositeOpCopyChannel(cs, COMPOSITE_COPY_BLUE , i18n("Copy Blue") , KoCompositeOp::categoryMisc())); add<&cfTangentNormalmap >(cs, COMPOSITE_TANGENT_NORMALMAP , i18n("Tangent Normalmap") , KoCompositeOp::categoryMisc()); add<&cfReorientedNormalMapCombine >(cs, COMPOSITE_COMBINE_NORMAL, i18n("Combine Normal Maps"), KoCompositeOp::categoryMisc()); - + add<&cfColor >(cs, COMPOSITE_COLOR , i18n("Color") , KoCompositeOp::categoryHSY()); add<&cfHue >(cs, COMPOSITE_HUE , i18n("Hue") , KoCompositeOp::categoryHSY()); add<&cfSaturation >(cs, COMPOSITE_SATURATION , i18n("Saturation") , KoCompositeOp::categoryHSY()); add<&cfIncreaseSaturation >(cs, COMPOSITE_INC_SATURATION, i18n("Increase Saturation"), KoCompositeOp::categoryHSY()); add<&cfDecreaseSaturation >(cs, COMPOSITE_DEC_SATURATION, i18n("Decrease Saturation"), KoCompositeOp::categoryHSY()); add<&cfLightness >(cs, COMPOSITE_LUMINIZE , i18n("Luminosity") , KoCompositeOp::categoryHSY()); add<&cfIncreaseLightness >(cs, COMPOSITE_INC_LUMINOSITY, i18n("Increase Luminosity"), KoCompositeOp::categoryHSY()); add<&cfDecreaseLightness >(cs, COMPOSITE_DEC_LUMINOSITY, i18n("Decrease Luminosity"), KoCompositeOp::categoryHSY()); add<&cfDarkerColor >(cs, COMPOSITE_DARKER_COLOR, i18n("Darker Color"), KoCompositeOp::categoryDark());//darker color as PSD does it// add<&cfLighterColor >(cs, COMPOSITE_LIGHTER_COLOR, i18n("Lighter Color"), KoCompositeOp::categoryLight());//lighter color as PSD does it// - + add<&cfColor >(cs, COMPOSITE_COLOR_HSI , i18n("Color HSI") , KoCompositeOp::categoryHSI()); add<&cfHue >(cs, COMPOSITE_HUE_HSI , i18n("Hue HSI") , KoCompositeOp::categoryHSI()); add<&cfSaturation >(cs, COMPOSITE_SATURATION_HSI , i18n("Saturation HSI") , KoCompositeOp::categoryHSI()); add<&cfIncreaseSaturation >(cs, COMPOSITE_INC_SATURATION_HSI, i18n("Increase Saturation HSI"), KoCompositeOp::categoryHSI()); add<&cfDecreaseSaturation >(cs, COMPOSITE_DEC_SATURATION_HSI, i18n("Decrease Saturation HSI"), KoCompositeOp::categoryHSI()); add<&cfLightness >(cs, COMPOSITE_INTENSITY , i18n("Intensity") , KoCompositeOp::categoryHSI()); add<&cfIncreaseLightness >(cs, COMPOSITE_INC_INTENSITY , i18n("Increase Intensity") , KoCompositeOp::categoryHSI()); add<&cfDecreaseLightness >(cs, COMPOSITE_DEC_INTENSITY , i18n("Decrease Intensity") , KoCompositeOp::categoryHSI()); - + add<&cfColor >(cs, COMPOSITE_COLOR_HSL , i18n("Color HSL") , KoCompositeOp::categoryHSL()); add<&cfHue >(cs, COMPOSITE_HUE_HSL , i18n("Hue HSL") , KoCompositeOp::categoryHSL()); add<&cfSaturation >(cs, COMPOSITE_SATURATION_HSL , i18n("Saturation HSL") , KoCompositeOp::categoryHSL()); add<&cfIncreaseSaturation >(cs, COMPOSITE_INC_SATURATION_HSL, i18n("Increase Saturation HSL"), KoCompositeOp::categoryHSL()); add<&cfDecreaseSaturation >(cs, COMPOSITE_DEC_SATURATION_HSL, i18n("Decrease Saturation HSL"), KoCompositeOp::categoryHSL()); add<&cfLightness >(cs, COMPOSITE_LIGHTNESS , i18n("Lightness") , KoCompositeOp::categoryHSL()); add<&cfIncreaseLightness >(cs, COMPOSITE_INC_LIGHTNESS , i18n("Increase Lightness") , KoCompositeOp::categoryHSL()); add<&cfDecreaseLightness >(cs, COMPOSITE_DEC_LIGHTNESS , i18n("Decrease Lightness") , KoCompositeOp::categoryHSL()); - + add<&cfColor >(cs, COMPOSITE_COLOR_HSV , i18n("Color HSV") , KoCompositeOp::categoryHSV()); add<&cfHue >(cs, COMPOSITE_HUE_HSV , i18n("Hue HSV") , KoCompositeOp::categoryHSV()); add<&cfSaturation >(cs, COMPOSITE_SATURATION_HSV , i18n("Saturation HSV") , KoCompositeOp::categoryHSV()); add<&cfIncreaseSaturation >(cs, COMPOSITE_INC_SATURATION_HSV, i18n("Increase Saturation HSV"), KoCompositeOp::categoryHSV()); add<&cfDecreaseSaturation >(cs, COMPOSITE_DEC_SATURATION_HSV, i18n("Decrease Saturation HSV"), KoCompositeOp::categoryHSV()); - add<&cfLightness >(cs, COMPOSITE_VALUE , i18n("Value") , KoCompositeOp::categoryHSV()); + add<&cfLightness >(cs, COMPOSITE_VALUE , i18nc("HSV Value","Value") , KoCompositeOp::categoryHSV()); add<&cfIncreaseLightness >(cs, COMPOSITE_INC_VALUE , i18n("Increase Value") , KoCompositeOp::categoryHSV()); add<&cfDecreaseLightness >(cs, COMPOSITE_DEC_VALUE , i18n("Decrease Value") , KoCompositeOp::categoryHSV()); } }; } /** * This function add to the colorspace all the composite ops defined by * the pigment library. */ template void addStandardCompositeOps(KoColorSpace* cs) { typedef typename _Traits_::channels_type channels_type; - + static const bool useGeneralOps = true; static const bool useRGBOps = (boost::is_base_of, _Traits_>::value || boost::is_base_of, _Traits_>::value); - + _Private::AddGeneralOps<_Traits_, useGeneralOps>::add(cs); _Private::AddRGBOps <_Traits_, useRGBOps >::add(cs); } #endif diff --git a/libs/pigment/compositeops/KoVcMultiArchBuildSupport.h b/libs/pigment/compositeops/KoVcMultiArchBuildSupport.h index 2bdd069a74..f81fbea0f6 100644 --- a/libs/pigment/compositeops/KoVcMultiArchBuildSupport.h +++ b/libs/pigment/compositeops/KoVcMultiArchBuildSupport.h @@ -1,112 +1,122 @@ /* * Copyright (c) 2012 Dmitry Kazakov * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef __KOVCMULTIARCHBUILDSUPPORT_H #define __KOVCMULTIARCHBUILDSUPPORT_H #include "config-vc.h" #ifdef HAVE_VC #if defined(__clang__) #pragma GCC diagnostic ignored "-Wlocal-type-template-args" #endif #if defined _MSC_VER // Lets shut up the "possible loss of data" and "forcing value to bool 'true' or 'false' #pragma warning ( push ) #pragma warning ( disable : 4244 ) #pragma warning ( disable : 4800 ) #endif #include #include #include #if defined _MSC_VER #pragma warning ( pop ) #endif #else /* HAVE_VC */ namespace Vc { enum Implementation /*: std::uint_least32_t*/ { ScalarImpl, }; class CurrentImplementation { public: static constexpr Implementation current() { return static_cast(ScalarImpl); } }; } #endif /* HAVE_VC */ #include #include #include #include template typename FactoryType::ReturnType createOptimizedClass(typename FactoryType::ParamType param) { static bool isConfigInitialized = false; static bool useVectorization = true; if (!isConfigInitialized) { KConfigGroup cfg = KSharedConfig::openConfig()->group(""); useVectorization = !cfg.readEntry("amdDisableVectorWorkaround", false); isConfigInitialized = true; } if (!useVectorization) { qWarning() << "WARNING: vector instructions disabled by \'amdDisableVectorWorkaround\' option!"; return FactoryType::template create(param); } #ifdef HAVE_VC /** * We use SSE2, SSSE3, SSE4.1, AVX and AVX2. * The rest are integer and string instructions mostly. * * TODO: Add FMA3/4 when it is adopted by Vc */ if (Vc::isImplementationSupported(Vc::AVX2Impl)) { return FactoryType::template create(param); } else if (Vc::isImplementationSupported(Vc::AVXImpl)) { return FactoryType::template create(param); } else if (Vc::isImplementationSupported(Vc::SSE41Impl)) { return FactoryType::template create(param); } else if (Vc::isImplementationSupported(Vc::SSSE3Impl)) { return FactoryType::template create(param); } else if (Vc::isImplementationSupported(Vc::SSE2Impl)) { return FactoryType::template create(param); } else { #endif return FactoryType::template create(param); #ifdef HAVE_VC } #endif } +template +typename FactoryType::ReturnType +createOptimizedClass(typename FactoryType::ParamType param, bool forceScalarImplemetation) +{ + if(forceScalarImplemetation){ + return FactoryType::template create(param); + } + return createOptimizedClass(param); +} + #endif /* __KOVCMULTIARCHBUILDSUPPORT_H */ diff --git a/libs/pigment/resources/KoColorSet.cpp b/libs/pigment/resources/KoColorSet.cpp index 9fa531c60d..26ae96982e 100644 --- a/libs/pigment/resources/KoColorSet.cpp +++ b/libs/pigment/resources/KoColorSet.cpp @@ -1,1569 +1,1573 @@ /* This file is part of the KDE project Copyright (c) 2005 Boudewijn Rempt Copyright (c) 2016 L. E. Segovia This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include // qFromLittleEndian #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KoColor.h" +#include "KoColorSetEntry.h" #include "KoColorProfile.h" #include "KoColorSpaceRegistry.h" #include "KoColorModelStandardIds.h" struct KoColorSet::Private { KoColorSet::PaletteType paletteType; QByteArray data; QString comment; qint32 columns; QVector colors; //ungrouped colors QStringList groupNames; //names of the groups, this is used to determine the order they are in. QMap> groups; //grouped colors. }; KoColorSet::PaletteType detectFormat(const QString &fileName, const QByteArray &ba) { QFileInfo fi(fileName); // .pal if (ba.startsWith("RIFF") && ba.indexOf("PAL data", 8)) { return KoColorSet::RIFF_PAL; } // .gpl else if (ba.startsWith("GIMP Palette")) { return KoColorSet::GPL; } // .pal else if (ba.startsWith("JASC-PAL")) { return KoColorSet::PSP_PAL; } else if (fi.suffix().toLower() == "aco") { return KoColorSet::ACO; } else if (fi.suffix().toLower() == "act") { return KoColorSet::ACT; } else if (fi.suffix().toLower() == "xml") { return KoColorSet::XML; } else if (fi.suffix().toLower() == "kpl") { return KoColorSet::KPL; } else if (fi.suffix().toLower() == "sbz") { return KoColorSet::SBZ; } return KoColorSet::UNKNOWN; } KoColorSet::KoColorSet(const QString& filename) : KoResource(filename) , d(new Private()) { // Implemented in KoResource class d->columns = 0; // Set the default value that the GIMP uses... } KoColorSet::KoColorSet() : KoResource(QString()) , d(new Private()) { d->columns = 0; // Set the default value that the GIMP uses... } /// Create an copied palette KoColorSet::KoColorSet(const KoColorSet& rhs) : QObject(0) , KoResource(QString()) , d(new Private()) { setFilename(rhs.filename()); d->comment = rhs.d->comment; d->columns = rhs.d->columns; d->colors = rhs.d->colors; d->groupNames = rhs.d->groupNames; d->groups = rhs.d->groups; setValid(true); } KoColorSet::~KoColorSet() { } bool KoColorSet::load() { QFile file(filename()); if (file.size() == 0) return false; if (!file.open(QIODevice::ReadOnly)) { warnPigment << "Can't open file " << filename(); return false; } bool res = loadFromDevice(&file); file.close(); return res; } bool KoColorSet::loadFromDevice(QIODevice *dev) { if (!dev->isOpen()) dev->open(QIODevice::ReadOnly); d->data = dev->readAll(); Q_ASSERT(d->data.size() != 0); return init(); } bool KoColorSet::save() { QFile file(filename()); if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { return false; } saveToDevice(&file); file.close(); return true; } bool KoColorSet::saveToDevice(QIODevice *dev) const { bool res; switch(d->paletteType) { case GPL: res = saveGpl(dev); break; default: res = saveKpl(dev); } if (res) { KoResource::saveToDevice(dev); } return res; } bool KoColorSet::init() { d->colors.clear(); // just in case this is a reload (eg by KoEditColorSetDialog), d->groups.clear(); d->groupNames.clear(); if (filename().isNull()) { warnPigment << "Cannot load palette" << name() << "there is no filename set"; return false; } if (d->data.isNull()) { QFile file(filename()); if (file.size() == 0) { warnPigment << "Cannot load palette" << name() << "there is no data available"; return false; } file.open(QIODevice::ReadOnly); d->data = file.readAll(); file.close(); } bool res = false; d->paletteType = detectFormat(filename(), d->data); switch(d->paletteType) { case GPL: res = loadGpl(); break; case ACT: res = loadAct(); break; case RIFF_PAL: res = loadRiff(); break; case PSP_PAL: res = loadPsp(); break; case ACO: res = loadAco(); break; case XML: res = loadXml(); break; case KPL: res = loadKpl(); break; case SBZ: res = loadSbz(); break; default: res = false; } setValid(res); if (d->columns == 0) { d->columns = 10; } QImage img(d->columns * 4, (d->colors.size() / d->columns) * 4, QImage::Format_ARGB32); QPainter gc(&img); gc.fillRect(img.rect(), Qt::darkGray); int counter = 0; for(int i = 0; i < d->columns; ++i) { for (int j = 0; j < (d->colors.size() / d->columns); ++j) { if (counter < d->colors.size()) { - QColor c = d->colors.at(counter).color.toQColor(); + QColor c = d->colors.at(counter).color().toQColor(); gc.fillRect(i * 4, j * 4, 4, 4, c); counter++; } else { break; } } } setImage(img); // save some memory d->data.clear(); return res; } bool KoColorSet::saveGpl(QIODevice *dev) const { QTextStream stream(dev); stream << "GIMP Palette\nName: " << name() << "\nColumns: " << d->columns << "\n#\n"; for (int i = 0; i < d->colors.size(); i++) { const KoColorSetEntry& entry = d->colors.at(i); - QColor c = entry.color.toQColor(); + QColor c = entry.color().toQColor(); stream << c.red() << " " << c.green() << " " << c.blue() << "\t"; - if (entry.name.isEmpty()) + if (entry.name().isEmpty()) stream << "Untitled\n"; else - stream << entry.name << "\n"; + stream << entry.name() << "\n"; } return true; } quint32 KoColorSet::nColors() { if (d->colors.isEmpty()) return 0; quint32 total = d->colors.size(); if (!d->groups.empty()) { Q_FOREACH (const QVector &group, d->groups.values()) { total += group.size(); } } return total; } quint32 KoColorSet::nColorsGroup(QString groupName) { if (d->groups.contains(groupName)) { return d->groups.value(groupName).size(); } else if (groupName.isEmpty() && !d->colors.isEmpty()){ return d->colors.size(); } else { return 0; } } quint32 KoColorSet::getIndexClosestColor(const KoColor color, bool useGivenColorSpace) { quint32 closestIndex = 0; quint8 highestPercentage = 0; quint8 testPercentage = 0; KoColor compare = color; for (quint32 i=0; idifference(compare.data(), entry.data())); if (testPercentage>highestPercentage) { closestIndex = i; highestPercentage = testPercentage; } } return closestIndex; } QString KoColorSet::closestColorName(const KoColor color, bool useGivenColorSpace) { int i = getIndexClosestColor(color, useGivenColorSpace); - QString name = getColorGlobal(i).name; - return name; + return getColorGlobal(i).name(); } void KoColorSet::add(const KoColorSetEntry & c, QString groupName) { if (d->groups.contains(groupName) || d->groupNames.contains(groupName)) { d->groups[groupName].push_back(c); } else { d->colors.push_back(c); } } quint32 KoColorSet::insertBefore(const KoColorSetEntry &c, qint32 index, const QString &groupName) { quint32 newIndex = index; if (d->groups.contains(groupName)) { d->groups[groupName].insert(index, c); } else if (groupName.isEmpty()){ d->colors.insert(index, c); } else { warnPigment << "Couldn't find group to insert to"; } return newIndex; } void KoColorSet::removeAt(quint32 index, QString groupName) { if (d->groups.contains(groupName)){ if ((quint32)d->groups.value(groupName).size()>index) { d->groups[groupName].remove(index); } } else { if ((quint32)d->colors.size()>index) { d->colors.remove(index); } } } void KoColorSet::clear() { d->colors.clear(); d->groups.clear(); } KoColorSetEntry KoColorSet::getColorGlobal(quint32 index) { KoColorSetEntry e; quint32 groupIndex = index; QString groupName = findGroupByGlobalIndex(index, &groupIndex); e = getColorGroup(groupIndex, groupName); return e; } KoColorSetEntry KoColorSet::getColorGroup(quint32 index, QString groupName) { KoColorSetEntry e; if (d->groups.contains(groupName)) { if (nColorsGroup(groupName)>index) { e = d->groups.value(groupName).at(index); } else { warnPigment<index) { e = d->colors.at(index); } else { warnPigment<colors.size()<=*index) { *index -= (quint32)d->colors.size(); if (!d->groups.empty() || !d->groupNames.empty()) { QStringList groupNames = getGroupNames(); Q_FOREACH (QString name, groupNames) { quint32 size = (quint32)d->groups.value(name).size(); if (size<=*index) { *index -= size; } else { groupName = name; return groupName; } } } } return groupName; } QString KoColorSet::findGroupByColorName(const QString &name, quint32 *index) { *index = 0; QString groupName = QString(); for (int i = 0; icolors.size(); i++) { - if(d->colors.at(i).name == name) { + if(d->colors.at(i).name() == name) { *index = (quint32)i; return groupName; } } QStringList groupNames = getGroupNames(); Q_FOREACH (QString name, groupNames) { for (int i=0; igroups[name].size(); i++) { - if(d->groups[name].at(i).name == name) { + if(d->groups[name].at(i).name() == name) { *index = (quint32)i; groupName = name; return groupName; } } } return groupName; } QString KoColorSet::findGroupByID(const QString &id, quint32 *index) { *index = 0; QString groupName = QString(); for (int i = 0; icolors.size(); i++) { - if(d->colors.at(i).id == id) { + if(d->colors.at(i).id() == id) { *index = (quint32)i; return groupName; } } QStringList groupNames = getGroupNames(); Q_FOREACH (QString name, groupNames) { for (int i=0; igroups[name].size(); i++) { - if(d->groups[name].at(i).id == id) { + if(d->groups[name].at(i).id() == id) { *index = (quint32)i; groupName = name; return groupName; } } } return groupName; } QStringList KoColorSet::getGroupNames() { if (d->groupNames.size()groups.size()) { warnPigment << "mismatch between groups and the groupnames list."; return QStringList(d->groups.keys()); } return d->groupNames; } bool KoColorSet::changeGroupName(QString oldGroupName, QString newGroupName) { if (d->groupNames.contains(oldGroupName)==false) { return false; } QVector dummyList = d->groups.value(oldGroupName); d->groups.remove(oldGroupName); d->groups[newGroupName] = dummyList; //rename the string in the stringlist; int index = d->groupNames.indexOf(oldGroupName); d->groupNames.replace(index, newGroupName); return true; } bool KoColorSet::changeColorSetEntry(KoColorSetEntry entry, QString groupName, quint32 index) { if (index>=nColorsGroup(groupName) || (d->groupNames.contains(groupName)==false && groupName.size()>0)) { return false; } if (groupName==QString()) { d->colors[index] = entry; } else { d->groups[groupName][index] = entry; } return true; } void KoColorSet::setColumnCount(int columns) { d->columns = columns; } int KoColorSet::columnCount() { return d->columns; } QString KoColorSet::comment() { return d->comment; } void KoColorSet::setComment(QString comment) { d->comment = comment; } bool KoColorSet::addGroup(const QString &groupName) { if (d->groups.contains(groupName) || d->groupNames.contains(groupName)) { return false; } d->groupNames.append(groupName); d->groups[groupName] = QVector(); return true; } bool KoColorSet::moveGroup(const QString &groupName, const QString &groupNameInsertBefore) { if (d->groupNames.contains(groupName)==false || d->groupNames.contains(groupNameInsertBefore)==false) { return false; } d->groupNames.removeAt(d->groupNames.indexOf(groupName)); int index = d->groupNames.size(); if (groupNameInsertBefore!=QString()) { index = d->groupNames.indexOf(groupNameInsertBefore); } d->groupNames.insert(index, groupName); return true; } bool KoColorSet::removeGroup(const QString &groupName, bool keepColors) { if (!d->groups.contains(groupName)) { return false; } if (keepColors) { for (int i = 0; igroups.value(groupName).size(); i++) { d->colors.append(d->groups.value(groupName).at(i)); } } for(int n = 0; ngroupNames.size(); n++) { if (d->groupNames.at(n) == groupName) { d->groupNames.removeAt(n); } } d->groups.remove(groupName); return true; } QString KoColorSet::defaultFileExtension() const { return QString(".kpl"); } bool KoColorSet::loadGpl() { QString s = QString::fromUtf8(d->data.data(), d->data.count()); if (s.isEmpty() || s.isNull() || s.length() < 50) { warnPigment << "Illegal Gimp palette file: " << filename(); return false; } quint32 index = 0; QStringList lines = s.split('\n', QString::SkipEmptyParts); if (lines.size() < 3) { return false; } QString columns; qint32 r, g, b; KoColorSetEntry e; // Read name if (!lines[0].startsWith("GIMP") || !lines[1].startsWith("Name: ")) { warnPigment << "Illegal Gimp palette file: " << filename(); return false; } setName(i18n(lines[1].mid(strlen("Name: ")).trimmed().toLatin1())); index = 2; // Read columns if (lines[index].startsWith("Columns: ")) { columns = lines[index].mid(strlen("Columns: ")).trimmed(); d->columns = columns.toInt(); index = 3; } for (qint32 i = index; i < lines.size(); i++) { if (lines[i].startsWith('#')) { d->comment += lines[i].mid(1).trimmed() + ' '; } else if (!lines[i].isEmpty()) { QStringList a = lines[i].replace('\t', ' ').split(' ', QString::SkipEmptyParts); if (a.count() < 3) { break; } r = a[0].toInt(); a.pop_front(); g = a[0].toInt(); a.pop_front(); b = a[0].toInt(); a.pop_front(); r = qBound(0, r, 255); g = qBound(0, g, 255); b = qBound(0, b, 255); - e.color = KoColor(KoColorSpaceRegistry::instance()->rgb8()); - e.color.fromQColor(QColor(r, g, b)); + e.setColor(KoColor(QColor(r, g, b), KoColorSpaceRegistry::instance()->rgb8())); QString name = a.join(" "); - e.name = name.isEmpty() ? i18n("Untitled") : name; + e.setName(name.isEmpty() ? i18n("Untitled") : name); add(e); } } return true; } bool KoColorSet::loadAct() { QFileInfo info(filename()); setName(info.baseName()); KoColorSetEntry e; for (int i = 0; i < d->data.size(); i += 3) { quint8 r = d->data[i]; quint8 g = d->data[i+1]; quint8 b = d->data[i+2]; - e.color = KoColor(KoColorSpaceRegistry::instance()->rgb8()); - e.color.fromQColor(QColor(r, g, b)); + e.setColor(KoColor(QColor(r, g, b), KoColorSpaceRegistry::instance()->rgb8())); add(e); } return true; } struct RiffHeader { quint32 riff; quint32 size; quint32 signature; quint32 data; quint32 datasize; quint16 version; quint16 colorcount; }; bool KoColorSet::loadRiff() { // http://worms2d.info/Palette_file QFileInfo info(filename()); setName(info.baseName()); KoColorSetEntry e; RiffHeader header; memcpy(&header, d->data.constData(), sizeof(RiffHeader)); header.colorcount = qFromBigEndian(header.colorcount); for (int i = sizeof(RiffHeader); (i < (int)(sizeof(RiffHeader) + header.colorcount) && i < d->data.size()); i += 4) { quint8 r = d->data[i]; quint8 g = d->data[i+1]; quint8 b = d->data[i+2]; - e.color = KoColor(KoColorSpaceRegistry::instance()->rgb8()); - e.color.fromQColor(QColor(r, g, b)); + e.setColor(KoColor(QColor(r, g, b), KoColorSpaceRegistry::instance()->rgb8())); add(e); } return true; } bool KoColorSet::loadPsp() { QFileInfo info(filename()); setName(info.baseName()); KoColorSetEntry e; qint32 r, g, b; QString s = QString::fromUtf8(d->data.data(), d->data.count()); QStringList l = s.split('\n', QString::SkipEmptyParts); if (l.size() < 4) return false; if (l[0] != "JASC-PAL") return false; if (l[1] != "0100") return false; int entries = l[2].toInt(); for (int i = 0; i < entries; ++i) { QStringList a = l[i + 3].replace('\t', ' ').split(' ', QString::SkipEmptyParts); if (a.count() != 3) { continue; } r = a[0].toInt(); a.pop_front(); g = a[0].toInt(); a.pop_front(); b = a[0].toInt(); a.pop_front(); r = qBound(0, r, 255); g = qBound(0, g, 255); b = qBound(0, b, 255); - e.color = KoColor(KoColorSpaceRegistry::instance()->rgb8()); - e.color.fromQColor(QColor(r, g, b)); + e.setColor(KoColor(QColor(r, g, b), + KoColorSpaceRegistry::instance()->rgb8())); QString name = a.join(" "); - e.name = name.isEmpty() ? i18n("Untitled") : name; + e.setName(name.isEmpty() ? i18n("Untitled") : name); add(e); } return true; } void scribusParseColor(KoColorSet *set, QXmlStreamReader *xml) { - KoColorSetEntry currentColor; - //It's a color, retrieve it + KoColorSetEntry colorEntry; + // It's a color, retrieve it QXmlStreamAttributes colorProperties = xml->attributes(); - QStringRef colorValue; + + QStringRef colorName = colorProperties.value("NAME"); + colorEntry.setName(colorName.isEmpty() || colorName.isNull() ? i18n("Untitled") : colorName.toString()); // RGB or CMYK? if (colorProperties.hasAttribute("RGB")) { dbgPigment << "Color " << colorProperties.value("NAME") << ", RGB " << colorProperties.value("RGB"); - QStringRef colorName = colorProperties.value("NAME"); - currentColor.name = colorName.isEmpty() || colorName.isNull() ? i18n("Untitled") : colorName.toString(); - - currentColor.color = KoColor(KoColorSpaceRegistry::instance()->rgb8()); + KoColor currentColor(KoColorSpaceRegistry::instance()->rgb8()); + QStringRef colorValue = colorProperties.value("RGB"); - colorValue = colorProperties.value("RGB"); if (colorValue.length() != 7 && colorValue.at(0) != '#') { // Color is a hexadecimal number xml->raiseError("Invalid rgb8 color (malformed): " + colorValue); return; - } - else { + } else { bool rgbOk; quint32 rgb = colorValue.mid(1).toUInt(&rgbOk, 16); if (!rgbOk) { xml->raiseError("Invalid rgb8 color (unable to convert): " + colorValue); return; } quint8 r = rgb >> 16 & 0xff; quint8 g = rgb >> 8 & 0xff; quint8 b = rgb & 0xff; dbgPigment << "Color parsed: "<< r << g << b; - currentColor.color.data()[0] = r; - currentColor.color.data()[1] = g; - currentColor.color.data()[2] = b; - currentColor.color.setOpacity(OPACITY_OPAQUE_U8); + currentColor.data()[0] = r; + currentColor.data()[1] = g; + currentColor.data()[2] = b; + currentColor.setOpacity(OPACITY_OPAQUE_U8); + colorEntry.setColor(currentColor); - set->add(currentColor); + set->add(colorEntry); while(xml->readNextStartElement()) { //ignore - these are all unknown or the /> element tag xml->skipCurrentElement(); } return; } } else if (colorProperties.hasAttribute("CMYK")) { dbgPigment << "Color " << colorProperties.value("NAME") << ", CMYK " << colorProperties.value("CMYK"); - QStringRef colorName = colorProperties.value("NAME"); - currentColor.name = colorName.isEmpty() || colorName.isNull() ? i18n("Untitled") : colorName.toString(); - - currentColor.color = KoColor(KoColorSpaceRegistry::instance()->colorSpace(CMYKAColorModelID.id(), Integer8BitsColorDepthID.id(), QString())); + KoColor currentColor(KoColorSpaceRegistry::instance()->colorSpace(CMYKAColorModelID.id(), Integer8BitsColorDepthID.id(), QString())); - colorValue = colorProperties.value("CMYK"); + QStringRef colorValue = colorProperties.value("CMYK"); if (colorValue.length() != 9 && colorValue.at(0) != '#') { // Color is a hexadecimal number xml->raiseError("Invalid cmyk color (malformed): " % colorValue); return; } else { bool cmykOk; quint32 cmyk = colorValue.mid(1).toUInt(&cmykOk, 16); // cmyk uses the full 32 bits if (!cmykOk) { xml->raiseError("Invalid cmyk color (unable to convert): " % colorValue); return; } quint8 c = cmyk >> 24 & 0xff; quint8 m = cmyk >> 16 & 0xff; quint8 y = cmyk >> 8 & 0xff; quint8 k = cmyk & 0xff; dbgPigment << "Color parsed: "<< c << m << y << k; - currentColor.color.data()[0] = c; - currentColor.color.data()[1] = m; - currentColor.color.data()[2] = y; - currentColor.color.data()[3] = k; - currentColor.color.setOpacity(OPACITY_OPAQUE_U8); + currentColor.data()[0] = c; + currentColor.data()[1] = m; + currentColor.data()[2] = y; + currentColor.data()[3] = k; + currentColor.setOpacity(OPACITY_OPAQUE_U8); + colorEntry.setColor(currentColor); - set->add(currentColor); + set->add(colorEntry); while(xml->readNextStartElement()) { //ignore - these are all unknown or the /> element tag xml->skipCurrentElement(); } return; } } else { - xml->raiseError("Unknown color space for color " + currentColor.name); + xml->raiseError("Unknown color space for color " + colorEntry.name()); } } bool loadScribusXmlPalette(KoColorSet *set, QXmlStreamReader *xml) { //1. Get name QXmlStreamAttributes paletteProperties = xml->attributes(); QStringRef paletteName = paletteProperties.value("Name"); dbgPigment << "Processed name of palette:" << paletteName; set->setName(paletteName.toString()); //2. Inside the SCRIBUSCOLORS, there are lots of colors. Retrieve them while(xml->readNextStartElement()) { QStringRef currentElement = xml->name(); if(QStringRef::compare(currentElement, "COLOR", Qt::CaseInsensitive) == 0) { scribusParseColor(set, xml); } else { xml->skipCurrentElement(); } } if(xml->hasError()) { return false; } return true; } bool KoColorSet::loadXml() { bool res = false; QXmlStreamReader *xml = new QXmlStreamReader(d->data); if (xml->readNextStartElement()) { QStringRef paletteId = xml->name(); if (QStringRef::compare(paletteId, "SCRIBUSCOLORS", Qt::CaseInsensitive) == 0) { // Scribus dbgPigment << "XML palette: " << filename() << ", Scribus format"; res = loadScribusXmlPalette(this, xml); } else { // Unknown XML format xml->raiseError("Unknown XML palette format. Expected SCRIBUSCOLORS, found " + paletteId); } } // If there is any error (it should be returned through the stream) if (xml->hasError() || !res) { warnPigment << "Illegal XML palette:" << filename(); warnPigment << "Error (line"<< xml->lineNumber() << ", column" << xml->columnNumber() << "):" << xml->errorString(); return false; } else { dbgPigment << "XML palette parsed successfully:" << filename(); return true; } } bool KoColorSet::saveKpl(QIODevice *dev) const { QScopedPointer store(KoStore::createStore(dev, KoStore::Write, "application/x-krita-palette", KoStore::Zip)); if (!store || store->bad()) return false; QSet profiles; QMap profileMap; { QDomDocument doc; QDomElement root = doc.createElement("Colorset"); root.setAttribute("version", "1.0"); root.setAttribute("name", name()); root.setAttribute("comment", d->comment); root.setAttribute("columns", d->columns); Q_FOREACH(const KoColorSetEntry &entry, d->colors) { // Only save non-builtin profiles.= - const KoColorProfile *profile = entry.color.colorSpace()->profile(); + const KoColorProfile *profile = entry.color().colorSpace()->profile(); if (!profile->fileName().isEmpty()) { profiles << profile; - profileMap[profile] = entry.color.colorSpace(); + profileMap[profile] = entry.color().colorSpace(); } QDomElement el = doc.createElement("ColorSetEntry"); - el.setAttribute("name", entry.name); - el.setAttribute("id", entry.id); - el.setAttribute("spot", entry.spotColor ? "true" : "false"); - el.setAttribute("bitdepth", entry.color.colorSpace()->colorDepthId().id()); - entry.color.toXML(doc, el); + el.setAttribute("name", entry.name()); + el.setAttribute("id", entry.id()); + el.setAttribute("spot", entry.spotColor() ? "true" : "false"); + el.setAttribute("bitdepth", entry.color().colorSpace()->colorDepthId().id()); + entry.color().toXML(doc, el); root.appendChild(el); } Q_FOREACH(const QString &groupName, d->groupNames) { QDomElement gl = doc.createElement("Group"); gl.setAttribute("name", groupName); root.appendChild(gl); Q_FOREACH(const KoColorSetEntry &entry, d->groups.value(groupName)) { // Only save non-builtin profiles.= - const KoColorProfile *profile = entry.color.colorSpace()->profile(); + const KoColorProfile *profile = entry.color().colorSpace()->profile(); if (!profile->fileName().isEmpty()) { profiles << profile; - profileMap[profile] = entry.color.colorSpace(); + profileMap[profile] = entry.color().colorSpace(); } QDomElement el = doc.createElement("ColorSetEntry"); - el.setAttribute("name", entry.name); - el.setAttribute("id", entry.id); - el.setAttribute("spot", entry.spotColor ? "true" : "false"); - el.setAttribute("bitdepth", entry.color.colorSpace()->colorDepthId().id()); - entry.color.toXML(doc, el); + el.setAttribute("name", entry.name()); + el.setAttribute("id", entry.id()); + el.setAttribute("spot", entry.spotColor() ? "true" : "false"); + el.setAttribute("bitdepth", entry.color().colorSpace()->colorDepthId().id()); + entry.color().toXML(doc, el); gl.appendChild(el); } } doc.appendChild(root); if (!store->open("colorset.xml")) { return false; } QByteArray ba = doc.toByteArray(); if (store->write(ba) != ba.size()) { return false; } if (!store->close()) { return false; } } QDomDocument doc; QDomElement profileElement = doc.createElement("Profiles"); Q_FOREACH(const KoColorProfile *profile, profiles) { QString fn = QFileInfo(profile->fileName()).fileName(); if (!store->open(fn)) { return false; } QByteArray profileRawData = profile->rawData(); if (!store->write(profileRawData)) { return false; } if (!store->close()) { return false; } QDomElement el = doc.createElement("Profile"); el.setAttribute("filename", fn); el.setAttribute("name", profile->name()); el.setAttribute("colorModelId", profileMap[profile]->colorModelId().id()); el.setAttribute("colorDepthId", profileMap[profile]->colorDepthId().id()); profileElement.appendChild(el); } doc.appendChild(profileElement); if (!store->open("profiles.xml")) { return false; } QByteArray ba = doc.toByteArray(); if (store->write(ba) != ba.size()) { return false; } if (!store->close()) { return false; } return store->finalize(); } bool KoColorSet::loadKpl() { QBuffer buf(&d->data); buf.open(QBuffer::ReadOnly); QScopedPointer store(KoStore::createStore(&buf, KoStore::Read, "application/x-krita-palette", KoStore::Zip)); if (!store || store->bad()) return false; if (store->hasFile("profiles.xml")) { if (!store->open("profiles.xml")) { return false; } QByteArray data; data.resize(store->size()); QByteArray ba = store->read(store->size()); store->close(); QDomDocument doc; doc.setContent(ba); QDomElement e = doc.documentElement(); QDomElement c = e.firstChildElement("Profiles"); while (!c.isNull()) { QString name = c.attribute("name"); QString filename = c.attribute("filename"); QString colorModelId = c.attribute("colorModelId"); QString colorDepthId = c.attribute("colorDepthId"); if (!KoColorSpaceRegistry::instance()->profileByName(name)) { store->open(filename); QByteArray data; data.resize(store->size()); data = store->read(store->size()); store->close(); const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(colorModelId, colorDepthId, data); if (profile && profile->valid()) { KoColorSpaceRegistry::instance()->addProfile(profile); } } c = c.nextSiblingElement(); } } { if (!store->open("colorset.xml")) { return false; } QByteArray data; data.resize(store->size()); QByteArray ba = store->read(store->size()); store->close(); QDomDocument doc; doc.setContent(ba); QDomElement e = doc.documentElement(); setName(e.attribute("name")); d->comment = e.attribute("comment"); d->columns = e.attribute("columns").toInt(); QDomElement c = e.firstChildElement("ColorSetEntry"); while (!c.isNull()) { QString colorDepthId = c.attribute("bitdepth", Integer8BitsColorDepthID.id()); KoColorSetEntry entry; - entry.color = KoColor::fromXML(c.firstChildElement(), colorDepthId); - entry.name = c.attribute("name"); - entry.id = c.attribute("id"); - entry.spotColor = c.attribute("spot", "false") == "true" ? true : false; + entry.setColor(KoColor::fromXML(c.firstChildElement(), colorDepthId)); + entry.setName(c.attribute("name")); + entry.setId(c.attribute("id")); + entry.setSpotColor(c.attribute("spot", "false") == "true" ? true : false); d->colors << entry; c = c.nextSiblingElement("ColorSetEntry"); } QDomElement g = e.firstChildElement("Group"); while (!g.isNull()) { QString groupName = g.attribute("name"); addGroup(groupName); QDomElement cg = g.firstChildElement("ColorSetEntry"); while (!cg.isNull()) { QString colorDepthId = cg.attribute("bitdepth", Integer8BitsColorDepthID.id()); KoColorSetEntry entry; - entry.color = KoColor::fromXML(cg.firstChildElement(), colorDepthId); - entry.name = cg.attribute("name"); - entry.id = cg.attribute("id"); - entry.spotColor = cg.attribute("spot", "false") == "true" ? true : false; + entry.setColor(KoColor::fromXML(cg.firstChildElement(), colorDepthId)); + entry.setName(cg.attribute("name")); + entry.setId(cg.attribute("id")); + entry.setSpotColor(cg.attribute("spot", "false") == "true" ? true : false); add(entry, groupName); cg = cg.nextSiblingElement("ColorSetEntry"); } g = g.nextSiblingElement("Group"); } } buf.close(); return true; } quint16 readShort(QIODevice *io) { quint16 val; quint64 read = io->read((char*)&val, 2); if (read != 2) return false; return qFromBigEndian(val); } bool KoColorSet::loadAco() { QFileInfo info(filename()); setName(info.baseName()); QBuffer buf(&d->data); buf.open(QBuffer::ReadOnly); quint16 version = readShort(&buf); quint16 numColors = readShort(&buf); KoColorSetEntry e; if (version == 1 && buf.size() > 4+numColors*10) { buf.seek(4+numColors*10); version = readShort(&buf); numColors = readShort(&buf); } const quint16 quint16_MAX = 65535; for (int i = 0; i < numColors && !buf.atEnd(); ++i) { quint16 colorSpace = readShort(&buf); quint16 ch1 = readShort(&buf); quint16 ch2 = readShort(&buf); quint16 ch3 = readShort(&buf); quint16 ch4 = readShort(&buf); bool skip = false; if (colorSpace == 0) { // RGB const KoColorProfile *srgb = KoColorSpaceRegistry::instance()->rgb8()->profile(); - e.color = KoColor(KoColorSpaceRegistry::instance()->rgb16(srgb)); - reinterpret_cast(e.color.data())[0] = ch3; - reinterpret_cast(e.color.data())[1] = ch2; - reinterpret_cast(e.color.data())[2] = ch1; - e.color.setOpacity(OPACITY_OPAQUE_U8); + KoColor c(KoColorSpaceRegistry::instance()->rgb16(srgb)); + reinterpret_cast(c.data())[0] = ch3; + reinterpret_cast(c.data())[1] = ch2; + reinterpret_cast(c.data())[2] = ch1; + c.setOpacity(OPACITY_OPAQUE_U8); + e.setColor(c); } else if (colorSpace == 1) { // HSB - e.color = KoColor(KoColorSpaceRegistry::instance()->rgb16()); - QColor c; - c.setHsvF(ch1 / 65536.0, ch2 / 65536.0, ch3 / 65536.0); - e.color.fromQColor(c); - e.color.setOpacity(OPACITY_OPAQUE_U8); + QColor qc; + qc.setHsvF(ch1 / 65536.0, ch2 / 65536.0, ch3 / 65536.0); + KoColor c(qc, KoColorSpaceRegistry::instance()->rgb16()); + c.setOpacity(OPACITY_OPAQUE_U8); + e.setColor(c); } else if (colorSpace == 2) { // CMYK - e.color = KoColor(KoColorSpaceRegistry::instance()->colorSpace(CMYKAColorModelID.id(), Integer16BitsColorDepthID.id(), QString())); - reinterpret_cast(e.color.data())[0] = quint16_MAX - ch1; - reinterpret_cast(e.color.data())[1] = quint16_MAX - ch2; - reinterpret_cast(e.color.data())[2] = quint16_MAX - ch3; - reinterpret_cast(e.color.data())[3] = quint16_MAX - ch4; - e.color.setOpacity(OPACITY_OPAQUE_U8); + KoColor c(KoColorSpaceRegistry::instance()->colorSpace(CMYKAColorModelID.id(), Integer16BitsColorDepthID.id(), QString())); + reinterpret_cast(c.data())[0] = quint16_MAX - ch1; + reinterpret_cast(c.data())[1] = quint16_MAX - ch2; + reinterpret_cast(c.data())[2] = quint16_MAX - ch3; + reinterpret_cast(c.data())[3] = quint16_MAX - ch4; + c.setOpacity(OPACITY_OPAQUE_U8); + e.setColor(c); } else if (colorSpace == 7) { // LAB - e.color = KoColor(KoColorSpaceRegistry::instance()->lab16()); - reinterpret_cast(e.color.data())[0] = ch3; - reinterpret_cast(e.color.data())[1] = ch2; - reinterpret_cast(e.color.data())[2] = ch1; - e.color.setOpacity(OPACITY_OPAQUE_U8); + KoColor c = KoColor(KoColorSpaceRegistry::instance()->lab16()); + reinterpret_cast(c.data())[0] = ch3; + reinterpret_cast(c.data())[1] = ch2; + reinterpret_cast(c.data())[2] = ch1; + c.setOpacity(OPACITY_OPAQUE_U8); + e.setColor(c); } else if (colorSpace == 8) { // GRAY - e.color = KoColor(KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), Integer16BitsColorDepthID.id(), QString())); - reinterpret_cast(e.color.data())[0] = ch1 * (quint16_MAX / 10000); - e.color.setOpacity(OPACITY_OPAQUE_U8); + KoColor c(KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), Integer16BitsColorDepthID.id(), QString())); + reinterpret_cast(c.data())[0] = ch1 * (quint16_MAX / 10000); + c.setOpacity(OPACITY_OPAQUE_U8); + e.setColor(c); } else { warnPigment << "Unsupported colorspace in palette" << filename() << "(" << colorSpace << ")"; skip = true; } if (version == 2) { quint16 v2 = readShort(&buf); //this isn't a version, it's a marker and needs to be skipped. Q_UNUSED(v2); quint16 size = readShort(&buf) -1; //then comes the length if (size>0) { QByteArray ba = buf.read(size*2); if (ba.size() == size*2) { QTextCodec *Utf16Codec = QTextCodec::codecForName("UTF-16BE"); - e.name = Utf16Codec->toUnicode(ba); + e.setName(Utf16Codec->toUnicode(ba)); } else { warnPigment << "Version 2 name block is the wrong size" << filename(); } } v2 = readShort(&buf); //end marker also needs to be skipped. Q_UNUSED(v2); } if (!skip) { add(e); } } return true; } bool KoColorSet::loadSbz() { QBuffer buf(&d->data); buf.open(QBuffer::ReadOnly); // &buf is a subclass of QIODevice QScopedPointer store(KoStore::createStore(&buf, KoStore::Read, "application/x-swatchbook", KoStore::Zip)); if (!store || store->bad()) return false; if (store->hasFile("swatchbook.xml")) { // Try opening... if (!store->open("swatchbook.xml")) { return false; } QByteArray data; data.resize(store->size()); QByteArray ba = store->read(store->size()); store->close(); dbgPigment << "XML palette: " << filename() << ", SwatchBooker format"; QDomDocument doc; int errorLine, errorColumn; QString errorMessage; bool status = doc.setContent(ba, &errorMessage, &errorLine, &errorColumn); if (!status) { warnPigment << "Illegal XML palette:" << filename(); warnPigment << "Error (line" << errorLine << ", column" << errorColumn << "):" << errorMessage; return false; } QDomElement e = doc.documentElement(); // SwatchBook // Start reading properties... QDomElement metadata = e.firstChildElement("metadata"); if (e.isNull()) { warnPigment << "Palette metadata not found"; return false; } QDomElement title = metadata.firstChildElement("dc:title"); QString colorName = title.text(); colorName = colorName.isEmpty() ? i18n("Untitled") : colorName; setName(colorName); dbgPigment << "Processed name of palette:" << name(); // End reading properties // Now read colors... QDomElement materials = e.firstChildElement("materials"); if (materials.isNull()) { warnPigment << "Materials (color definitions) not found"; return false; } // This one has lots of "color" elements QDomElement colorElement = materials.firstChildElement("color"); if (colorElement.isNull()) { warnPigment << "Color definitions not found (line" << materials.lineNumber() << ", column" << materials.columnNumber() << ")"; return false; } // Also read the swatch book... QDomElement book = e.firstChildElement("book"); if (book.isNull()) { warnPigment << "Palette book (swatch composition) not found (line" << e.lineNumber() << ", column" << e.columnNumber() << ")"; return false; } // Which has lots of "swatch"es (todo: support groups) QDomElement swatch = book.firstChildElement(); if (swatch.isNull()) { warnPigment << "Swatches/groups definition not found (line" << book.lineNumber() << ", column" << book.columnNumber() << ")"; return false; } // We'll store colors here, and as we process swatches // we'll add them to the palette QHash materialsBook; QHash fileColorSpaces; // Color processing for(; !colorElement.isNull(); colorElement = colorElement.nextSiblingElement("color")) { - KoColorSetEntry currentColor; + KoColorSetEntry currentEntry; // Set if color is spot - currentColor.spotColor = colorElement.attribute("usage") == "spot"; + currentEntry.setSpotColor(colorElement.attribute("usage") == "spot"); // inside contains id and name // one or more define the color QDomElement currentColorMetadata = colorElement.firstChildElement("metadata"); QDomNodeList currentColorValues = colorElement.elementsByTagName("values"); // Get color name QDomElement colorTitle = currentColorMetadata.firstChildElement("dc:title"); QDomElement colorId = currentColorMetadata.firstChildElement("dc:identifier"); // Is there an id? (we need that at the very least for identifying a color) if (colorId.text().isEmpty()) { warnPigment << "Unidentified color (line" << colorId.lineNumber()<< ", column" << colorId.columnNumber() << ")"; return false; } if (materialsBook.contains(colorId.text())) { warnPigment << "Duplicated color definition (line" << colorId.lineNumber()<< ", column" << colorId.columnNumber() << ")"; return false; } // Get a valid color name - currentColor.id = colorId.text(); - currentColor.name = colorTitle.text().isEmpty() ? colorId.text() : colorTitle.text(); + currentEntry.setId(colorId.text()); + currentEntry.setName(colorTitle.text().isEmpty() ? colorId.text() : colorTitle.text()); // Get a valid color definition if (currentColorValues.isEmpty()) { warnPigment << "Color definitions not found (line" << colorElement.lineNumber() << ", column" << colorElement.columnNumber() << ")"; return false; } bool firstDefinition = false; const KoColorProfile *srgb = KoColorSpaceRegistry::instance()->rgb8()->profile(); // Priority: Lab, otherwise the first definition found for(int j = 0; j < currentColorValues.size(); j++) { QDomNode colorValue = currentColorValues.at(j); QDomElement colorValueE = colorValue.toElement(); QString model = colorValueE.attribute("model", QString()); // sRGB,RGB,HSV,HSL,CMY,CMYK,nCLR: 0 -> 1 // YIQ: Y 0 -> 1 : IQ -0.5 -> 0.5 // Lab: L 0 -> 100 : ab -128 -> 127 // XYZ: 0 -> ~100 if (model == "Lab") { QStringList lab = colorValueE.text().split(" "); if (lab.length() != 3) { warnPigment << "Invalid Lab color definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } float l = lab.at(0).toFloat(&status); float a = lab.at(1).toFloat(&status); float b = lab.at(2).toFloat(&status); if (!status) { warnPigment << "Invalid float definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } - currentColor.color = KoColor(KoColorSpaceRegistry::instance()->colorSpace(LABAColorModelID.id(), Float32BitsColorDepthID.id(), QString())); - reinterpret_cast(currentColor.color.data())[0] = l; - reinterpret_cast(currentColor.color.data())[1] = a; - reinterpret_cast(currentColor.color.data())[2] = b; - currentColor.color.setOpacity(OPACITY_OPAQUE_F); + KoColor c(KoColorSpaceRegistry::instance()->colorSpace(LABAColorModelID.id(), Float32BitsColorDepthID.id(), QString())); + reinterpret_cast(c.data())[0] = l; + reinterpret_cast(c.data())[1] = a; + reinterpret_cast(c.data())[2] = b; + c.setOpacity(OPACITY_OPAQUE_F); firstDefinition = true; + currentEntry.setColor(c); break; // Immediately add this one } else if (model == "sRGB" && !firstDefinition) { QStringList rgb = colorValueE.text().split(" "); if (rgb.length() != 3) { warnPigment << "Invalid sRGB color definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } float r = rgb.at(0).toFloat(&status); float g = rgb.at(1).toFloat(&status); float b = rgb.at(2).toFloat(&status); if (!status) { warnPigment << "Invalid float definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } - currentColor.color = KoColor(KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), srgb)); - reinterpret_cast(currentColor.color.data())[0] = r; - reinterpret_cast(currentColor.color.data())[1] = g; - reinterpret_cast(currentColor.color.data())[2] = b; - currentColor.color.setOpacity(OPACITY_OPAQUE_F); + KoColor c(KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), srgb)); + reinterpret_cast(c.data())[0] = r; + reinterpret_cast(c.data())[1] = g; + reinterpret_cast(c.data())[2] = b; + c.setOpacity(OPACITY_OPAQUE_F); + currentEntry.setColor(c); firstDefinition = true; } else if (model == "XYZ" && !firstDefinition) { QStringList xyz = colorValueE.text().split(" "); if (xyz.length() != 3) { warnPigment << "Invalid XYZ color definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } float x = xyz.at(0).toFloat(&status); float y = xyz.at(1).toFloat(&status); float z = xyz.at(2).toFloat(&status); if (!status) { warnPigment << "Invalid float definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } - currentColor.color = KoColor(KoColorSpaceRegistry::instance()->colorSpace(XYZAColorModelID.id(), Float32BitsColorDepthID.id(), QString())); - reinterpret_cast(currentColor.color.data())[0] = x; - reinterpret_cast(currentColor.color.data())[1] = y; - reinterpret_cast(currentColor.color.data())[2] = z; - currentColor.color.setOpacity(OPACITY_OPAQUE_F); + KoColor c(KoColorSpaceRegistry::instance()->colorSpace(XYZAColorModelID.id(), Float32BitsColorDepthID.id(), QString())); + reinterpret_cast(c.data())[0] = x; + reinterpret_cast(c.data())[1] = y; + reinterpret_cast(c.data())[2] = z; + c.setOpacity(OPACITY_OPAQUE_F); + currentEntry.setColor(c); firstDefinition = true; } // The following color spaces admit an ICC profile (in SwatchBooker) else if (model == "CMYK" && !firstDefinition) { QStringList cmyk = colorValueE.text().split(" "); if (cmyk.length() != 4) { warnPigment << "Invalid CMYK color definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } float c = cmyk.at(0).toFloat(&status); float m = cmyk.at(1).toFloat(&status); float y = cmyk.at(2).toFloat(&status); float k = cmyk.at(3).toFloat(&status); if (!status) { warnPigment << "Invalid float definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } QString space = colorValueE.attribute("space"); const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->colorSpace(CMYKAColorModelID.id(), Float32BitsColorDepthID.id(), QString()); if (!space.isEmpty()) { // Try loading the profile and add it to the registry if (!fileColorSpaces.contains(space)) { store->enterDirectory("profiles"); store->open(space); QByteArray data; data.resize(store->size()); data = store->read(store->size()); store->close(); const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(CMYKAColorModelID.id(), Float32BitsColorDepthID.id(), data); if (profile && profile->valid()) { KoColorSpaceRegistry::instance()->addProfile(profile); colorSpace = KoColorSpaceRegistry::instance()->colorSpace(CMYKAColorModelID.id(), Float32BitsColorDepthID.id(), profile); fileColorSpaces.insert(space, colorSpace); } } else { colorSpace = fileColorSpaces.value(space); } } - currentColor.color = KoColor(colorSpace); - reinterpret_cast(currentColor.color.data())[0] = c; - reinterpret_cast(currentColor.color.data())[1] = m; - reinterpret_cast(currentColor.color.data())[2] = y; - reinterpret_cast(currentColor.color.data())[3] = k; - currentColor.color.setOpacity(OPACITY_OPAQUE_F); + KoColor color(colorSpace); + reinterpret_cast(color.data())[0] = c; + reinterpret_cast(color.data())[1] = m; + reinterpret_cast(color.data())[2] = y; + reinterpret_cast(color.data())[3] = k; + color.setOpacity(OPACITY_OPAQUE_F); + currentEntry.setColor(color); firstDefinition = true; } else if (model == "GRAY" && !firstDefinition) { QString gray = colorValueE.text(); float g = gray.toFloat(&status); if (!status) { warnPigment << "Invalid float definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } QString space = colorValueE.attribute("space"); const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), Float32BitsColorDepthID.id(), QString()); if (!space.isEmpty()) { // Try loading the profile and add it to the registry if (!fileColorSpaces.contains(space)) { store->enterDirectory("profiles"); store->open(space); QByteArray data; data.resize(store->size()); data = store->read(store->size()); store->close(); const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(CMYKAColorModelID.id(), Float32BitsColorDepthID.id(), data); if (profile && profile->valid()) { KoColorSpaceRegistry::instance()->addProfile(profile); colorSpace = KoColorSpaceRegistry::instance()->colorSpace(CMYKAColorModelID.id(), Float32BitsColorDepthID.id(), profile); fileColorSpaces.insert(space, colorSpace); } } else { colorSpace = fileColorSpaces.value(space); } } - currentColor.color = KoColor(colorSpace); - reinterpret_cast(currentColor.color.data())[0] = g; - currentColor.color.setOpacity(OPACITY_OPAQUE_F); + KoColor c(colorSpace); + reinterpret_cast(c.data())[0] = g; + c.setOpacity(OPACITY_OPAQUE_F); + currentEntry.setColor(c); firstDefinition = true; } else if (model == "RGB" && !firstDefinition) { QStringList rgb = colorValueE.text().split(" "); if (rgb.length() != 3) { warnPigment << "Invalid RGB color definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } float r = rgb.at(0).toFloat(&status); float g = rgb.at(1).toFloat(&status); float b = rgb.at(2).toFloat(&status); if (!status) { warnPigment << "Invalid float definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } QString space = colorValueE.attribute("space"); const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), srgb); if (!space.isEmpty()) { // Try loading the profile and add it to the registry if (!fileColorSpaces.contains(space)) { store->enterDirectory("profiles"); store->open(space); QByteArray data; data.resize(store->size()); data = store->read(store->size()); store->close(); const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), data); if (profile && profile->valid()) { KoColorSpaceRegistry::instance()->addProfile(profile); colorSpace = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), profile); fileColorSpaces.insert(space, colorSpace); } } else { colorSpace = fileColorSpaces.value(space); } } - currentColor.color = KoColor(colorSpace); - reinterpret_cast(currentColor.color.data())[0] = r; - reinterpret_cast(currentColor.color.data())[1] = g; - reinterpret_cast(currentColor.color.data())[2] = b; - currentColor.color.setOpacity(OPACITY_OPAQUE_F); + KoColor c(colorSpace); + reinterpret_cast(c.data())[0] = r; + reinterpret_cast(c.data())[1] = g; + reinterpret_cast(c.data())[2] = b; + c.setOpacity(OPACITY_OPAQUE_F); + currentEntry.setColor(c); firstDefinition = true; } else { warnPigment << "Color space not implemented:" << model << "(line" << colorValueE.lineNumber() << ", column "<< colorValueE.columnNumber() << ")"; } } if (firstDefinition) { - materialsBook.insert(currentColor.id, currentColor); + materialsBook.insert(currentEntry.id(), currentEntry); } else { warnPigment << "No supported color spaces for the current color (line" << colorElement.lineNumber() << ", column "<< colorElement.columnNumber() << ")"; return false; } } // End colors // Now decide which ones will go into the palette for(;!swatch.isNull(); swatch = swatch.nextSiblingElement()) { QString type = swatch.tagName(); if (type.isEmpty() || type.isNull()) { warnPigment << "Invalid swatch/group definition (no id) (line" << swatch.lineNumber() << ", column" << swatch.columnNumber() << ")"; return false; } else if (type == "swatch") { QString id = swatch.attribute("material"); if (id.isEmpty() || id.isNull()) { warnPigment << "Invalid swatch definition (no material id) (line" << swatch.lineNumber() << ", column" << swatch.columnNumber() << ")"; return false; } if (materialsBook.contains(id)) { add(materialsBook.value(id)); } else { warnPigment << "Invalid swatch definition (material not found) (line" << swatch.lineNumber() << ", column" << swatch.columnNumber() << ")"; return false; } } else if (type == "group") { QDomElement groupMetadata = swatch.firstChildElement("metadata"); if (groupMetadata.isNull()) { warnPigment << "Invalid group definition (missing metadata) (line" << groupMetadata.lineNumber() << ", column" << groupMetadata.columnNumber() << ")"; return false; } QDomElement groupTitle = metadata.firstChildElement("dc:title"); if (groupTitle.isNull()) { warnPigment << "Invalid group definition (missing title) (line" << groupTitle.lineNumber() << ", column" << groupTitle.columnNumber() << ")"; return false; } QString currentGroupName = groupTitle.text(); QDomElement groupSwatch = swatch.firstChildElement("swatch"); while(!groupSwatch.isNull()) { QString id = groupSwatch.attribute("material"); if (id.isEmpty() || id.isNull()) { warnPigment << "Invalid swatch definition (no material id) (line" << groupSwatch.lineNumber() << ", column" << groupSwatch.columnNumber() << ")"; return false; } if (materialsBook.contains(id)) { add(materialsBook.value(id), currentGroupName); } else { warnPigment << "Invalid swatch definition (material not found) (line" << groupSwatch.lineNumber() << ", column" << groupSwatch.columnNumber() << ")"; return false; } groupSwatch = groupSwatch.nextSiblingElement("swatch"); } } } // End palette } buf.close(); return true; } diff --git a/libs/pigment/resources/KoColorSet.h b/libs/pigment/resources/KoColorSet.h index 36e66eb29b..e8ba024596 100644 --- a/libs/pigment/resources/KoColorSet.h +++ b/libs/pigment/resources/KoColorSet.h @@ -1,232 +1,219 @@ /* This file is part of the KDE project Copyright (c) 2005 Boudewijn Rempt Copyright (c) 2016 L. E. Segovia This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef KOCOLORSET #define KOCOLORSET #include #include #include #include #include #include "KoColor.h" - -struct KoColorSetEntry { - KoColorSetEntry() {} - KoColorSetEntry(const KoColor &_color, const QString &_name) - : color(_color), name(_name) {} - - KoColor color; - QString name; - QString id; - bool spotColor {false}; - - bool operator==(const KoColorSetEntry& rhs) const { - return color == rhs.color && name == rhs.name; - } -}; +#include "KoColorSetEntry.h" /** * Open Gimp, Photoshop or RIFF palette files. This is a straight port * from the Gimp. */ class KRITAPIGMENT_EXPORT KoColorSet : public QObject, public KoResource { Q_OBJECT public: enum PaletteType { UNKNOWN = 0, GPL, // GIMP RIFF_PAL, // RIFF ACT, // Photoshop binary PSP_PAL, // PaintShop Pro ACO, // Photoshop Swatches XML, // XML palette (Scribus) KPL, // KoColor-based XML palette SBZ // SwatchBooker }; /** * Load a color set from a file. This can be a Gimp * palette, a RIFF palette, a Photoshop palette, + * a Krita palette, * a Scribus palette or a SwatchBooker palette. */ explicit KoColorSet(const QString &filename); /// Create an empty color set KoColorSet(); /// Explicit copy constructor (KoResource copy constructor is private) KoColorSet(const KoColorSet& rhs); ~KoColorSet() override; bool load() override; bool loadFromDevice(QIODevice *dev) override; bool save() override; bool saveToDevice(QIODevice* dev) const override; QString defaultFileExtension() const override; void setColumnCount(int columns); int columnCount(); /** * @brief comment * @return the comment. */ QString comment(); void setComment(QString comment); public: /** * @brief add Add a color to the palette. * @param groupName color to add the group to. If empty, it will be added to the unsorted. */ void add(const KoColorSetEntry &, QString groupName = QString()); /** * @brief insertBefore insert color before index into group. * @param index * @param groupName name of the group that the color goes into. * @return new index of index after the prepending. */ quint32 insertBefore(const KoColorSetEntry &, qint32 index, const QString &groupName = QString()); void removeAt(quint32 index, QString groupName = QString()); /** * @brief getColorGlobal * A function for getting a color based on a global index. Useful for itterating through all color entries. * @param globalIndex the global index over the whole palette. * @return the entry. */ KoColorSetEntry getColorGlobal(quint32 globalIndex); /** * @brief getColorGroup * A function for getting the color from a specific group. * @param groupName the name of the group, will give unosrted when not defined. * @param index the index within the group. * @return the entry */ KoColorSetEntry getColorGroup(quint32 index, QString groupName = QString()); QString findGroupByGlobalIndex(quint32 globalIndex, quint32 *index); QString findGroupByColorName(const QString &name, quint32 *index); QString findGroupByID(const QString &id,quint32 *index); /** * @brief getGroupNames * @return returns a list of group names, excluding the unsorted group. */ QStringList getGroupNames(); bool changeGroupName(QString oldGroupName, QString newGroupName); bool changeColorSetEntry(KoColorSetEntry entry, QString groupName, quint32 index); /** * @brief nColorsGroup * @param groupName string name of the group, when not specified, returns unsorted colors. * @return the amount of colors in this group. */ quint32 nColorsGroup(QString groupName = QString()); /** * @brief nColors * @return total colors in palette. */ quint32 nColors(); /** * @brief addGroup * Adds a new group. * @param groupName the name of the new group. When not specified, this will fail. * @return whether thegroup was made. */ bool addGroup(const QString &groupName); /** * @brief moveGroup * Move a group in the internal stringlist. * @param groupName the groupname to move. * @param groupNameInsertBefore the groupname to insert before. Empty means it will be added to the end. * @return */ bool moveGroup(const QString &groupName, const QString &groupNameInsertBefore = QString()); /** * @brief removeGroup * Remove a group from the KoColorSet * @param groupName the name of the group you want to remove. * @param keepColors Whether you wish to keep the colorsetentries. These will be added to the unsorted. * @return whether it could find the group to remove. */ bool removeGroup(const QString &groupName, bool keepColors = true); void clear(); /** * @brief getIndexClosestColor * function that matches the color to all colors in the colorset, and returns the index * of the closest match. * @param color the color you wish to compare. * @param useGivenColorSpace whether to use the color space of the color given * when the two colors' colorspaces don't match. Else it'll use the entry's colorspace. * @return returns the int of the closest match. */ quint32 getIndexClosestColor(KoColor color, bool useGivenColorSpace = true); /** * @brief closestColorName * convenience function to get the name of the closest match. * @param color * @param useGivenColorSpace * @return */ QString closestColorName(KoColor color, bool useGivenColorSpace = true); private: bool init(); bool saveGpl(QIODevice *dev) const; bool loadGpl(); bool loadAct(); bool loadRiff(); bool loadPsp(); bool loadAco(); bool loadXml(); bool loadSbz(); bool saveKpl(QIODevice *dev) const; bool loadKpl(); struct Private; const QScopedPointer d; }; #endif // KOCOLORSET diff --git a/plugins/color/colorspaceextensions/kis_hsv_adjustment.h b/libs/pigment/resources/KoColorSetEntry.cpp similarity index 53% copy from plugins/color/colorspaceextensions/kis_hsv_adjustment.h copy to libs/pigment/resources/KoColorSetEntry.cpp index b46d4b01f7..76d5628572 100644 --- a/plugins/color/colorspaceextensions/kis_hsv_adjustment.h +++ b/libs/pigment/resources/KoColorSetEntry.cpp @@ -1,37 +1,31 @@ /* - * Copyright (c) 2007 Cyrille Berger + * Copyright (c) 2018 Michael Zhou * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; version 2 - * of the License. + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ -#ifndef _KIS_HSV_ADJUSTMENT_H_ -#define _KIS_HSV_ADJUSTMENT_H_ +#include "KoColorSetEntry.h" -#include "KoColorTransformationFactory.h" +KoColorSetEntry::KoColorSetEntry() + : m_spotColor(false) +{ } -class KisHSVAdjustmentFactory : public KoColorTransformationFactory -{ -public: +KoColorSetEntry::KoColorSetEntry(const KoColor &color, const QString &name) + : m_color(color) + , m_name(name) + , m_spotColor(false) +{ } - KisHSVAdjustmentFactory(); - - QList< QPair< KoID, KoID > > supportedModels() const override; - - KoColorTransformation* createTransformation(const KoColorSpace* colorSpace, QHash parameters) const override; - -}; - -#endif diff --git a/libs/pigment/resources/KoColorSetEntry.h b/libs/pigment/resources/KoColorSetEntry.h new file mode 100644 index 0000000000..1801831680 --- /dev/null +++ b/libs/pigment/resources/KoColorSetEntry.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2018 Michael Zhou + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KOCOLORSETENTRY_H +#define KOCOLORSETENTRY_H + +#include "kritapigment_export.h" +#include +#include "KoColor.h" + +class KRITAPIGMENT_EXPORT KoColorSetEntry +{ +public: + KoColorSetEntry(); + KoColorSetEntry(const KoColor &color, const QString &name); + +public: + QString name() const { return m_name; } + void setName(const QString &name) { m_name = name; } + QString id() const { return m_id; } + void setId(const QString &id) { m_id = id; } + KoColor color() const { return m_color; } + void setColor(const KoColor &color) { m_color = color; } + bool spotColor() const { return m_spotColor; } + void setSpotColor(bool spotColor) { m_spotColor = spotColor; } + +public: + bool operator==(const KoColorSetEntry& rhs) const { + return m_color == rhs.m_color && m_name == rhs.m_name; + } + +private: + KoColor m_color; + QString m_name; + QString m_id; + bool m_spotColor; +}; + +#endif // KOCOLORSETENTRY_H diff --git a/libs/ui/CMakeLists.txt b/libs/ui/CMakeLists.txt index d660226ce5..38b6e288e8 100644 --- a/libs/ui/CMakeLists.txt +++ b/libs/ui/CMakeLists.txt @@ -1,591 +1,599 @@ include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/qtlockedfile ${EXIV2_INCLUDE_DIR} ) include_directories(SYSTEM ${EIGEN3_INCLUDE_DIR} ${OCIO_INCLUDE_DIR} ) add_subdirectory( tests ) if (APPLE) find_library(FOUNDATION_LIBRARY Foundation) find_library(APPKIT_LIBRARY AppKit) endif () set(kritaui_LIB_SRCS canvas/kis_canvas_widget_base.cpp canvas/kis_canvas2.cpp canvas/kis_canvas_updates_compressor.cpp canvas/kis_canvas_controller.cpp canvas/kis_paintop_transformation_connector.cpp canvas/kis_display_color_converter.cpp canvas/kis_display_filter.cpp canvas/kis_exposure_gamma_correction_interface.cpp canvas/kis_tool_proxy.cpp canvas/kis_canvas_decoration.cc canvas/kis_coordinates_converter.cpp canvas/kis_grid_manager.cpp canvas/kis_grid_decoration.cpp canvas/kis_grid_config.cpp canvas/kis_prescaled_projection.cpp canvas/kis_qpainter_canvas.cpp canvas/kis_projection_backend.cpp canvas/kis_update_info.cpp canvas/kis_image_patch.cpp canvas/kis_image_pyramid.cpp canvas/kis_infinity_manager.cpp canvas/kis_change_guides_command.cpp canvas/kis_guides_decoration.cpp canvas/kis_guides_manager.cpp canvas/kis_guides_config.cpp canvas/kis_snap_config.cpp canvas/kis_snap_line_strategy.cpp canvas/KisSnapPointStrategy.cpp dialogs/kis_about_application.cpp dialogs/kis_dlg_adj_layer_props.cc dialogs/kis_dlg_adjustment_layer.cc dialogs/kis_dlg_filter.cpp dialogs/kis_dlg_generator_layer.cpp dialogs/kis_dlg_file_layer.cpp dialogs/kis_dlg_filter.cpp dialogs/kis_dlg_stroke_selection_properties.cpp dialogs/kis_dlg_image_properties.cc dialogs/kis_dlg_layer_properties.cc dialogs/kis_dlg_preferences.cc dialogs/slider_and_spin_box_sync.cpp dialogs/kis_dlg_blacklist_cleanup.cpp dialogs/kis_dlg_layer_style.cpp dialogs/kis_dlg_png_import.cpp dialogs/kis_dlg_import_image_sequence.cpp dialogs/kis_delayed_save_dialog.cpp dialogs/kis_dlg_internal_color_selector.cpp dialogs/KisSessionManagerDialog.cpp dialogs/KisNewWindowLayoutDialog.cpp flake/kis_node_dummies_graph.cpp flake/kis_dummies_facade_base.cpp flake/kis_dummies_facade.cpp flake/kis_node_shapes_graph.cpp flake/kis_node_shape.cpp flake/kis_shape_controller.cpp flake/kis_shape_layer.cc flake/kis_shape_layer_canvas.cpp flake/kis_shape_selection.cpp flake/kis_shape_selection_canvas.cpp flake/kis_shape_selection_model.cpp flake/kis_take_all_shapes_command.cpp brushhud/kis_uniform_paintop_property_widget.cpp brushhud/kis_brush_hud.cpp brushhud/kis_round_hud_button.cpp brushhud/kis_dlg_brush_hud_config.cpp brushhud/kis_brush_hud_properties_list.cpp brushhud/kis_brush_hud_properties_config.cpp kis_aspect_ratio_locker.cpp kis_autogradient.cc kis_bookmarked_configurations_editor.cc kis_bookmarked_configurations_model.cc kis_bookmarked_filter_configurations_model.cc KisPaintopPropertiesBase.cpp kis_canvas_resource_provider.cpp kis_derived_resources.cpp kis_categories_mapper.cpp kis_categorized_list_model.cpp kis_categorized_item_delegate.cpp kis_clipboard.cc kis_config.cc kis_config_notifier.cpp kis_control_frame.cpp kis_composite_ops_model.cc kis_paint_ops_model.cpp kis_cursor.cc kis_cursor_cache.cpp kis_custom_pattern.cc kis_file_layer.cpp kis_change_file_layer_command.h kis_safe_document_loader.cpp kis_splash_screen.cpp kis_filter_manager.cc kis_filters_model.cc kis_histogram_view.cc KisImageBarrierLockerWithFeedback.cpp kis_image_manager.cc kis_image_view_converter.cpp kis_import_catcher.cc kis_layer_manager.cc kis_mask_manager.cc kis_mimedata.cpp kis_node_commands_adapter.cpp kis_node_manager.cpp kis_node_juggler_compressed.cpp kis_node_selection_adapter.cpp kis_node_insertion_adapter.cpp kis_node_model.cpp kis_node_filter_proxy_model.cpp kis_model_index_converter_base.cpp kis_model_index_converter.cpp kis_model_index_converter_show_all.cpp kis_painting_assistant.cc kis_painting_assistants_decoration.cpp KisDecorationsManager.cpp kis_paintop_box.cc kis_paintop_option.cpp kis_paintop_options_model.cpp kis_paintop_settings_widget.cpp kis_popup_palette.cpp kis_png_converter.cpp kis_preference_set_registry.cpp KisResourceServerProvider.cpp KisResourceBundleServerProvider.cpp KisSelectedShapesProxy.cpp kis_selection_decoration.cc kis_selection_manager.cc kis_statusbar.cc kis_zoom_manager.cc kis_favorite_resource_manager.cpp kis_workspace_resource.cpp kis_action.cpp kis_action_manager.cpp KisActionPlugin.cpp kis_canvas_controls_manager.cpp kis_tooltip_manager.cpp kis_multinode_property.cpp kis_stopgradient_editor.cpp kisexiv2/kis_exif_io.cpp kisexiv2/kis_exiv2.cpp kisexiv2/kis_iptc_io.cpp kisexiv2/kis_xmp_io.cpp opengl/kis_opengl.cpp opengl/kis_opengl_canvas2.cpp opengl/kis_opengl_canvas_debugger.cpp opengl/kis_opengl_image_textures.cpp opengl/kis_texture_tile.cpp opengl/kis_opengl_shader_loader.cpp opengl/kis_texture_tile_info_pool.cpp + opengl/KisOpenGLUpdateInfoBuilder.cpp kis_fps_decoration.cpp recorder/kis_node_query_path_editor.cc recorder/kis_recorded_action_creator.cc recorder/kis_recorded_action_creator_factory.cc recorder/kis_recorded_action_creator_factory_registry.cc recorder/kis_recorded_action_editor_factory.cc recorder/kis_recorded_action_editor_factory_registry.cc recorder/kis_recorded_filter_action_editor.cc recorder/kis_recorded_filter_action_creator.cpp recorder/kis_recorded_paint_action_editor.cc tool/kis_selection_tool_helper.cpp tool/kis_selection_tool_config_widget_helper.cpp tool/kis_rectangle_constraint_widget.cpp tool/kis_shape_tool_helper.cpp tool/kis_tool.cc tool/kis_delegated_tool_policies.cpp tool/kis_tool_freehand.cc tool/kis_speed_smoother.cpp tool/kis_painting_information_builder.cpp tool/kis_stabilized_events_sampler.cpp tool/kis_tool_freehand_helper.cpp tool/kis_tool_multihand_helper.cpp tool/kis_figure_painting_tool_helper.cpp tool/kis_recording_adapter.cpp tool/kis_tool_paint.cc tool/kis_tool_shape.cc tool/kis_tool_ellipse_base.cpp tool/kis_tool_rectangle_base.cpp tool/kis_tool_polyline_base.cpp tool/kis_tool_utils.cpp tool/kis_resources_snapshot.cpp tool/kis_smoothing_options.cpp tool/KisStabilizerDelayedPaintHelper.cpp tool/KisStrokeSpeedMonitor.cpp tool/strokes/freehand_stroke.cpp tool/strokes/KisStrokeEfficiencyMeasurer.cpp tool/strokes/kis_painter_based_stroke_strategy.cpp tool/strokes/kis_filter_stroke_strategy.cpp tool/strokes/kis_color_picker_stroke_strategy.cpp tool/strokes/KisFreehandStrokeInfo.cpp tool/strokes/KisMaskedFreehandStrokePainter.cpp tool/strokes/KisMaskingBrushRenderer.cpp tool/strokes/KisMaskingBrushCompositeOpFactory.cpp widgets/kis_cmb_composite.cc widgets/kis_cmb_contour.cpp widgets/kis_cmb_gradient.cpp widgets/kis_paintop_list_widget.cpp widgets/kis_cmb_idlist.cc widgets/kis_color_space_selector.cc widgets/kis_advanced_color_space_selector.cc widgets/kis_cie_tongue_widget.cpp widgets/kis_tone_curve_widget.cpp widgets/kis_curve_widget.cpp widgets/kis_custom_image_widget.cc widgets/kis_image_from_clipboard_widget.cpp widgets/kis_double_widget.cc widgets/kis_filter_selector_widget.cc widgets/kis_gradient_chooser.cc widgets/kis_iconwidget.cc widgets/kis_mask_widgets.cpp widgets/kis_meta_data_merge_strategy_chooser_widget.cc widgets/kis_multi_bool_filter_widget.cc widgets/kis_multi_double_filter_widget.cc widgets/kis_multi_integer_filter_widget.cc widgets/kis_multipliers_double_slider_spinbox.cpp widgets/kis_paintop_presets_popup.cpp widgets/kis_tool_options_popup.cpp widgets/kis_paintop_presets_chooser_popup.cpp widgets/kis_paintop_presets_save.cpp widgets/kis_paintop_preset_icon_library.cpp widgets/kis_pattern_chooser.cc widgets/kis_popup_button.cc widgets/kis_preset_chooser.cpp widgets/kis_progress_widget.cpp widgets/kis_selection_options.cc widgets/kis_scratch_pad.cpp widgets/kis_scratch_pad_event_filter.cpp widgets/kis_preset_selector_strip.cpp widgets/kis_slider_spin_box.cpp widgets/KisSelectionPropertySlider.cpp widgets/kis_size_group.cpp widgets/kis_size_group_p.cpp widgets/kis_wdg_generator.cpp widgets/kis_workspace_chooser.cpp widgets/kis_categorized_list_view.cpp widgets/kis_widget_chooser.cpp widgets/kis_tool_button.cpp widgets/kis_floating_message.cpp widgets/kis_lod_availability_widget.cpp widgets/kis_color_label_selector_widget.cpp widgets/kis_color_filter_combo.cpp widgets/kis_elided_label.cpp widgets/kis_stopgradient_slider_widget.cpp widgets/kis_spinbox_color_selector.cpp widgets/kis_screen_color_picker.cpp widgets/kis_preset_live_preview_view.cpp widgets/KoDualColorButton.cpp widgets/kis_color_input.cpp widgets/kis_color_button.cpp widgets/KisVisualColorSelector.cpp widgets/KisVisualColorSelectorShape.cpp widgets/KisVisualEllipticalSelectorShape.cpp widgets/KisVisualRectangleSelectorShape.cpp widgets/KisVisualTriangleSelectorShape.cpp widgets/KoStrokeConfigWidget.cpp widgets/KoFillConfigWidget.cpp utils/kis_document_aware_spin_box_unit_manager.cpp input/kis_input_manager.cpp input/kis_input_manager_p.cpp input/kis_extended_modifiers_mapper.cpp input/kis_abstract_input_action.cpp input/kis_tool_invocation_action.cpp input/kis_pan_action.cpp input/kis_alternate_invocation_action.cpp input/kis_rotate_canvas_action.cpp input/kis_zoom_action.cpp input/kis_change_frame_action.cpp input/kis_gamma_exposure_action.cpp input/kis_show_palette_action.cpp input/kis_change_primary_setting_action.cpp input/kis_abstract_shortcut.cpp input/kis_native_gesture_shortcut.cpp input/kis_single_action_shortcut.cpp input/kis_stroke_shortcut.cpp input/kis_shortcut_matcher.cpp input/kis_select_layer_action.cpp input/KisQtWidgetsTweaker.cpp input/KisInputActionGroup.cpp operations/kis_operation.cpp operations/kis_operation_configuration.cpp operations/kis_operation_registry.cpp operations/kis_operation_ui_factory.cpp operations/kis_operation_ui_widget.cpp operations/kis_filter_selection_operation.cpp actions/kis_selection_action_factories.cpp actions/KisPasteActionFactory.cpp input/kis_touch_shortcut.cpp kis_document_undo_store.cpp kis_transaction_based_command.cpp kis_gui_context_command.cpp kis_gui_context_command_p.cpp input/kis_tablet_debugger.cpp input/kis_input_profile_manager.cpp input/kis_input_profile.cpp input/kis_shortcut_configuration.cpp input/config/kis_input_configuration_page.cpp input/config/kis_edit_profiles_dialog.cpp input/config/kis_input_profile_model.cpp input/config/kis_input_configuration_page_item.cpp input/config/kis_action_shortcuts_model.cpp input/config/kis_input_type_delegate.cpp input/config/kis_input_mode_delegate.cpp input/config/kis_input_button.cpp input/config/kis_input_editor_delegate.cpp input/config/kis_mouse_input_editor.cpp input/config/kis_wheel_input_editor.cpp input/config/kis_key_input_editor.cpp processing/fill_processing_visitor.cpp kis_asl_layer_style_serializer.cpp kis_psd_layer_style_resource.cpp canvas/kis_mirror_axis.cpp kis_abstract_perspective_grid.cpp KisApplication.cpp KisAutoSaveRecoveryDialog.cpp KisDetailsPane.cpp KisDocument.cpp + KisCloneDocumentStroke.cpp KisNodeDelegate.cpp kis_node_view_visibility_delegate.cpp KisNodeToolTip.cpp KisNodeView.cpp kis_node_view_color_scheme.cpp KisImportExportFilter.cpp KisFilterEntry.cpp KisImportExportManager.cpp KisImportExportUtils.cpp kis_async_action_feedback.cpp KisMainWindow.cpp KisOpenPane.cpp KisPart.cpp KisPrintJob.cpp KisTemplate.cpp KisTemplateCreateDia.cpp KisTemplateGroup.cpp KisTemplates.cpp KisTemplatesPane.cpp KisTemplateTree.cpp KisUndoActionsUpdateManager.cpp KisView.cpp thememanager.cpp kis_mainwindow_observer.cpp KisViewManager.cpp kis_mirror_manager.cpp qtlockedfile/qtlockedfile.cpp qtsingleapplication/qtlocalpeer.cpp qtsingleapplication/qtsingleapplication.cpp KisResourceBundle.cpp KisResourceBundleManifest.cpp kis_md5_generator.cpp KisApplicationArguments.cpp KisNetworkAccessManager.cpp KisMultiFeedRSSModel.cpp KisRemoteFileFetcher.cpp KisPaletteModel.cpp kis_palette_delegate.cpp kis_palette_view.cpp KisColorsetChooser.cpp KisSaveGroupVisitor.cpp KisWindowLayoutResource.cpp KisWindowLayoutManager.cpp KisSessionResource.cpp KisReferenceImagesDecoration.cpp KisReferenceImage.cpp flake/KisReferenceImagesLayer.cpp flake/KisReferenceImagesLayer.h ) if(WIN32) set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} input/kis_tablet_event.cpp input/wintab/kis_tablet_support_win.cpp input/wintab/kis_screen_size_choice_dialog.cpp qtlockedfile/qtlockedfile_win.cpp input/wintab/kis_tablet_support_win8.cpp opengl/kis_opengl_win.cpp ) endif() set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} kis_animation_frame_cache.cpp kis_animation_cache_populator.cpp KisAsyncAnimationRendererBase.cpp KisAsyncAnimationCacheRenderer.cpp KisAsyncAnimationFramesSavingRenderer.cpp dialogs/KisAsyncAnimationRenderDialogBase.cpp dialogs/KisAsyncAnimationCacheRenderDialog.cpp dialogs/KisAsyncAnimationFramesSaveDialog.cpp canvas/kis_animation_player.cpp kis_animation_importer.cpp KisSyncedAudioPlayback.cpp + KisFrameDataSerializer.cpp + KisFrameCacheStore.cpp + KisFrameCacheSwapper.cpp + KisAbstractFrameCacheSwapper.cpp + KisInMemoryFrameCacheSwapper.cpp + input/wintab/drawpile_tablettester/tablettester.cpp input/wintab/drawpile_tablettester/tablettest.cpp ) if(UNIX) set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} input/kis_tablet_event.cpp input/wintab/kis_tablet_support.cpp qtlockedfile/qtlockedfile_unix.cpp ) if(NOT APPLE) set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} input/wintab/qxcbconnection_xi2.cpp input/wintab/qxcbconnection.cpp input/wintab/kis_xi2_event_filter.cpp ) endif() endif() if(APPLE) set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} osx.mm ) endif() ki18n_wrap_ui(kritaui_LIB_SRCS widgets/KoFillConfigWidget.ui widgets/KoStrokeConfigWidget.ui forms/wdgdlgpngimport.ui forms/wdgfullscreensettings.ui forms/wdgautogradient.ui forms/wdggeneralsettings.ui forms/wdgperformancesettings.ui forms/wdggenerators.ui forms/wdgbookmarkedconfigurationseditor.ui forms/wdgapplyprofile.ui forms/wdgcustompattern.ui forms/wdglayerproperties.ui forms/wdgcolorsettings.ui forms/wdgtabletsettings.ui forms/wdgcolorspaceselector.ui forms/wdgcolorspaceselectoradvanced.ui forms/wdgdisplaysettings.ui forms/kis_previewwidgetbase.ui forms/kis_matrix_widget.ui forms/wdgselectionoptions.ui forms/wdggeometryoptions.ui forms/wdgnewimage.ui forms/wdgimageproperties.ui forms/wdgmaskfromselection.ui forms/wdgmasksource.ui forms/wdgfilterdialog.ui forms/wdgmetadatamergestrategychooser.ui forms/wdgpaintoppresets.ui forms/wdgpaintopsettings.ui forms/wdgdlggeneratorlayer.ui forms/wdgdlgfilelayer.ui forms/wdgfilterselector.ui forms/wdgfilternodecreation.ui forms/wdgpaintactioneditor.ui forms/wdgmultipliersdoublesliderspinbox.ui forms/wdgnodequerypatheditor.ui forms/wdgpresetselectorstrip.ui forms/wdgsavebrushpreset.ui forms/wdgpreseticonlibrary.ui forms/wdgdlgblacklistcleanup.ui forms/wdgrectangleconstraints.ui forms/wdgimportimagesequence.ui forms/wdgstrokeselectionproperties.ui forms/KisDetailsPaneBase.ui forms/KisOpenPaneBase.ui forms/wdgstopgradienteditor.ui forms/wdgsessionmanager.ui forms/wdgnewwindowlayout.ui brushhud/kis_dlg_brush_hud_config.ui forms/wdgdlginternalcolorselector.ui dialogs/kis_delayed_save_dialog.ui input/config/kis_input_configuration_page.ui input/config/kis_edit_profiles_dialog.ui input/config/kis_input_configuration_page_item.ui input/config/kis_mouse_input_editor.ui input/config/kis_wheel_input_editor.ui input/config/kis_key_input_editor.ui layerstyles/wdgBevelAndEmboss.ui layerstyles/wdgblendingoptions.ui layerstyles/WdgColorOverlay.ui layerstyles/wdgContour.ui layerstyles/wdgdropshadow.ui layerstyles/WdgGradientOverlay.ui layerstyles/wdgInnerGlow.ui layerstyles/wdglayerstyles.ui layerstyles/WdgPatternOverlay.ui layerstyles/WdgSatin.ui layerstyles/WdgStroke.ui layerstyles/wdgstylesselector.ui layerstyles/wdgTexture.ui wdgsplash.ui input/wintab/kis_screen_size_choice_dialog.ui input/wintab/drawpile_tablettester/tablettest.ui ) QT5_WRAP_CPP(kritaui_HEADERS_MOC KisNodePropertyAction_p.h) add_library(kritaui SHARED ${kritaui_HEADERS_MOC} ${kritaui_LIB_SRCS} ) generate_export_header(kritaui BASE_NAME kritaui) target_link_libraries(kritaui KF5::CoreAddons KF5::Completion KF5::I18n KF5::ItemViews Qt5::Network kritaimpex kritacolor kritaimage kritalibbrush kritawidgets kritawidgetutils ${PNG_LIBRARIES} ${EXIV2_LIBRARIES} ) if (HAVE_QT_MULTIMEDIA) target_link_libraries(kritaui Qt5::Multimedia) endif() if (HAVE_KIO) target_link_libraries(kritaui KF5::KIOCore) endif() if (NOT WIN32 AND NOT APPLE) target_link_libraries(kritaui ${X11_X11_LIB} ${X11_Xinput_LIB} ${XCB_LIBRARIES}) endif() if(APPLE) target_link_libraries(kritaui ${FOUNDATION_LIBRARY}) target_link_libraries(kritaui ${APPKIT_LIBRARY}) endif () target_link_libraries(kritaui ${OPENEXR_LIBRARIES}) # Add VSync disable workaround if(NOT WIN32 AND NOT APPLE) target_link_libraries(kritaui ${CMAKE_DL_LIBS} Qt5::X11Extras) endif() if(X11_FOUND) target_link_libraries(kritaui Qt5::X11Extras ${X11_LIBRARIES}) endif() target_include_directories(kritaui PUBLIC $ $ $ $ $ $ $ ) set_target_properties(kritaui PROPERTIES VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION} ) install(TARGETS kritaui ${INSTALL_TARGETS_DEFAULT_ARGS}) if (APPLE) install(FILES osx.stylesheet DESTINATION ${DATA_INSTALL_DIR}/krita) endif () diff --git a/libs/ui/dialogs/kis_about_application.h b/libs/ui/KisAbstractFrameCacheSwapper.cpp similarity index 69% copy from libs/ui/dialogs/kis_about_application.h copy to libs/ui/KisAbstractFrameCacheSwapper.cpp index 23a94d2dcb..1540d8f32b 100644 --- a/libs/ui/dialogs/kis_about_application.h +++ b/libs/ui/KisAbstractFrameCacheSwapper.cpp @@ -1,35 +1,22 @@ /* - * Copyright (c) 2014 Boudewijn Rempt + * Copyright (c) 2018 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifndef KIS_ABOUT_APPLICATION_H -#define KIS_ABOUT_APPLICATION_H +#include "KisAbstractFrameCacheSwapper.h" -#include - -class KisAboutApplication : public QDialog +KisAbstractFrameCacheSwapper::~KisAbstractFrameCacheSwapper() { - Q_OBJECT -public: - explicit KisAboutApplication(QWidget *parent = 0); - -Q_SIGNALS: - -public Q_SLOTS: - -}; - -#endif // KIS_ABOUT_APPLICATION_H +} diff --git a/libs/ui/KisAbstractFrameCacheSwapper.h b/libs/ui/KisAbstractFrameCacheSwapper.h new file mode 100644 index 0000000000..c01db7953c --- /dev/null +++ b/libs/ui/KisAbstractFrameCacheSwapper.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2018 Dmitry Kazakov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#ifndef KISABSTRACTFRAMECACHESWAPPER_H +#define KISABSTRACTFRAMECACHESWAPPER_H + +#include "kritaui_export.h" + +class QRect; + +template +class KisSharedPtr; + +class KisOpenGLUpdateInfo; +typedef KisSharedPtr KisOpenGLUpdateInfoSP; + + +class KRITAUI_EXPORT KisAbstractFrameCacheSwapper +{ +public: + virtual ~KisAbstractFrameCacheSwapper(); + + // WARNING: after transferring \p info to saveFrame() the object becomes invalid + virtual void saveFrame(int frameId, KisOpenGLUpdateInfoSP info, const QRect &imageBounds) = 0; + virtual KisOpenGLUpdateInfoSP loadFrame(int frameId) = 0; + + virtual void moveFrame(int srcFrameId, int dstFrameId) = 0; + virtual void forgetFrame(int frameId) = 0; + + virtual bool hasFrame(int frameId) const = 0; + + virtual int frameLevelOfDetail(int frameId) const = 0; + virtual QRect frameDirtyRect(int frameId) const = 0; +}; + +#endif // KISABSTRACTFRAMECACHESWAPPER_H diff --git a/libs/ui/KisApplication.cpp b/libs/ui/KisApplication.cpp index c97a622d24..de1791799f 100644 --- a/libs/ui/KisApplication.cpp +++ b/libs/ui/KisApplication.cpp @@ -1,865 +1,844 @@ /* * Copyright (C) 1998, 1999 Torben Weis * Copyright (C) 2012 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisApplication.h" #include #ifdef Q_OS_WIN #include #include #endif #ifdef Q_OS_OSX #include "osx.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KoGlobal.h" #include "KoConfig.h" #include #include #include #include "thememanager.h" #include "KisPrintJob.h" #include "KisDocument.h" #include "KisMainWindow.h" #include "KisAutoSaveRecoveryDialog.h" #include "KisPart.h" #include #include "kis_md5_generator.h" #include "kis_splash_screen.h" #include "kis_config.h" #include "flake/kis_shape_selection.h" #include #include #include #include #include #include #include #include "kisexiv2/kis_exiv2.h" #include "KisApplicationArguments.h" #include #include "kis_action_registry.h" #include #include #include #include "kis_image_barrier_locker.h" #include "opengl/kis_opengl.h" #include "kis_spin_box_unit_manager.h" #include "kis_document_aware_spin_box_unit_manager.h" #include "KisViewManager.h" #include "kis_workspace_resource.h" +#include #include #include namespace { const QTime appStartTime(QTime::currentTime()); } -class KisApplicationPrivate +class KisApplication::Private { public: - KisApplicationPrivate() - : splashScreen(0) - {} + Private() {} QPointer splashScreen; + KisAutoSaveRecoveryDialog *autosaveDialog {0}; + QPointer mainWindow; // The first mainwindow we create on startup + bool batchRun {false}; + }; class KisApplication::ResetStarting { public: ResetStarting(KisSplashScreen *splash, int fileCount) : m_splash(splash) , m_fileCount(fileCount) { } ~ResetStarting() { if (m_splash) { KConfigGroup cfg( KSharedConfig::openConfig(), "SplashScreen"); bool hideSplash = cfg.readEntry("HideSplashAfterStartup", false); if (m_fileCount > 0 || hideSplash) { m_splash->hide(); } else { m_splash->setWindowFlags(Qt::Dialog); QRect r(QPoint(), m_splash->size()); m_splash->move(QApplication::desktop()->availableGeometry().center() - r.center()); m_splash->setWindowTitle(qAppName()); m_splash->setParent(0); Q_FOREACH (QObject *o, m_splash->children()) { QWidget *w = qobject_cast(o); if (w && w->isHidden()) { w->setVisible(true); } } m_splash->show(); m_splash->activateWindow(); } } } QPointer m_splash; int m_fileCount; }; KisApplication::KisApplication(const QString &key, int &argc, char **argv) : QtSingleApplication(key, argc, argv) - , d(new KisApplicationPrivate) - , m_autosaveDialog(0) - , m_mainWindow(0) - , m_batchRun(false) + , d(new Private) { #ifdef Q_OS_OSX setMouseCoalescingEnabled(false); #endif QCoreApplication::addLibraryPath(QCoreApplication::applicationDirPath()); setApplicationDisplayName("Krita"); setApplicationName("krita"); // Note: Qt docs suggest we set this, but if we do, we get resource paths of the form of krita/krita, which is weird. // setOrganizationName("krita"); setOrganizationDomain("krita.org"); QString version = KritaVersionWrapper::versionString(true); setApplicationVersion(version); setWindowIcon(KisIconUtils::loadIcon("calligrakrita")); if (qgetenv("KRITA_NO_STYLE_OVERRIDE").isEmpty()) { QStringList styles = QStringList() << "breeze" << "fusion" << "plastique"; if (!styles.contains(style()->objectName().toLower())) { Q_FOREACH (const QString & style, styles) { if (!setStyle(style)) { qDebug() << "No" << style << "available."; } else { qDebug() << "Set style" << style; break; } } } } else { qDebug() << "Style override disabled, using" << style()->objectName(); } KisOpenGL::initialize(); qDebug() << "krita has opengl" << KisOpenGL::hasOpenGL(); } #if defined(Q_OS_WIN) && defined(ENV32BIT) typedef BOOL (WINAPI *LPFN_ISWOW64PROCESS) (HANDLE, PBOOL); LPFN_ISWOW64PROCESS fnIsWow64Process; BOOL isWow64() { BOOL bIsWow64 = FALSE; //IsWow64Process is not available on all supported versions of Windows. //Use GetModuleHandle to get a handle to the DLL that contains the function //and GetProcAddress to get a pointer to the function if available. fnIsWow64Process = (LPFN_ISWOW64PROCESS) GetProcAddress( GetModuleHandle(TEXT("kernel32")),"IsWow64Process"); if(0 != fnIsWow64Process) { if (!fnIsWow64Process(GetCurrentProcess(),&bIsWow64)) { //handle error } } return bIsWow64; } #endif void KisApplication::initializeGlobals(const KisApplicationArguments &args) { int dpiX = args.dpiX(); int dpiY = args.dpiY(); if (dpiX > 0 && dpiY > 0) { KoDpi::setDPI(dpiX, dpiY); } } void KisApplication::addResourceTypes() { -// qDebug() << "addResourceTypes();"; + // qDebug() << "addResourceTypes();"; // All Krita's resource types KoResourcePaths::addResourceType("kis_pics", "data", "/pics/"); KoResourcePaths::addResourceType("kis_images", "data", "/images/"); KoResourcePaths::addResourceType("icc_profiles", "data", "/profiles/"); KoResourcePaths::addResourceType("metadata_schema", "data", "/metadata/schemas/"); KoResourcePaths::addResourceType("kis_brushes", "data", "/brushes/"); KoResourcePaths::addResourceType("kis_taskset", "data", "/taskset/"); KoResourcePaths::addResourceType("kis_taskset", "data", "/taskset/"); KoResourcePaths::addResourceType("gmic_definitions", "data", "/gmic/"); KoResourcePaths::addResourceType("kis_resourcebundles", "data", "/bundles/"); KoResourcePaths::addResourceType("kis_defaultpresets", "data", "/defaultpresets/"); KoResourcePaths::addResourceType("kis_paintoppresets", "data", "/paintoppresets/"); KoResourcePaths::addResourceType("kis_workspaces", "data", "/workspaces/"); KoResourcePaths::addResourceType("kis_windowlayouts", "data", "/windowlayouts/"); KoResourcePaths::addResourceType("kis_sessions", "data", "/sessions/"); KoResourcePaths::addResourceType("psd_layer_style_collections", "data", "/asl"); KoResourcePaths::addResourceType("ko_patterns", "data", "/patterns/", true); KoResourcePaths::addResourceType("ko_gradients", "data", "/gradients/"); KoResourcePaths::addResourceType("ko_gradients", "data", "/gradients/", true); KoResourcePaths::addResourceType("ko_palettes", "data", "/palettes/", true); KoResourcePaths::addResourceType("kis_shortcuts", "data", "/shortcuts/"); KoResourcePaths::addResourceType("kis_actions", "data", "/actions"); KoResourcePaths::addResourceType("icc_profiles", "data", "/color/icc"); KoResourcePaths::addResourceType("ko_effects", "data", "/effects/"); KoResourcePaths::addResourceType("tags", "data", "/tags/"); KoResourcePaths::addResourceType("templates", "data", "/templates"); KoResourcePaths::addResourceType("pythonscripts", "data", "/pykrita"); KoResourcePaths::addResourceType("symbols", "data", "/symbols"); KoResourcePaths::addResourceType("preset_icons", "data", "/preset_icons"); // // Extra directories to look for create resources. (Does anyone actually use that anymore?) // KoResourcePaths::addResourceDir("ko_gradients", "/usr/share/create/gradients/gimp"); // KoResourcePaths::addResourceDir("ko_gradients", QDir::homePath() + QString("/.create/gradients/gimp")); // KoResourcePaths::addResourceDir("ko_patterns", "/usr/share/create/patterns/gimp"); // KoResourcePaths::addResourceDir("ko_patterns", QDir::homePath() + QString("/.create/patterns/gimp")); // KoResourcePaths::addResourceDir("kis_brushes", "/usr/share/create/brushes/gimp"); // KoResourcePaths::addResourceDir("kis_brushes", QDir::homePath() + QString("/.create/brushes/gimp")); // KoResourcePaths::addResourceDir("ko_palettes", "/usr/share/create/swatches"); // KoResourcePaths::addResourceDir("ko_palettes", QDir::homePath() + QString("/.create/swatches")); // Make directories for all resources we can save, and tags QDir d; d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/tags/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/asl/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/bundles/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/brushes/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/gradients/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/paintoppresets/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/palettes/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/patterns/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/taskset/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/workspaces/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/input/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/pykrita/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/symbols/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/color-schemes/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/preset_icons/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/preset_icons/tool_icons/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/preset_icons/emblem_icons/"); // Indicate that it is now safe for users of KoResourcePaths to load resources KoResourcePaths::setReady(); } void KisApplication::loadResources() { -// qDebug() << "loadResources();"; + // qDebug() << "loadResources();"; setSplashScreenLoadingText(i18n("Loading Resources...")); processEvents(); KoResourceServerProvider::instance(); setSplashScreenLoadingText(i18n("Loading Brush Presets...")); processEvents(); KisResourceServerProvider::instance(); setSplashScreenLoadingText(i18n("Loading Brushes...")); processEvents(); KisBrushServer::instance()->brushServer(); setSplashScreenLoadingText(i18n("Loading Bundles...")); processEvents(); KisResourceBundleServerProvider::instance(); } void KisApplication::loadResourceTags() { -// qDebug() << "loadResourceTags()"; + // qDebug() << "loadResourceTags()"; KoResourceServerProvider::instance()->patternServer()->loadTags(); KoResourceServerProvider::instance()->gradientServer()->loadTags(); KoResourceServerProvider::instance()->paletteServer()->loadTags(); KoResourceServerProvider::instance()->svgSymbolCollectionServer()->loadTags(); KisBrushServer::instance()->brushServer()->loadTags(); KisResourceServerProvider::instance()->workspaceServer()->loadTags(); KisResourceServerProvider::instance()->layerStyleCollectionServer()->loadTags(); KisResourceBundleServerProvider::instance()->resourceBundleServer()->loadTags(); KisResourceServerProvider::instance()->paintOpPresetServer()->loadTags(); KisResourceServerProvider::instance()->paintOpPresetServer()->clearOldSystemTags(); } void KisApplication::loadPlugins() { -// qDebug() << "loadPlugins();"; + // qDebug() << "loadPlugins();"; KoShapeRegistry* r = KoShapeRegistry::instance(); r->add(new KisShapeSelectionFactory()); KisActionRegistry::instance(); KisFilterRegistry::instance(); KisGeneratorRegistry::instance(); KisPaintOpRegistry::instance(); KoColorSpaceRegistry::instance(); } void KisApplication::loadGuiPlugins() { -// qDebug() << "loadGuiPlugins();"; + // qDebug() << "loadGuiPlugins();"; // Load the krita-specific tools setSplashScreenLoadingText(i18n("Loading Plugins for Krita/Tool...")); processEvents(); -// qDebug() << "loading tools"; + // qDebug() << "loading tools"; KoPluginLoader::instance()->load(QString::fromLatin1("Krita/Tool"), QString::fromLatin1("[X-Krita-Version] == 28")); // Load dockers setSplashScreenLoadingText(i18n("Loading Plugins for Krita/Dock...")); processEvents(); -// qDebug() << "loading dockers"; + // qDebug() << "loading dockers"; KoPluginLoader::instance()->load(QString::fromLatin1("Krita/Dock"), QString::fromLatin1("[X-Krita-Version] == 28")); // XXX_EXIV: make the exiv io backends real plugins setSplashScreenLoadingText(i18n("Loading Plugins Exiv/IO...")); processEvents(); -// qDebug() << "loading exiv2"; + // qDebug() << "loading exiv2"; KisExiv2::initialize(); } bool KisApplication::start(const KisApplicationArguments &args) { KisConfig cfg; #if defined(Q_OS_WIN) #ifdef ENV32BIT if (isWow64() && !cfg.readEntry("WarnedAbout32Bits", false)) { QMessageBox::information(0, i18nc("@title:window", "Krita: Warning"), i18n("You are running a 32 bits build on a 64 bits Windows.\n" "This is not recommended.\n" "Please download and install the x64 build instead.")); cfg.writeEntry("WarnedAbout32Bits", true); } #endif #endif QString opengl = cfg.canvasState(); if (opengl == "OPENGL_NOT_TRIED" ) { cfg.setCanvasState("TRY_OPENGL"); } else if (opengl != "OPENGL_SUCCESS") { cfg.setCanvasState("OPENGL_FAILED"); } setSplashScreenLoadingText(i18n("Initializing Globals")); processEvents(); initializeGlobals(args); const bool doNewImage = args.doNewImage(); const bool doTemplate = args.doTemplate(); - const bool print = args.print(); const bool exportAs = args.exportAs(); - const bool exportAsPdf = args.exportAsPdf(); const QString exportFileName = args.exportFileName(); - m_batchRun = (print || exportAs || exportAsPdf || !exportFileName.isEmpty()); - // print & exportAsPdf do user interaction ATM + d->batchRun = (exportAs || !exportFileName.isEmpty()); const bool needsMainWindow = !exportAs; // only show the mainWindow when no command-line mode option is passed - // TODO: fix print & exportAsPdf to work without mainwindow shown bool showmainWindow = !exportAs; // would be !batchRun; - const bool showSplashScreen = !m_batchRun && qEnvironmentVariableIsEmpty("NOSPLASH"); + const bool showSplashScreen = !d->batchRun && qEnvironmentVariableIsEmpty("NOSPLASH"); if (showSplashScreen && d->splashScreen) { d->splashScreen->show(); d->splashScreen->repaint(); processEvents(); } KoHashGeneratorProvider::instance()->setGenerator("MD5", new KisMD5Generator()); // Initialize all Krita directories etc. KoGlobal::initialize(); KConfigGroup group(KSharedConfig::openConfig(), "theme"); Digikam::ThemeManager themeManager; themeManager.setCurrentTheme(group.readEntry("Theme", "Krita dark")); ResetStarting resetStarting(d->splashScreen, args.filenames().count()); // remove the splash when done Q_UNUSED(resetStarting); // Make sure we can save resources and tags setSplashScreenLoadingText(i18n("Adding resource types")); processEvents(); addResourceTypes(); // Load the plugins loadPlugins(); // Load all resources loadResources(); // Load all the tags loadResourceTags(); // Load the gui plugins loadGuiPlugins(); KisPart *kisPart = KisPart::instance(); if (needsMainWindow) { // show a mainWindow asap, if we want that setSplashScreenLoadingText(i18n("Loading Main Window...")); processEvents(); bool sessionNeeded = true; auto sessionMode = cfg.sessionOnStartup(); if (!args.session().isEmpty()) { sessionNeeded = !kisPart->restoreSession(args.session()); } else if (sessionMode == KisConfig::SOS_ShowSessionManager) { showmainWindow = false; sessionNeeded = false; kisPart->showSessionManager(); } else if (sessionMode == KisConfig::SOS_PreviousSession) { KConfigGroup sessionCfg = KSharedConfig::openConfig()->group("session"); const QString &sessionName = sessionCfg.readEntry("previousSession"); sessionNeeded = !kisPart->restoreSession(sessionName); } if (sessionNeeded) { kisPart->startBlankSession(); } if (!args.windowLayout().isEmpty()) { KoResourceServer * rserver = KisResourceServerProvider::instance()->windowLayoutServer(); KisWindowLayoutResource* windowLayout = rserver->resourceByName(args.windowLayout()); if (windowLayout) { windowLayout->applyLayout(); } } if (showmainWindow) { - m_mainWindow = kisPart->currentMainwindow(); + d->mainWindow = kisPart->currentMainwindow(); if (!args.workspace().isEmpty()) { KoResourceServer * rserver = KisResourceServerProvider::instance()->workspaceServer(); KisWorkspaceResource* workspace = rserver->resourceByName(args.workspace()); if (workspace) { - m_mainWindow->restoreWorkspace(workspace); + d->mainWindow->restoreWorkspace(workspace); } } if (args.canvasOnly()) { - m_mainWindow->viewManager()->switchCanvasOnly(true); + d->mainWindow->viewManager()->switchCanvasOnly(true); } if (args.fullScreen()) { - m_mainWindow->showFullScreen(); + d->mainWindow->showFullScreen(); } } else { - m_mainWindow = kisPart->createMainWindow(); + d->mainWindow = kisPart->createMainWindow(); } } short int numberOfOpenDocuments = 0; // number of documents open - // Check for autosave files that can be restored, if we're not running a batchrun (test, print, export to pdf) - if (!m_batchRun) { + // Check for autosave files that can be restored, if we're not running a batchrun (test) + if (!d->batchRun) { checkAutosaveFiles(); } setSplashScreenLoadingText(QString()); // done loading, so clear out label processEvents(); //configure the unit manager KisSpinBoxUnitManagerFactory::setDefaultUnitManagerBuilder(new KisDocumentAwareSpinBoxUnitManagerBuilder()); connect(this, &KisApplication::aboutToQuit, &KisSpinBoxUnitManagerFactory::clearUnitManagerBuilder); //ensure the builder is destroyed when the application leave. //the new syntax slot syntax allow to connect to a non q_object static method. // Create a new image, if needed if (doNewImage) { KisDocument *doc = args.image(); if (doc) { kisPart->addDocument(doc); - m_mainWindow->addViewAndNotifyLoadingCompleted(doc); + d->mainWindow->addViewAndNotifyLoadingCompleted(doc); } } // Get the command line arguments which we have to parse int argsCount = args.filenames().count(); if (argsCount > 0) { // Loop through arguments - short int nPrinted = 0; for (int argNumber = 0; argNumber < argsCount; argNumber++) { QString fileName = args.filenames().at(argNumber); // are we just trying to open a template? if (doTemplate) { // called in mix with batch options? ignore and silently skip - if (m_batchRun) { + if (d->batchRun) { continue; } - if (createNewDocFromTemplate(fileName, m_mainWindow)) { + if (createNewDocFromTemplate(fileName, d->mainWindow)) { ++numberOfOpenDocuments; } // now try to load } else { if (exportAs) { QString outputMimetype = KisMimeDatabase::mimeTypeForFile(exportFileName, false); if (outputMimetype == "application/octetstream") { dbgKrita << i18n("Mimetype not found, try using the -mimetype option") << endl; return 1; } KisDocument *doc = kisPart->createDocument(); - doc->setFileBatchMode(m_batchRun); + doc->setFileBatchMode(d->batchRun); doc->openUrl(QUrl::fromLocalFile(fileName)); qApp->processEvents(); // For vector layers to be updated doc->setFileBatchMode(true); if (!doc->exportDocumentSync(QUrl::fromLocalFile(exportFileName), outputMimetype.toLatin1())) { dbgKrita << "Could not export " << fileName << "to" << exportFileName << ":" << doc->errorMessage(); } - nPrinted++; QTimer::singleShot(0, this, SLOT(quit())); } - else if (m_mainWindow) { - KisMainWindow::OpenFlags flags = m_batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; - if (m_mainWindow->openDocument(QUrl::fromLocalFile(fileName), flags)) { - if (print) { - m_mainWindow->slotFilePrint(); - nPrinted++; - // TODO: trigger closing of app once printing is done - } - else if (exportAsPdf) { - KisPrintJob *job = m_mainWindow->exportToPdf(exportFileName); - if (job) - connect (job, SIGNAL(destroyed(QObject*)), m_mainWindow, - SLOT(slotFileQuit()), Qt::QueuedConnection); - nPrinted++; - } else { + else if (d->mainWindow) { + if (fileName.endsWith(".bundle")) { + d->mainWindow->installBundle(fileName); + } + else { + KisMainWindow::OpenFlags flags = d->batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; + + if (d->mainWindow->openDocument(QUrl::fromLocalFile(fileName), flags)) { // Normal case, success numberOfOpenDocuments++; } - } else { - // .... if failed - // delete doc; done by openDocument } } } } - - if (m_batchRun) { - return nPrinted > 0; - } } // fixes BUG:369308 - Krita crashing on splash screen when loading. // trying to open a file before Krita has loaded can cause it to hang and crash if (d->splashScreen) { d->splashScreen->displayLinks(true); d->splashScreen->displayRecentFiles(true); } // not calling this before since the program will quit there. return true; } KisApplication::~KisApplication() { - delete d; } void KisApplication::setSplashScreen(QWidget *splashScreen) { d->splashScreen = qobject_cast(splashScreen); } void KisApplication::setSplashScreenLoadingText(QString textToLoad) { if (d->splashScreen) { - //d->splashScreen->loadingLabel->setText(textToLoad); + //d->splashScreen->loadingLabel->setText(textToLoad); d->splashScreen->setLoadingText(textToLoad); d->splashScreen->repaint(); } } void KisApplication::hideSplashScreen() { if (d->splashScreen) { // hide the splashscreen to see the dialog d->splashScreen->hide(); } } bool KisApplication::notify(QObject *receiver, QEvent *event) { try { return QApplication::notify(receiver, event); } catch (std::exception &e) { qWarning("Error %s sending event %i to object %s", e.what(), event->type(), qPrintable(receiver->objectName())); } catch (...) { qWarning("Error sending event %i to object %s", event->type(), qPrintable(receiver->objectName())); } return false; } void KisApplication::remoteArguments(QByteArray message, QObject *socket) { Q_UNUSED(socket); // check if we have any mainwindow KisMainWindow *mw = qobject_cast(qApp->activeWindow()); if (!mw) { mw = KisPart::instance()->mainWindows().first(); } if (!mw) { return; } KisApplicationArguments args = KisApplicationArguments::deserialize(message); const bool doTemplate = args.doTemplate(); const int argsCount = args.filenames().count(); if (argsCount > 0) { // Loop through arguments for (int argNumber = 0; argNumber < argsCount; ++argNumber) { QString filename = args.filenames().at(argNumber); // are we just trying to open a template? if (doTemplate) { createNewDocFromTemplate(filename, mw); } else if (QFile(filename).exists()) { - KisMainWindow::OpenFlags flags = m_batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; + KisMainWindow::OpenFlags flags = d->batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; mw->openDocument(QUrl::fromLocalFile(filename), flags); } } } } void KisApplication::fileOpenRequested(const QString &url) { KisMainWindow *mainWindow = KisPart::instance()->mainWindows().first(); if (mainWindow) { - KisMainWindow::OpenFlags flags = m_batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; + KisMainWindow::OpenFlags flags = d->batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; mainWindow->openDocument(QUrl::fromLocalFile(url), flags); } } void KisApplication::checkAutosaveFiles() { - if (m_batchRun) return; + if (d->batchRun) return; // Check for autosave files from a previous run. There can be several, and // we want to offer a restore for every one. Including a nice thumbnail! QStringList filters; filters << QString(".krita-*-*-autosave.kra"); #ifdef Q_OS_WIN QDir dir = QDir::temp(); #else QDir dir = QDir::home(); #endif // all autosave files for our application QStringList autosaveFiles = dir.entryList(filters, QDir::Files | QDir::Hidden); // Allow the user to make their selection if (autosaveFiles.size() > 0) { if (d->splashScreen) { // hide the splashscreen to see the dialog d->splashScreen->hide(); } - m_autosaveDialog = new KisAutoSaveRecoveryDialog(autosaveFiles, activeWindow()); - QDialog::DialogCode result = (QDialog::DialogCode) m_autosaveDialog->exec(); + d->autosaveDialog = new KisAutoSaveRecoveryDialog(autosaveFiles, activeWindow()); + QDialog::DialogCode result = (QDialog::DialogCode) d->autosaveDialog->exec(); if (result == QDialog::Accepted) { - QStringList filesToRecover = m_autosaveDialog->recoverableFiles(); + QStringList filesToRecover = d->autosaveDialog->recoverableFiles(); Q_FOREACH (const QString &autosaveFile, autosaveFiles) { if (!filesToRecover.contains(autosaveFile)) { QFile::remove(dir.absolutePath() + "/" + autosaveFile); } } autosaveFiles = filesToRecover; } else { autosaveFiles.clear(); } if (autosaveFiles.size() > 0) { QList autosaveUrls; Q_FOREACH (const QString &autoSaveFile, autosaveFiles) { const QUrl url = QUrl::fromLocalFile(dir.absolutePath() + QLatin1Char('/') + autoSaveFile); autosaveUrls << url; } - if (m_mainWindow) { + if (d->mainWindow) { Q_FOREACH (const QUrl &url, autosaveUrls) { - KisMainWindow::OpenFlags flags = m_batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; - m_mainWindow->openDocument(url, flags | KisMainWindow::RecoveryFile); + KisMainWindow::OpenFlags flags = d->batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; + d->mainWindow->openDocument(url, flags | KisMainWindow::RecoveryFile); } } } // cleanup - delete m_autosaveDialog; - m_autosaveDialog = nullptr; + delete d->autosaveDialog; + d->autosaveDialog = nullptr; } } bool KisApplication::createNewDocFromTemplate(const QString &fileName, KisMainWindow *mainWindow) { QString templatePath; const QUrl templateUrl = QUrl::fromLocalFile(fileName); if (QFile::exists(fileName)) { templatePath = templateUrl.toLocalFile(); dbgUI << "using full path..."; } else { QString desktopName(fileName); const QString templatesResourcePath = QStringLiteral("templates/"); QStringList paths = KoResourcePaths::findAllResources("data", templatesResourcePath + "*/" + desktopName); if (paths.isEmpty()) { paths = KoResourcePaths::findAllResources("data", templatesResourcePath + desktopName); } if (paths.isEmpty()) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("No template found for: %1", desktopName)); } else if (paths.count() > 1) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Too many templates found for: %1", desktopName)); } else { templatePath = paths.at(0); } } if (!templatePath.isEmpty()) { QUrl templateBase; templateBase.setPath(templatePath); KDesktopFile templateInfo(templatePath); QString templateName = templateInfo.readUrl(); QUrl templateURL; templateURL.setPath(templateBase.adjusted(QUrl::RemoveFilename|QUrl::StripTrailingSlash).path() + '/' + templateName); - KisMainWindow::OpenFlags batchFlags = m_batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; + KisMainWindow::OpenFlags batchFlags = d->batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; if (mainWindow->openDocument(templateURL, KisMainWindow::Import | batchFlags)) { dbgUI << "Template loaded..."; return true; } else { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Template %1 failed to load.", templateURL.toDisplayString())); } } return false; } void KisApplication::clearConfig() { KIS_ASSERT_RECOVER_RETURN(qApp->thread() == QThread::currentThread()); KSharedConfigPtr config = KSharedConfig::openConfig(); // find user settings file bool createDir = false; QString kritarcPath = KoResourcePaths::locateLocal("config", "kritarc", createDir); QFile configFile(kritarcPath); if (configFile.exists()) { // clear file if (configFile.open(QFile::WriteOnly)) { configFile.close(); } else { QMessageBox::warning(0, i18nc("@title:window", "Krita"), i18n("Failed to clear %1\n\n" "Please make sure no other program is using the file and try again.", kritarcPath), QMessageBox::Ok, QMessageBox::Ok); } } // reload from disk; with the user file settings cleared, // this should load any default configuration files shipping with the program config->reparseConfiguration(); config->sync(); } void KisApplication::askClearConfig() { Qt::KeyboardModifiers mods = QApplication::queryKeyboardModifiers(); bool askClearConfig = (mods & Qt::ControlModifier) && (mods & Qt::ShiftModifier) && (mods & Qt::AltModifier); if (askClearConfig) { bool ok = QMessageBox::question(0, i18nc("@title:window", "Krita"), i18n("Do you want to clear the settings file?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes; if (ok) { clearConfig(); } } } diff --git a/libs/ui/KisApplication.h b/libs/ui/KisApplication.h index c1aa8310d4..54fed0058c 100644 --- a/libs/ui/KisApplication.h +++ b/libs/ui/KisApplication.h @@ -1,126 +1,124 @@ /* * Copyright (C) 1998, 1999 Torben Weis * Copyright (C) 2012 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KIS_APPLICATION_H #define KIS_APPLICATION_H #include +#include #include #include "kritaui_export.h" -#include class KisMainWindow; class KisApplicationPrivate; class QWidget; class KisApplicationArguments; class KisAutoSaveRecoveryDialog; #include /** * @brief Base class for the %Krita app * * This class handles arguments given on the command line and * shows a generic about dialog for the Krita app. * * In addition it adds the standard directories where Krita * can find its images etc. * * If the last mainwindow becomes closed, KisApplication automatically * calls QApplication::quit. */ class KRITAUI_EXPORT KisApplication : public QtSingleApplication { Q_OBJECT public: /** * Creates an application object, adds some standard directories and * initializes kimgio. */ explicit KisApplication(const QString &key, int &argc, char **argv); /** * Destructor. */ ~KisApplication() override; /** * Call this to start the application. * * Parses command line arguments and creates the initial main windowss and docs * from them (or an empty doc if no cmd-line argument is specified ). * * You must call this method directly before calling QApplication::exec. * * It is valid behaviour not to call this method at all. In this case you * have to process your command line parameters by yourself. */ virtual bool start(const KisApplicationArguments &args); /** * Checks if user is holding ctrl+alt+shift keys and asks if the settings file should be cleared. * * Typically called during startup before reading the config. */ void askClearConfig(); /** * Tell KisApplication to show this splashscreen when you call start(); * when start returns, the splashscreen is hidden. Use KSplashScreen * to have the splash show correctly on Xinerama displays. */ void setSplashScreen(QWidget *splash); void setSplashScreenLoadingText(QString); void hideSplashScreen(); /// Overridden to handle exceptions from event handlers. bool notify(QObject *receiver, QEvent *event) override; void addResourceTypes(); void loadResources(); void loadResourceTags(); void loadPlugins(); void loadGuiPlugins(); void initializeGlobals(const KisApplicationArguments &args); public Q_SLOTS: void remoteArguments(QByteArray message, QObject*socket); void fileOpenRequested(const QString & url); private: /// @return the number of autosavefiles opened void checkAutosaveFiles(); bool createNewDocFromTemplate(const QString &fileName, KisMainWindow *m_mainWindow); void clearConfig(); private: - KisApplicationPrivate * const d; + class Private; + QScopedPointer d; class ResetStarting; friend class ResetStarting; - KisAutoSaveRecoveryDialog *m_autosaveDialog; - QPointer m_mainWindow; // The first mainwindow we create on startup - bool m_batchRun; }; #endif diff --git a/libs/ui/KisApplicationArguments.cpp b/libs/ui/KisApplicationArguments.cpp index c0de4c62f4..759636541d 100644 --- a/libs/ui/KisApplicationArguments.cpp +++ b/libs/ui/KisApplicationArguments.cpp @@ -1,361 +1,337 @@ /* * Copyright (c) 2015 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisApplicationArguments.h" #include #include #include #include #include #include #include #include #include #include #include #include struct Q_DECL_HIDDEN KisApplicationArguments::Private { Private() { } QStringList filenames; int dpiX {72}; int dpiY {72}; bool doTemplate {false}; - bool print {false}; bool exportAs {false}; - bool exportAsPdf {false}; QString exportFileName; QString workspace; QString windowLayout; QString session; bool canvasOnly {false}; bool noSplash {false}; bool fullScreen {false}; bool newImage {false}; QString colorModel {"RGBA"}; QString colorDepth {"U8"}; int width {2000}; int height {5000}; }; KisApplicationArguments::KisApplicationArguments() : d(new Private) { } KisApplicationArguments::~KisApplicationArguments() { } KisApplicationArguments::KisApplicationArguments(const QApplication &app) : d(new Private) { QCommandLineParser parser; parser.addVersionOption(); parser.addHelpOption(); - parser.addOption(QCommandLineOption(QStringList() << QLatin1String("print"), i18n("Only print and exit"))); parser.addOption(QCommandLineOption(QStringList() << QLatin1String("template"), i18n("Open a new document with a template"))); parser.addOption(QCommandLineOption(QStringList() << QLatin1String("new-image"), i18n("Create a new image on startup.\n" "Possible colorspace values are:\n" " * RGBA\n" " * XYZA\n" " * LABA\n" " * CMYKA\n" " * GRAY\n" " * YCbCrA\n" "Possible channel depth arguments are\n" " * U8 (8 bits integer)\n" " * U16 (16 bits integer)\n" " * F16 (16 bits floating point)\n" " * F32 (32 bits floating point)\n"), QLatin1String("colorspace,depth,width,height"))); parser.addOption(QCommandLineOption(QStringList() << QLatin1String("workspace"), i18n("The name of the workspace to open Krita with"), QLatin1String("workspace"))); parser.addOption(QCommandLineOption(QStringList() << QLatin1String("windowlayout"), i18n("The name of the window layout to open Krita with"), QLatin1String("windowlayout"))); parser.addOption(QCommandLineOption(QStringList() << QLatin1String("load-session"), i18n("The name of the session to open Krita with"), QLatin1String("load-session"))); // NB: the argument "session" is already used by QGuiApplication parser.addOption(QCommandLineOption(QStringList() << QLatin1String("canvasonly"), i18n("Start Krita in canvas-only mode"))); parser.addOption(QCommandLineOption(QStringList() << QLatin1String("nosplash"), i18n("Do not show the splash screen"))); parser.addOption(QCommandLineOption(QStringList() << QLatin1String("fullscreen"), i18n("Start Krita in full-screen mode"))); parser.addOption(QCommandLineOption(QStringList() << QLatin1String("dpi"), i18n("Override display DPI"), QLatin1String("dpiX,dpiY"))); - parser.addOption(QCommandLineOption(QStringList() << QLatin1String("export-pdf"), i18n("Only export to PDF and exit"))); parser.addOption(QCommandLineOption(QStringList() << QLatin1String("export"), i18n("Export to the given filename and exit"))); - parser.addOption(QCommandLineOption(QStringList() << QLatin1String("export-filename"), i18n("Filename for export/export-pdf"), QLatin1String("filename"))); + parser.addOption(QCommandLineOption(QStringList() << QLatin1String("export-filename"), i18n("Filename for export"), QLatin1String("filename"))); parser.addPositionalArgument(QLatin1String("[file(s)]"), i18n("File(s) or URL(s) to open")); parser.process(app); QString dpiValues = parser.value("dpi"); if (!dpiValues.isEmpty()) { int sep = dpiValues.indexOf(QRegExp("[x, ]")); bool ok = true; if (sep != -1) { d->dpiY = dpiValues.mid(sep + 1).toInt(&ok); dpiValues.truncate(sep); } if (ok) { d->dpiX = dpiValues.toInt(&ok); if (ok) { if (!d->dpiY) d->dpiY = d->dpiX; } } } QString newImageValues = parser.value("new-image"); d->newImage = !newImageValues.isEmpty(); if (d->newImage) { QStringList v = newImageValues.split(","); if (v.size() != 4) { d->newImage = false; qWarning() << "Cannot create a new image: please specify colormodel, depth, width and height."; } d->colorModel = v[0].toUpper(); d->colorDepth = v[1].toUpper(); d->width = v[2].toInt(); d->height = v[3].toInt(); } d->exportFileName = parser.value("export-filename"); d->workspace = parser.value("workspace"); d->windowLayout = parser.value("windowlayout"); d->session = parser.value("load-session"); d->doTemplate = parser.isSet("template"); - d->print = parser.isSet("print"); d->exportAs = parser.isSet("export"); - d->exportAsPdf = parser.isSet("export-pdf"); d->canvasOnly = parser.isSet("canvasonly"); d->noSplash = parser.isSet("nosplash"); d->fullScreen = parser.isSet("fullscreen"); const QDir currentDir = QDir::current(); Q_FOREACH (const QString &filename, parser.positionalArguments()) { d->filenames << currentDir.absoluteFilePath(filename); } } KisApplicationArguments::KisApplicationArguments(const KisApplicationArguments &rhs) : d(new Private) { d->filenames = rhs.filenames(); d->dpiX = rhs.dpiX(); d->dpiY = rhs.dpiY(); d->doTemplate = rhs.doTemplate(); - d->print = rhs.print(); d->exportAs = rhs.exportAs(); - d->exportAsPdf = rhs.exportAsPdf(); d->exportFileName = rhs.exportFileName(); d->canvasOnly = rhs.canvasOnly(); d->workspace = rhs.workspace(); d->windowLayout = rhs.windowLayout(); d->session = rhs.session(); d->noSplash = rhs.noSplash(); d->fullScreen = rhs.fullScreen(); } void KisApplicationArguments::operator=(const KisApplicationArguments &rhs) { d->filenames = rhs.filenames(); d->dpiX = rhs.dpiX(); d->dpiY = rhs.dpiY(); d->doTemplate = rhs.doTemplate(); - d->print = rhs.print(); d->exportAs = rhs.exportAs(); - d->exportAsPdf = rhs.exportAsPdf(); d->exportFileName = rhs.exportFileName(); d->canvasOnly = rhs.canvasOnly(); d->workspace = rhs.workspace(); d->windowLayout = rhs.windowLayout(); d->session = rhs.session(); d->noSplash = rhs.noSplash(); d->fullScreen = rhs.fullScreen(); } QByteArray KisApplicationArguments::serialize() { QByteArray ba; QBuffer buf(&ba); buf.open(QIODevice::WriteOnly); QDataStream ds(&buf); ds.setVersion(QDataStream::Qt_5_0); ds << d->filenames.count(); Q_FOREACH (const QString &filename, d->filenames) { ds << filename; } ds << d->dpiX; ds << d->dpiY; ds << d->doTemplate; - ds << d->print; ds << d->exportAs; - ds << d->exportAsPdf; ds << d->exportFileName; ds << d->workspace; ds << d->windowLayout; ds << d->session; ds << d->canvasOnly; ds << d->noSplash; ds << d->fullScreen; ds << d->newImage; ds << d->height; ds << d->width; ds << d->height; ds << d->colorModel; ds << d->colorDepth; buf.close(); return ba; } KisApplicationArguments KisApplicationArguments::deserialize(QByteArray &serialized) { KisApplicationArguments args; QBuffer buf(&serialized); buf.open(QIODevice::ReadOnly); QDataStream ds(&buf); ds.setVersion(QDataStream::Qt_5_0); int count; ds >> count; for(int i = 0; i < count; ++i) { QString s; ds >> s; args.d->filenames << s; } ds >> args.d->dpiX; ds >> args.d->dpiY; ds >> args.d->doTemplate; - ds >> args.d->print; ds >> args.d->exportAs; - ds >> args.d->exportAsPdf; ds >> args.d->exportFileName; ds >> args.d->workspace; ds >> args.d->windowLayout; ds >> args.d->session; ds >> args.d->canvasOnly; ds >> args.d->noSplash; ds >> args.d->fullScreen; ds >> args.d->newImage; ds >> args.d->height; ds >> args.d->width; ds >> args.d->height; ds >> args.d->colorModel; ds >> args.d->colorDepth; buf.close(); return args; } QStringList KisApplicationArguments::filenames() const { return d->filenames; } int KisApplicationArguments::dpiX() const { return d->dpiX; } int KisApplicationArguments::dpiY() const { return d->dpiY; } bool KisApplicationArguments::doTemplate() const { return d->doTemplate; } -bool KisApplicationArguments::print() const -{ - return d->print; -} - bool KisApplicationArguments::exportAs() const { return d->exportAs; } -bool KisApplicationArguments::exportAsPdf() const -{ - return d->exportAsPdf; -} - QString KisApplicationArguments::exportFileName() const { return d->exportFileName; } QString KisApplicationArguments::workspace() const { return d->workspace; } QString KisApplicationArguments::windowLayout() const { return d->windowLayout; } QString KisApplicationArguments::session() const { return d->session; } bool KisApplicationArguments::canvasOnly() const { return d->canvasOnly; } bool KisApplicationArguments::noSplash() const { return d->noSplash; } bool KisApplicationArguments::fullScreen() const { return d->fullScreen; } bool KisApplicationArguments::doNewImage() const { return d->newImage; } KisDocument *KisApplicationArguments::image() const { KisDocument *doc = KisPart::instance()->createDocument(); qDebug() << d->colorDepth << d->colorDepth; const KoColorSpace *cs = KoColorSpaceRegistry::instance()->colorSpace(d->colorModel, d->colorDepth, ""); if (!cs) { qWarning() << "Could not create the colorspace for the new image. Check the colorspace and depth arguments."; return 0; } doc->newImage(i18n("Unnamed"), d->width, d->height, cs, KoColor(QColor(Qt::white), cs), false, 1, "", 100.0); return doc; } diff --git a/libs/ui/KisApplicationArguments.h b/libs/ui/KisApplicationArguments.h index d6e9d92219..e7129d11ab 100644 --- a/libs/ui/KisApplicationArguments.h +++ b/libs/ui/KisApplicationArguments.h @@ -1,70 +1,67 @@ /* * Copyright (c) 2015 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KISAPPLICATIONARGUMENTS_H #define KISAPPLICATIONARGUMENTS_H #include class QApplication; class QByteArray; class QStringList; class KisDocument; #include "kritaui_export.h" class KRITAUI_EXPORT KisApplicationArguments { public: KisApplicationArguments(const QApplication &app); KisApplicationArguments(const KisApplicationArguments &rhs); ~KisApplicationArguments(); void operator=(const KisApplicationArguments& rhs); QByteArray serialize(); static KisApplicationArguments deserialize(QByteArray &serialized); QStringList filenames() const; int dpiX() const; int dpiY() const; bool doTemplate() const; - bool print() const; bool exportAs() const; - bool exportAsPdf() const; QString exportFileName() const; QString workspace() const; QString windowLayout() const; QString session() const; bool canvasOnly() const; bool noSplash() const; bool fullScreen() const; bool doNewImage() const; KisDocument *image() const; - private: KisApplicationArguments(); struct Private; const QScopedPointer d; }; #endif // KISAPPLICATIONARGUMENTS_H diff --git a/libs/ui/KisAsyncAnimationCacheRenderer.cpp b/libs/ui/KisAsyncAnimationCacheRenderer.cpp index 1e49f3720a..d41b7dcd02 100644 --- a/libs/ui/KisAsyncAnimationCacheRenderer.cpp +++ b/libs/ui/KisAsyncAnimationCacheRenderer.cpp @@ -1,84 +1,84 @@ /* * Copyright (c) 2017 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KisAsyncAnimationCacheRenderer.h" #include "kis_animation_frame_cache.h" #include "kis_update_info.h" struct KisAsyncAnimationCacheRenderer::Private { KisAnimationFrameCacheSP requestedCache; KisOpenGLUpdateInfoSP requestInfo; }; KisAsyncAnimationCacheRenderer::KisAsyncAnimationCacheRenderer() : m_d(new Private) { connect(this, SIGNAL(sigCompleteRegenerationInternal(int)), SLOT(slotCompleteRegenerationInternal(int))); } KisAsyncAnimationCacheRenderer::~KisAsyncAnimationCacheRenderer() { } void KisAsyncAnimationCacheRenderer::setFrameCache(KisAnimationFrameCacheSP cache) { m_d->requestedCache = cache; } -void KisAsyncAnimationCacheRenderer::frameCompletedCallback(int frame) +void KisAsyncAnimationCacheRenderer::frameCompletedCallback(int frame, const QRegion &requestedRegion) { KisAnimationFrameCacheSP cache = m_d->requestedCache; KisImageSP image = requestedImage(); if (!cache || !image) return; - m_d->requestInfo = cache->fetchFrameData(frame, image); + m_d->requestInfo = cache->fetchFrameData(frame, image, requestedRegion); emit sigCompleteRegenerationInternal(frame); } void KisAsyncAnimationCacheRenderer::slotCompleteRegenerationInternal(int frame) { if (!isActive()) return; KIS_SAFE_ASSERT_RECOVER(m_d->requestInfo) { frameCancelledCallback(frame); return; } m_d->requestedCache->addConvertedFrameData(m_d->requestInfo, frame); notifyFrameCompleted(frame); } void KisAsyncAnimationCacheRenderer::frameCancelledCallback(int frame) { notifyFrameCancelled(frame); } void KisAsyncAnimationCacheRenderer::clearFrameRegenerationState(bool isCancelled) { m_d->requestInfo.clear(); m_d->requestedCache.clear(); KisAsyncAnimationRendererBase::clearFrameRegenerationState(isCancelled); } diff --git a/libs/ui/KisAsyncAnimationCacheRenderer.h b/libs/ui/KisAsyncAnimationCacheRenderer.h index 9701203dfc..4d581c6a59 100644 --- a/libs/ui/KisAsyncAnimationCacheRenderer.h +++ b/libs/ui/KisAsyncAnimationCacheRenderer.h @@ -1,50 +1,50 @@ /* * Copyright (c) 2017 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KISASYNCANIMATIONCACHERENDERER_H #define KISASYNCANIMATIONCACHERENDERER_H #include class KisAsyncAnimationCacheRenderer : public KisAsyncAnimationRendererBase { Q_OBJECT public: KisAsyncAnimationCacheRenderer(); ~KisAsyncAnimationCacheRenderer(); void setFrameCache(KisAnimationFrameCacheSP cache); protected: - void frameCompletedCallback(int frame) override; + void frameCompletedCallback(int frame, const QRegion &requestedRegion) override; void frameCancelledCallback(int frame) override; void clearFrameRegenerationState(bool isCancelled) override; Q_SIGNALS: void sigCompleteRegenerationInternal(int frame); private Q_SLOTS: void slotCompleteRegenerationInternal(int frame); private: struct Private; const QScopedPointer m_d; }; #endif // KISASYNCANIMATIONCACHERENDERER_H diff --git a/libs/ui/KisAsyncAnimationFramesSavingRenderer.cpp b/libs/ui/KisAsyncAnimationFramesSavingRenderer.cpp index 1afb8acd6d..76809a84fd 100644 --- a/libs/ui/KisAsyncAnimationFramesSavingRenderer.cpp +++ b/libs/ui/KisAsyncAnimationFramesSavingRenderer.cpp @@ -1,127 +1,132 @@ /* * Copyright (c) 2017 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KisAsyncAnimationFramesSavingRenderer.h" #include "kis_image.h" #include "kis_paint_device.h" #include "KisImportExportFilter.h" #include "KisPart.h" #include "KisDocument.h" #include "kis_time_range.h" #include "kis_paint_layer.h" struct KisAsyncAnimationFramesSavingRenderer::Private { Private(KisImageSP image, const KisTimeRange &_range, int _sequenceNumberingOffset, KisPropertiesConfigurationSP _exportConfiguration) : savingDoc(KisPart::instance()->createDocument()), range(_range), sequenceNumberingOffset(_sequenceNumberingOffset), exportConfiguration(_exportConfiguration) { - savingDoc->setAutoSaveDelay(0); + savingDoc->setInfiniteAutoSaveInterval(); savingDoc->setFileBatchMode(true); KisImageSP savingImage = new KisImage(savingDoc->createUndoStore(), image->bounds().width(), image->bounds().height(), image->colorSpace(), QString()); savingImage->setResolution(image->xRes(), image->yRes()); savingDoc->setCurrentImage(savingImage); KisPaintLayer* paintLayer = new KisPaintLayer(savingImage, "paint device", 255); savingImage->addNode(paintLayer, savingImage->root(), KisLayerSP(0)); savingDevice = paintLayer->paintDevice(); } QScopedPointer savingDoc; KisPaintDeviceSP savingDevice; KisTimeRange range; int sequenceNumberingOffset = 0; QString filenamePrefix; QString filenameSuffix; QByteArray outputMimeType; KisPropertiesConfigurationSP exportConfiguration; }; KisAsyncAnimationFramesSavingRenderer::KisAsyncAnimationFramesSavingRenderer(KisImageSP image, const QString &fileNamePrefix, const QString &fileNameSuffix, const QByteArray &outputMimeType, const KisTimeRange &range, const int sequenceNumberingOffset, KisPropertiesConfigurationSP exportConfiguration) : m_d(new Private(image, range, sequenceNumberingOffset, exportConfiguration)) { m_d->filenamePrefix = fileNamePrefix; m_d->filenameSuffix = fileNameSuffix; m_d->outputMimeType = outputMimeType; connect(this, SIGNAL(sigCompleteRegenerationInternal(int)), SLOT(notifyFrameCompleted(int))); connect(this, SIGNAL(sigCancelRegenerationInternal(int)), SLOT(notifyFrameCancelled(int))); } KisAsyncAnimationFramesSavingRenderer::~KisAsyncAnimationFramesSavingRenderer() { } -void KisAsyncAnimationFramesSavingRenderer::frameCompletedCallback(int frame) +void KisAsyncAnimationFramesSavingRenderer::frameCompletedCallback(int frame, const QRegion &requestedRegion) { KisImageSP image = requestedImage(); if (!image) return; + KIS_SAFE_ASSERT_RECOVER (requestedRegion == image->bounds()) { + emit sigCancelRegenerationInternal(frame); + return; + } + m_d->savingDevice->makeCloneFromRough(image->projection(), image->bounds()); KisTimeRange range(frame, 1); KisImportExportFilter::ConversionStatus status = KisImportExportFilter::OK; for (int i = range.start(); i <= range.end(); i++) { QString frameNumber = QString("%1").arg(i + m_d->sequenceNumberingOffset, 4, 10, QChar('0')); QString filename = m_d->filenamePrefix + frameNumber + m_d->filenameSuffix; if (!m_d->savingDoc->exportDocumentSync(QUrl::fromLocalFile(filename), m_d->outputMimeType, m_d->exportConfiguration)) { status = KisImportExportFilter::InternalError; break; } } if (status == KisImportExportFilter::OK) { emit sigCompleteRegenerationInternal(frame); } else { emit sigCancelRegenerationInternal(frame); } } void KisAsyncAnimationFramesSavingRenderer::frameCancelledCallback(int frame) { notifyFrameCancelled(frame); } diff --git a/libs/ui/KisAsyncAnimationFramesSavingRenderer.h b/libs/ui/KisAsyncAnimationFramesSavingRenderer.h index 6dffff69a5..efa5fcaf5e 100644 --- a/libs/ui/KisAsyncAnimationFramesSavingRenderer.h +++ b/libs/ui/KisAsyncAnimationFramesSavingRenderer.h @@ -1,53 +1,53 @@ /* * Copyright (c) 2017 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KISASYNCANIMATIONFRAMESSAVINGRENDERER_H #define KISASYNCANIMATIONFRAMESSAVINGRENDERER_H #include class KisDocument; class KisTimeRange; class KisAsyncAnimationFramesSavingRenderer : public KisAsyncAnimationRendererBase { Q_OBJECT public: KisAsyncAnimationFramesSavingRenderer(KisImageSP image, const QString &fileNamePrefix, const QString &fileNameSuffix, const QByteArray &outputMimeType, const KisTimeRange &range, int sequenceNumberingOffset, KisPropertiesConfigurationSP exportConfiguration); ~KisAsyncAnimationFramesSavingRenderer(); protected: - void frameCompletedCallback(int frame) override; + void frameCompletedCallback(int frame, const QRegion &requestedRegion) override; void frameCancelledCallback(int frame) override; Q_SIGNALS: void sigCompleteRegenerationInternal(int frame); void sigCancelRegenerationInternal(int frame); private: struct Private; const QScopedPointer m_d; }; #endif // KISASYNCANIMATIONFRAMESSAVINGRENDERER_H diff --git a/libs/ui/KisAsyncAnimationRendererBase.cpp b/libs/ui/KisAsyncAnimationRendererBase.cpp index 4216a923b7..1ba2f92c5f 100644 --- a/libs/ui/KisAsyncAnimationRendererBase.cpp +++ b/libs/ui/KisAsyncAnimationRendererBase.cpp @@ -1,160 +1,168 @@ /* * Copyright (c) 2017 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KisAsyncAnimationRendererBase.h" #include #include #include "kis_image.h" #include "kis_image_animation_interface.h" #include "kis_signal_auto_connection.h" -struct KisAsyncAnimationRendererBase::Private +struct KRITAUI_NO_EXPORT KisAsyncAnimationRendererBase::Private { KisSignalAutoConnectionsStore imageRequestConnections; QTimer regenerationTimeout; KisImageSP requestedImage; int requestedFrame = -1; bool isCancelled = false; + QRegion requestedRegion; static const int WAITING_FOR_FRAME_TIMEOUT = 10000; }; KisAsyncAnimationRendererBase::KisAsyncAnimationRendererBase(QObject *parent) : QObject(parent), m_d(new Private()) { connect(&m_d->regenerationTimeout, SIGNAL(timeout()), SLOT(slotFrameRegenerationCancelled())); m_d->regenerationTimeout.setSingleShot(true); m_d->regenerationTimeout.setInterval(Private::WAITING_FOR_FRAME_TIMEOUT); } KisAsyncAnimationRendererBase::~KisAsyncAnimationRendererBase() { } -void KisAsyncAnimationRendererBase::startFrameRegeneration(KisImageSP image, int frame) +void KisAsyncAnimationRendererBase::startFrameRegeneration(KisImageSP image, int frame, const QRegion ®ionOfInterest) { KIS_SAFE_ASSERT_RECOVER_NOOP(QThread::currentThread() == this->thread()); m_d->requestedImage = image; m_d->requestedFrame = frame; m_d->isCancelled = false; + m_d->requestedRegion = !regionOfInterest.isEmpty() ? regionOfInterest : image->bounds(); KisImageAnimationInterface *animation = m_d->requestedImage->animationInterface(); m_d->imageRequestConnections.clear(); m_d->imageRequestConnections.addConnection( animation, SIGNAL(sigFrameReady(int)), this, SLOT(slotFrameRegenerationFinished(int)), Qt::DirectConnection); m_d->imageRequestConnections.addConnection( animation, SIGNAL(sigFrameCancelled()), this, SLOT(slotFrameRegenerationCancelled()), Qt::AutoConnection); m_d->regenerationTimeout.start(); - animation->requestFrameRegeneration(m_d->requestedFrame, image->bounds()); + animation->requestFrameRegeneration(m_d->requestedFrame, m_d->requestedRegion); +} + +void KisAsyncAnimationRendererBase::startFrameRegeneration(KisImageSP image, int frame) +{ + startFrameRegeneration(image, frame, QRegion()); } bool KisAsyncAnimationRendererBase::isActive() const { return m_d->requestedImage; } void KisAsyncAnimationRendererBase::cancelCurrentFrameRendering() { KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->requestedImage); frameCancelledCallback(m_d->requestedFrame); } void KisAsyncAnimationRendererBase::slotFrameRegenerationCancelled() { // the timeout can arrive in async way if (!m_d->requestedImage) return; frameCancelledCallback(m_d->requestedFrame); } void KisAsyncAnimationRendererBase::slotFrameRegenerationFinished(int frame) { // We might have already cancelled the regeneration. We don't check // isCancelled flag here because this code runs asynchronously. if (!m_d->requestedImage) return; // WARNING: executed in the context of image worker thread! // probably a bit too strict... KIS_SAFE_ASSERT_RECOVER_NOOP(QThread::currentThread() != this->thread()); - frameCompletedCallback(frame); + frameCompletedCallback(frame, m_d->requestedRegion); } void KisAsyncAnimationRendererBase::notifyFrameCompleted(int frame) { KIS_SAFE_ASSERT_RECOVER_NOOP(QThread::currentThread() == this->thread()); // the image events can come with a delay, even after // the processing was cancelled if (m_d->isCancelled) return; KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->requestedImage); KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->requestedFrame == frame); clearFrameRegenerationState(false); emit sigFrameCompleted(frame); } void KisAsyncAnimationRendererBase::notifyFrameCancelled(int frame) { KIS_SAFE_ASSERT_RECOVER_NOOP(QThread::currentThread() == this->thread()); // the image events can come with a delay, even after // the processing was cancelled if (m_d->isCancelled) return; KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->requestedImage); KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->requestedFrame == frame); clearFrameRegenerationState(true); emit sigFrameCancelled(frame); } void KisAsyncAnimationRendererBase::clearFrameRegenerationState(bool isCancelled) { // TODO: for some reason we mark the process as cancelled in any case, and it // seem to be a correct behavior Q_UNUSED(isCancelled); m_d->imageRequestConnections.clear(); m_d->requestedImage = 0; m_d->requestedFrame = -1; m_d->regenerationTimeout.stop(); m_d->isCancelled = true; + m_d->requestedRegion = QRegion(); } KisImageSP KisAsyncAnimationRendererBase::requestedImage() const { return m_d->requestedImage; } diff --git a/libs/ui/KisAsyncAnimationRendererBase.h b/libs/ui/KisAsyncAnimationRendererBase.h index 388d586b61..48074838a5 100644 --- a/libs/ui/KisAsyncAnimationRendererBase.h +++ b/libs/ui/KisAsyncAnimationRendererBase.h @@ -1,136 +1,145 @@ /* * Copyright (c) 2017 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KISASYNCANIMATIONRENDERERBASE_H #define KISASYNCANIMATIONRENDERERBASE_H #include #include "kis_types.h" +#include "kritaui_export.h" + /** * KisAsyncAnimationRendererBase is a special class represinting a * single worker thread inside KisAsyncAnimationRenderDialogBase. It connects * the specified image using correct Qt::DirectConnection connections and * reacts on them. On sigFrameReady() signal it calls frameCompletedCallback(), * so the derived class can fetch a frame from the image and process it. On * sigFrameCancelled() it calls frameCancelledCallback(). The derived class * should override these two methods to do the actual work. */ -class KisAsyncAnimationRendererBase : public QObject +class KRITAUI_EXPORT KisAsyncAnimationRendererBase : public QObject { Q_OBJECT public: explicit KisAsyncAnimationRendererBase(QObject *parent = 0); virtual ~KisAsyncAnimationRendererBase(); /** - * Initiates the rendering of the frame \p frame on an image \p image + * Initiates the rendering of the frame \p frame on an image \p image. + * Only \p regionOfInterest is regenerated. If \p regionOfInterest is + * empty, then entire bounds of the image is regenerated. + */ + void startFrameRegeneration(KisImageSP image, int frame, const QRegion ®ionOfInterest); + + /** + * Convenience overload that regenerates the full image */ - virtual void startFrameRegeneration(KisImageSP image, int frame); + void startFrameRegeneration(KisImageSP image, int frame); /** * @return true if the regeneration process is in progress */ bool isActive() const; public Q_SLOTS: /** * @brief cancels current rendering operation * * After calling this slot requestedImage() becomes invalid. * @see requestedImage() */ void cancelCurrentFrameRendering(); Q_SIGNALS: void sigFrameCompleted(int frame); void sigFrameCancelled(int frame); private Q_SLOTS: void slotFrameRegenerationCancelled(); void slotFrameRegenerationFinished(int frame); protected Q_SLOTS: /** * Called by a derived class to continue processing of the frames */ void notifyFrameCompleted(int frame); /** * Called by a derived class to cancel processing of the frames. After calling * this method, the dialog will stop processing the frames and close. */ void notifyFrameCancelled(int frame); protected: /** * @brief frameCompletedCallback is called by the renderer when * a new frame becomes ready * * NOTE1: the callback is called from the context of a image * worker thread! So it is asynchronous from the GUI thread. * NOTE2: in case of successful processing of the frame, the callback * must issue some signal, connected to notifyFrameCompleted() * via auto connection, to continue processing. Please do not * call the method directly, because notifyFame*() slots should * be called from the context of the GUI thread. * NOTE3: In case of failure, notifyFrameCancelled(). The same threading * rules apply. */ - virtual void frameCompletedCallback(int frame) = 0; + virtual void frameCompletedCallback(int frame, const QRegion &requestedRegion) = 0; /** * @brief frameCancelledCallback is called when the rendering of * the frame was cancelled. * * The rendering of the frame can be either cancelled by the image itself or * by receiving a timeout signal (10 seconds). * * NOTE: the slot is called in the GUI thread. Don't forget to call * notifyFrameCancelled() in he end of your call. */ virtual void frameCancelledCallback(int frame) = 0; /** * Called by KisAsyncAnimationRendererBase when the processing has been completed * and the internal state of the populator should be cleared * * @param isCancelled tells if frame regeneration has failed to be regenerated */ virtual void clearFrameRegenerationState(bool isCancelled); protected: /** * @return the image that for which the rendering was requested using * startFrameRegeneration(). Should be used by the derived classes only. * * Please note that requestedImage() will become null as soon as the user * cancels the processing. That happends in the GUI thread so * frameCompletedCallback() should be extremely careful when requesting the * value (check the shared pointer after fetching). */ KisImageSP requestedImage() const; private: struct Private; const QScopedPointer m_d; }; #endif // KISASYNCANIMATIONRENDERERBASE_H diff --git a/libs/ui/KisCloneDocumentStroke.cpp b/libs/ui/KisCloneDocumentStroke.cpp new file mode 100644 index 0000000000..17071af1dc --- /dev/null +++ b/libs/ui/KisCloneDocumentStroke.cpp @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2018 Dmitry Kazakov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#include "KisCloneDocumentStroke.h" + +#include "KisDocument.h" +#include "kis_layer_utils.h" + +#include + + +struct KRITAIMAGE_NO_EXPORT KisCloneDocumentStroke::Private +{ + Private(KisDocument *_document) + : document(_document) + { + } + + KisDocument *document = 0; +}; + +KisCloneDocumentStroke::KisCloneDocumentStroke(KisDocument *document) + : KisSimpleStrokeStrategy("clone-document-stroke", kundo2_i18n("Clone Document")), + m_d(new Private(document)) +{ + setClearsRedoOnStart(false); + setRequestsOtherStrokesToEnd(false); + enableJob(JOB_INIT, true, KisStrokeJobData::BARRIER, KisStrokeJobData::EXCLUSIVE); + enableJob(JOB_FINISH, true, KisStrokeJobData::BARRIER, KisStrokeJobData::EXCLUSIVE); +} + +KisCloneDocumentStroke::~KisCloneDocumentStroke() +{ +} + +void KisCloneDocumentStroke::initStrokeCallback() +{ + KisLayerUtils::forceAllDelayedNodesUpdate(m_d->document->image()->root()); +} + +void KisCloneDocumentStroke::finishStrokeCallback() +{ + KisDocument *doc = m_d->document->clone(); + doc->moveToThread(qApp->thread()); + emit sigDocumentCloned(doc); +} diff --git a/plugins/dockers/animation/timeline_docker.h b/libs/ui/KisCloneDocumentStroke.h similarity index 59% copy from plugins/dockers/animation/timeline_docker.h copy to libs/ui/KisCloneDocumentStroke.h index d441ce29f6..d3d8d3ae25 100644 --- a/plugins/dockers/animation/timeline_docker.h +++ b/libs/ui/KisCloneDocumentStroke.h @@ -1,53 +1,45 @@ /* - * Copyright (c) 2015 Jouni Pentikäinen + * Copyright (c) 2018 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ - -#ifndef _TIMELINE_DOCKER_H_ -#define _TIMELINE_DOCKER_H_ +#ifndef KISCLONEDOCUMENTSTROKE_H +#define KISCLONEDOCUMENTSTROKE_H #include "kritaimage_export.h" - -#include -#include - #include +#include "kis_simple_stroke_strategy.h" -class KisCanvas2; -class KisAction; +class KisDocument; -class TimelineDocker : public QDockWidget, public KisMainwindowObserver +class KisCloneDocumentStroke : public QObject, public KisSimpleStrokeStrategy { Q_OBJECT public: - TimelineDocker(); - ~TimelineDocker() override; + KisCloneDocumentStroke(KisDocument *document); + ~KisCloneDocumentStroke(); - QString observerName() override { return "TimelineDocker"; } - void setCanvas(KoCanvasBase *canvas) override; - void unsetCanvas() override; - void setMainWindow(KisViewManager *kisview) override; + void initStrokeCallback() override; + void finishStrokeCallback() override; -public Q_SLOTS: - void slotUpdateIcons(); +Q_SIGNALS: + void sigDocumentCloned(KisDocument *image); private: struct Private; const QScopedPointer m_d; }; - -#endif +#endif // KISCLONEDOCUMENTSTROKE_H diff --git a/libs/ui/KisColorsetChooser.cpp b/libs/ui/KisColorsetChooser.cpp index a48d199b3c..7cbee899b0 100644 --- a/libs/ui/KisColorsetChooser.cpp +++ b/libs/ui/KisColorsetChooser.cpp @@ -1,166 +1,166 @@ /* This file is part of the KDE project * Copyright (C) 2013 Sven Langkamp * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisColorsetChooser.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "KisResourceServerProvider.h" #include "KisViewManager.h" #include #include #include #include -#include + #include #include "kis_int_parse_spin_box.h" class ColorSetDelegate : public QAbstractItemDelegate { public: ColorSetDelegate(QObject * parent = 0) : QAbstractItemDelegate(parent) {} ~ColorSetDelegate() override {} /// reimplemented void paint(QPainter *, const QStyleOptionViewItem &, const QModelIndex &) const override; /// reimplemented QSize sizeHint(const QStyleOptionViewItem & option, const QModelIndex &) const override { return option.decorationSize; } }; void ColorSetDelegate::paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const { painter->save(); if (! index.isValid()) return; KoResource* resource = static_cast(index.internalPointer()); KoColorSet* colorSet = static_cast(resource); if (option.state & QStyle::State_Selected) { painter->fillRect(option.rect, option.palette.highlight()); painter->setPen(option.palette.highlightedText().color()); } else { painter->setBrush(option.palette.text().color()); } painter->drawText(option.rect.x() + 5, option.rect.y() + painter->fontMetrics().ascent() + 5, colorSet->name()); int size = 7; for (quint32 i = 0; i < colorSet->nColors() && i*size < (quint32)option.rect.width(); i++) { QRect rect(option.rect.x() + i*size, option.rect.y() + option.rect.height() - size, size, size); - painter->fillRect(rect, colorSet->getColorGlobal(i).color.toQColor()); + painter->fillRect(rect, colorSet->getColorGlobal(i).color().toQColor()); } painter->restore(); } KisColorsetChooser::KisColorsetChooser(QWidget* parent): QWidget(parent) { KoResourceServer * rserver = KoResourceServerProvider::instance()->paletteServer(); QSharedPointer adapter(new KoResourceServerAdapter(rserver)); m_itemChooser = new KoResourceItemChooser(adapter, this); m_itemChooser->setItemDelegate(new ColorSetDelegate(this)); m_itemChooser->showTaggingBar(true); m_itemChooser->setFixedSize(250, 250); m_itemChooser->setRowHeight(30); m_itemChooser->setColumnCount(1); connect(m_itemChooser, SIGNAL(resourceSelected(KoResource*)), this, SLOT(resourceSelected(KoResource*))); KisConfig cfg; m_itemChooser->configureKineticScrolling(cfg.kineticScrollingGesture(), cfg.kineticScrollingSensitivity(), cfg.kineticScrollingScrollbar()); QPushButton* saveButton = new QPushButton(i18n("Save")); connect(saveButton, SIGNAL(clicked(bool)), this, SLOT(slotSave())); m_nameEdit = new QLineEdit(this); m_nameEdit->setPlaceholderText(i18n("Insert name")); m_nameEdit->setClearButtonEnabled(true); m_columnEdit = new KisIntParseSpinBox(this); m_columnEdit->setRange(1, 30); m_columnEdit->setValue(10); QGridLayout* layout = new QGridLayout(this); layout->addWidget(m_itemChooser, 0, 0, 1, 3); - layout->addWidget(new QLabel(i18n("Name:"), this), 1, 0, 1, 1); + layout->setColumnStretch(1, 1); + layout->addWidget(saveButton, 2, 2, 1, 1); layout->addWidget(m_nameEdit, 1, 1, 1, 2); - layout->addWidget(new QLabel(i18n("Columns:"), this), 2, 0, 1, 1); + layout->addWidget(new QLabel(i18n("Name:"), this), 1, 0, 1, 1); layout->addWidget(m_columnEdit, 2, 1, 1, 1); - layout->addWidget(saveButton, 2, 2, 1, 1); - layout->setColumnStretch(1, 1); + layout->addWidget(new QLabel(i18n("Columns:"), this), 2, 0, 1, 1); } KisColorsetChooser::~KisColorsetChooser() { } void KisColorsetChooser::resourceSelected(KoResource* resource) { emit paletteSelected(static_cast(resource)); } void KisColorsetChooser::slotSave() { KoResourceServer * rserver = KoResourceServerProvider::instance()->paletteServer(); KoColorSet* colorset = new KoColorSet(); colorset->setValid(true); QString saveLocation = rserver->saveLocation(); QString name = m_nameEdit->text(); int columns = m_columnEdit->value(); bool newName = false; if(name.isEmpty()) { newName = true; name = i18n("Palette"); } QFileInfo fileInfo(saveLocation + name + colorset->defaultFileExtension()); int i = 1; while (fileInfo.exists()) { fileInfo.setFile(saveLocation + name + QString("%1").arg(i) + colorset->defaultFileExtension()); i++; } colorset->setFilename(fileInfo.filePath()); if(newName) { name = i18n("Palette %1", i); } colorset->setName(name); colorset->setColumnCount(columns); rserver->addResource(colorset); } diff --git a/libs/ui/KisDocument.cpp b/libs/ui/KisDocument.cpp index 2b1a597315..b2b8b6cba5 100644 --- a/libs/ui/KisDocument.cpp +++ b/libs/ui/KisDocument.cpp @@ -1,1732 +1,1797 @@ /* This file is part of the Krita project * * Copyright (C) 2014 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisMainWindow.h" // XXX: remove #include // XXX: remove #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Krita Image #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_layer_utils.h" // Local #include "KisViewManager.h" #include "kis_clipboard.h" #include "widgets/kis_custom_image_widget.h" #include "canvas/kis_canvas2.h" #include "flake/kis_shape_controller.h" #include "kis_statusbar.h" #include "widgets/kis_progress_widget.h" #include "kis_canvas_resource_provider.h" #include "KisResourceServerProvider.h" #include "kis_node_manager.h" #include "KisPart.h" #include "KisApplication.h" #include "KisDocument.h" #include "KisImportExportManager.h" #include "KisPart.h" #include "KisView.h" #include "kis_grid_config.h" #include "kis_guides_config.h" #include "kis_image_barrier_lock_adapter.h" #include "KisReferenceImagesLayer.h" #include #include "kis_config_notifier.h" #include "kis_async_action_feedback.h" +#include "KisCloneDocumentStroke.h" // Define the protocol used here for embedded documents' URL // This used to "store" but QUrl didn't like it, // so let's simply make it "tar" ! #define STORE_PROTOCOL "tar" // The internal path is a hack to make QUrl happy and for document children #define INTERNAL_PROTOCOL "intern" #define INTERNAL_PREFIX "intern:/" // Warning, keep it sync in koStore.cc #include using namespace std; namespace { constexpr int errorMessageTimeout = 5000; constexpr int successMessageTimeout = 1000; } /********************************************************** * * KisDocument * **********************************************************/ //static QString KisDocument::newObjectName() { static int s_docIFNumber = 0; QString name; name.setNum(s_docIFNumber++); name.prepend("document_"); return name; } class UndoStack : public KUndo2Stack { public: UndoStack(KisDocument *doc) : KUndo2Stack(doc), m_doc(doc) { } void setIndex(int idx) override { KisImageWSP image = this->image(); image->requestStrokeCancellation(); if(image->tryBarrierLock()) { KUndo2Stack::setIndex(idx); image->unlock(); } } void notifySetIndexChangedOneCommand() override { KisImageWSP image = this->image(); image->unlock(); /** * Some very weird commands may emit blocking signals to * the GUI (e.g. KisGuiContextCommand). Here is the best thing * we can do to avoid the deadlock */ while(!image->tryBarrierLock()) { QApplication::processEvents(); } } void undo() override { KisImageWSP image = this->image(); image->requestUndoDuringStroke(); if (image->tryUndoUnfinishedLod0Stroke() == UNDO_OK) { return; } if(image->tryBarrierLock()) { KUndo2Stack::undo(); image->unlock(); } } void redo() override { KisImageWSP image = this->image(); if(image->tryBarrierLock()) { KUndo2Stack::redo(); image->unlock(); } } private: KisImageWSP image() { KisImageWSP currentImage = m_doc->image(); Q_ASSERT(currentImage); return currentImage; } private: KisDocument *m_doc; }; class Q_DECL_HIDDEN KisDocument::Private { public: Private(KisDocument *q) : docInfo(new KoDocumentInfo(q)) // deleted by QObject , importExportManager(new KisImportExportManager(q)) // deleted manually + , autoSaveTimer(new QTimer(q)) , undoStack(new UndoStack(q)) // deleted by QObject , m_bAutoDetectedMime(false) , modified(false) , readwrite(true) , firstMod(QDateTime::currentDateTime()) , lastMod(firstMod) , nserver(new KisNameServer(1)) , imageIdleWatcher(2000 /*ms*/) , savingLock(&savingMutex) , batchMode(false) { if (QLocale().measurementSystem() == QLocale::ImperialSystem) { unit = KoUnit::Inch; } else { unit = KoUnit::Centimeter; } } Private(const Private &rhs, KisDocument *q) : docInfo(new KoDocumentInfo(*rhs.docInfo, q)) , unit(rhs.unit) , importExportManager(new KisImportExportManager(q)) , mimeType(rhs.mimeType) , outputMimeType(rhs.outputMimeType) + , autoSaveTimer(new QTimer(q)) , undoStack(new UndoStack(q)) , guidesConfig(rhs.guidesConfig) , m_bAutoDetectedMime(rhs.m_bAutoDetectedMime) , m_url(rhs.m_url) , m_file(rhs.m_file) , modified(rhs.modified) , readwrite(rhs.readwrite) , firstMod(rhs.firstMod) , lastMod(rhs.lastMod) , nserver(new KisNameServer(*rhs.nserver)) , preActivatedNode(0) // the node is from another hierarchy! , imageIdleWatcher(2000 /*ms*/) , assistants(rhs.assistants) // WARNING: assistants should not store pointers to the document! , gridConfig(rhs.gridConfig) , savingLock(&savingMutex) , batchMode(rhs.batchMode) { } ~Private() { // Don't delete m_d->shapeController because it's in a QObject hierarchy. delete nserver; } KoDocumentInfo *docInfo = 0; KoUnit unit; KisImportExportManager *importExportManager = 0; // The filter-manager to use when loading/saving [for the options] QByteArray mimeType; // The actual mimetype of the document QByteArray outputMimeType; // The mimetype to use when saving - QTimer autoSaveTimer; + QTimer *autoSaveTimer; QString lastErrorMessage; // see openFile() QString lastWarningMessage; int autoSaveDelay = 300; // in seconds, 0 to disable. bool modifiedAfterAutosave = false; bool isAutosaving = false; bool disregardAutosaveFailure = false; + int autoSaveFailureCount = 0; KUndo2Stack *undoStack = 0; KisGuidesConfig guidesConfig; bool m_bAutoDetectedMime = false; // whether the mimetype in the arguments was detected by the part itself QUrl m_url; // local url - the one displayed to the user. QString m_file; // Local file - the only one the part implementation should deal with. QMutex savingMutex; bool modified = false; bool readwrite = false; QDateTime firstMod; QDateTime lastMod; KisNameServer *nserver; KisImageSP image; KisImageSP savingImage; KisNodeWSP preActivatedNode; KisShapeController* shapeController = 0; KoShapeController* koShapeController = 0; KisIdleWatcher imageIdleWatcher; QScopedPointer imageIdleConnection; QList assistants; KisSharedPtr referenceImagesLayer; KisGridConfig gridConfig; StdLockableWrapper savingLock; bool modifiedWhileSaving = false; QScopedPointer backgroundSaveDocument; QPointer savingUpdater; QFuture childSavingFuture; KritaUtils::ExportFileJob backgroundSaveJob; bool isRecovered = false; bool batchMode { false }; void setImageAndInitIdleWatcher(KisImageSP _image) { image = _image; imageIdleWatcher.setTrackedImage(image); if (image) { imageIdleConnection.reset( new KisSignalAutoConnection( &imageIdleWatcher, SIGNAL(startedIdleMode()), image.data(), SLOT(explicitRegenerateLevelOfDetail()))); } } class StrippedSafeSavingLocker; }; class KisDocument::Private::StrippedSafeSavingLocker { public: StrippedSafeSavingLocker(QMutex *savingMutex, KisImageSP image) : m_locked(false) , m_image(image) , m_savingLock(savingMutex) , m_imageLock(image, true) { /** * Initial try to lock both objects. Locking the image guards * us from any image composition threads running in the * background, while savingMutex guards us from entering the * saving code twice by autosave and main threads. * * Since we are trying to lock multiple objects, so we should * do it in a safe manner. */ m_locked = std::try_lock(m_imageLock, m_savingLock) < 0; if (!m_locked) { m_image->requestStrokeEnd(); QApplication::processEvents(); // one more try... m_locked = std::try_lock(m_imageLock, m_savingLock) < 0; } } ~StrippedSafeSavingLocker() { if (m_locked) { m_imageLock.unlock(); m_savingLock.unlock(); } } bool successfullyLocked() const { return m_locked; } private: bool m_locked; KisImageSP m_image; StdLockableWrapper m_savingLock; KisImageBarrierLockAdapter m_imageLock; }; KisDocument::KisDocument() : d(new Private(this)) { connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); connect(d->undoStack, SIGNAL(cleanChanged(bool)), this, SLOT(slotUndoStackCleanChanged(bool))); - connect(&d->autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave())); + connect(d->autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave())); setObjectName(newObjectName()); // preload the krita resources KisResourceServerProvider::instance(); d->shapeController = new KisShapeController(this, d->nserver), d->koShapeController = new KoShapeController(0, d->shapeController), slotConfigChanged(); } KisDocument::KisDocument(const KisDocument &rhs) : QObject(), d(new Private(*rhs.d, this)) { connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); connect(d->undoStack, SIGNAL(cleanChanged(bool)), this, SLOT(slotUndoStackCleanChanged(bool))); - connect(&d->autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave())); + connect(d->autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave())); setObjectName(rhs.objectName()); d->shapeController = new KisShapeController(this, d->nserver), d->koShapeController = new KoShapeController(0, d->shapeController), slotConfigChanged(); // clone the image with keeping the GUIDs of the layers intact // NOTE: we expect the image to be locked! setCurrentImage(rhs.image()->clone(true), false); if (rhs.d->preActivatedNode) { // since we clone uuid's, we can use them for lacating new // nodes. Otherwise we would need to use findSymmetricClone() d->preActivatedNode = KisLayerUtils::findNodeByUuid(d->image->root(), rhs.d->preActivatedNode->uuid()); } } KisDocument::~KisDocument() { // wait until all the pending operations are in progress waitForSavingToComplete(); /** * Push a timebomb, which will try to release the memory after * the document has been deleted */ KisPaintDevice::createMemoryReleaseObject()->deleteLater(); - d->autoSaveTimer.disconnect(this); - d->autoSaveTimer.stop(); + d->autoSaveTimer->disconnect(this); + d->autoSaveTimer->stop(); delete d->importExportManager; // Despite being QObject they needs to be deleted before the image delete d->shapeController; delete d->koShapeController; if (d->image) { d->image->notifyAboutToBeDeleted(); /** * WARNING: We should wait for all the internal image jobs to * finish before entering KisImage's destructor. The problem is, * while execution of KisImage::~KisImage, all the weak shared * pointers pointing to the image enter an inconsistent * state(!). The shared counter is already zero and destruction * has started, but the weak reference doesn't know about it, * because KisShared::~KisShared hasn't been executed yet. So all * the threads running in background and having weak pointers will * enter the KisImage's destructor as well. */ d->image->requestStrokeCancellation(); d->image->waitForDone(); // clear undo commands that can still point to the image d->undoStack->clear(); d->image->waitForDone(); KisImageWSP sanityCheckPointer = d->image; Q_UNUSED(sanityCheckPointer); // The following line trigger the deletion of the image d->image.clear(); // check if the image has actually been deleted KIS_SAFE_ASSERT_RECOVER_NOOP(!sanityCheckPointer.isValid()); } delete d; } bool KisDocument::reload() { // XXX: reimplement! return false; } KisDocument *KisDocument::clone() { return new KisDocument(*this); } bool KisDocument::exportDocumentImpl(const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration) { QFileInfo filePathInfo(job.filePath); if (filePathInfo.exists() && !filePathInfo.isWritable()) { slotCompleteSavingDocument(job, KisImportExportFilter::CreationError, i18n("%1 cannot be written to. Please save under a different name.", job.filePath)); return false; } KisConfig cfg; if (cfg.backupFile() && filePathInfo.exists()) { KBackup::backupFile(job.filePath); } KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!job.mimeType.isEmpty(), false); const QString actionName = job.flags & KritaUtils::SaveIsExporting ? i18n("Exporting Document...") : i18n("Saving Document..."); bool started = initiateSavingInBackground(actionName, this, SLOT(slotCompleteSavingDocument(KritaUtils::ExportFileJob, KisImportExportFilter::ConversionStatus,QString)), job, exportConfiguration); if (!started) { emit canceled(QString()); } return started; } bool KisDocument::exportDocument(const QUrl &url, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { using namespace KritaUtils; SaveFlags flags = SaveIsExporting; if (showWarnings) { flags |= SaveShowWarnings; } return exportDocumentImpl(KritaUtils::ExportFileJob(url.toLocalFile(), mimeType, flags), exportConfiguration); } bool KisDocument::saveAs(const QUrl &url, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { using namespace KritaUtils; return exportDocumentImpl(ExportFileJob(url.toLocalFile(), mimeType, showWarnings ? SaveShowWarnings : SaveNone), exportConfiguration); } bool KisDocument::save(bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { return saveAs(url(), mimeType(), showWarnings, exportConfiguration); } QByteArray KisDocument::serializeToNativeByteArray() { QByteArray byteArray; QBuffer buffer(&byteArray); QScopedPointer filter(KisImportExportManager::filterForMimeType(nativeFormatMimeType(), KisImportExportManager::Export)); filter->setBatchMode(true); filter->setMimeType(nativeFormatMimeType()); Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image); if (!locker.successfullyLocked()) { return byteArray; } d->savingImage = d->image; if (filter->convert(this, &buffer) != KisImportExportFilter::OK) { qWarning() << "serializeToByteArray():: Could not export to our native format"; } return byteArray; } void KisDocument::slotCompleteSavingDocument(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage) { if (status == KisImportExportFilter::UserCancelled) return; const QString fileName = QFileInfo(job.filePath).fileName(); if (status != KisImportExportFilter::OK) { emit statusBarMessage(i18nc("%1 --- failing file name, %2 --- error message", "Error during saving %1: %2", fileName, exportErrorToUserMessage(status, errorMessage)), errorMessageTimeout); if (!fileBatchMode()) { const QString filePath = job.filePath; QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not save %1\nReason: %2", filePath, errorMessage)); } } else { if (!(job.flags & KritaUtils::SaveIsExporting)) { setUrl(QUrl::fromLocalFile(job.filePath)); setLocalFilePath(job.filePath); setMimeType(job.mimeType); updateEditingTime(true); if (!d->modifiedWhileSaving) { d->undoStack->setClean(); } setRecovered(false); removeAutoSaveFiles(); } emit completed(); emit sigSavingFinished(); emit statusBarMessage(i18n("Finished saving %1", fileName), successMessageTimeout); } } QByteArray KisDocument::mimeType() const { return d->mimeType; } void KisDocument::setMimeType(const QByteArray & mimeType) { d->mimeType = mimeType; } bool KisDocument::fileBatchMode() const { return d->batchMode; } void KisDocument::setFileBatchMode(const bool batchMode) { d->batchMode = batchMode; } KisDocument* KisDocument::lockAndCloneForSaving() { // force update of all the asynchronous nodes before cloning QApplication::processEvents(); KisLayerUtils::forceAllDelayedNodesUpdate(d->image->root()); KisMainWindow *window = KisPart::instance()->currentMainwindow(); if (window) { if (window->viewManager()) { if (!window->viewManager()->blockUntilOperationsFinished(d->image)) { return 0; } } } Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image); if (!locker.successfullyLocked()) { return 0; } return new KisDocument(*this); } bool KisDocument::exportDocumentSync(const QUrl &url, const QByteArray &mimeType, KisPropertiesConfigurationSP exportConfiguration) { Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image); if (!locker.successfullyLocked()) { return false; } d->savingImage = d->image; const QString fileName = url.toLocalFile(); KisImportExportFilter::ConversionStatus status = d->importExportManager-> exportDocument(fileName, fileName, mimeType, false, exportConfiguration); d->savingImage = 0; return status == KisImportExportFilter::OK; } bool KisDocument::initiateSavingInBackground(const QString actionName, const QObject *receiverObject, const char *receiverMethod, const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration) +{ + return initiateSavingInBackground(actionName, receiverObject, receiverMethod, + job, exportConfiguration, std::unique_ptr()); +} + +bool KisDocument::initiateSavingInBackground(const QString actionName, + const QObject *receiverObject, const char *receiverMethod, + const KritaUtils::ExportFileJob &job, + KisPropertiesConfigurationSP exportConfiguration, + std::unique_ptr &&optionalClonedDocument) { KIS_ASSERT_RECOVER_RETURN_VALUE(job.isValid(), false); - QScopedPointer clonedDocument(lockAndCloneForSaving()); + QScopedPointer clonedDocument; + + if (!optionalClonedDocument) { + clonedDocument.reset(lockAndCloneForSaving()); + } else { + clonedDocument.reset(optionalClonedDocument.release()); + } // we block saving until the current saving is finished! if (!clonedDocument || !d->savingMutex.tryLock()) { return false; } KIS_ASSERT_RECOVER_RETURN_VALUE(!d->backgroundSaveDocument, false); KIS_ASSERT_RECOVER_RETURN_VALUE(!d->backgroundSaveJob.isValid(), false); d->backgroundSaveDocument.reset(clonedDocument.take()); d->backgroundSaveJob = job; d->modifiedWhileSaving = false; if (d->backgroundSaveJob.flags & KritaUtils::SaveInAutosaveMode) { d->backgroundSaveDocument->d->isAutosaving = true; } connect(d->backgroundSaveDocument.data(), SIGNAL(sigBackgroundSavingFinished(KisImportExportFilter::ConversionStatus, const QString&)), this, SLOT(slotChildCompletedSavingInBackground(KisImportExportFilter::ConversionStatus, const QString&))); connect(this, SIGNAL(sigCompleteBackgroundSaving(KritaUtils::ExportFileJob,KisImportExportFilter::ConversionStatus,QString)), receiverObject, receiverMethod, Qt::UniqueConnection); bool started = d->backgroundSaveDocument->startExportInBackground(actionName, job.filePath, job.filePath, job.mimeType, job.flags & KritaUtils::SaveShowWarnings, exportConfiguration); if (!started) { // the state should have been deinitialized in slotChildCompletedSavingInBackground() KIS_SAFE_ASSERT_RECOVER (!d->backgroundSaveDocument && !d->backgroundSaveJob.isValid()) { d->backgroundSaveDocument.take()->deleteLater(); d->savingMutex.unlock(); d->backgroundSaveJob = KritaUtils::ExportFileJob(); } } return started; } void KisDocument::slotChildCompletedSavingInBackground(KisImportExportFilter::ConversionStatus status, const QString &errorMessage) { KIS_SAFE_ASSERT_RECOVER(!d->savingMutex.tryLock()) { d->savingMutex.unlock(); return; } KIS_SAFE_ASSERT_RECOVER_RETURN(d->backgroundSaveDocument); if (d->backgroundSaveJob.flags & KritaUtils::SaveInAutosaveMode) { d->backgroundSaveDocument->d->isAutosaving = false; } d->backgroundSaveDocument.take()->deleteLater(); d->savingMutex.unlock(); KIS_SAFE_ASSERT_RECOVER_RETURN(d->backgroundSaveJob.isValid()); const KritaUtils::ExportFileJob job = d->backgroundSaveJob; d->backgroundSaveJob = KritaUtils::ExportFileJob(); emit sigCompleteBackgroundSaving(job, status, errorMessage); } -void KisDocument::slotAutoSave() +void KisDocument::slotAutoSaveImpl(std::unique_ptr &&optionalClonedDocument) { if (!d->modified || !d->modifiedAfterAutosave) return; const QString autoSaveFileName = generateAutoSaveFileName(localFilePath()); emit statusBarMessage(i18n("Autosaving... %1", autoSaveFileName), successMessageTimeout); + const bool hadClonedDocument = bool(optionalClonedDocument); bool started = false; - if (d->image->isIdle()) { + if (d->image->isIdle() || hadClonedDocument) { started = initiateSavingInBackground(i18n("Autosaving..."), this, SLOT(slotCompleteAutoSaving(KritaUtils::ExportFileJob, KisImportExportFilter::ConversionStatus, const QString&)), KritaUtils::ExportFileJob(autoSaveFileName, nativeFormatMimeType(), KritaUtils::SaveIsExporting | KritaUtils::SaveInAutosaveMode), - 0); + 0, + std::move(optionalClonedDocument)); } else { emit statusBarMessage(i18n("Autosaving postponed: document is busy..."), errorMessageTimeout); } - if (!started) { - const int emergencyAutoSaveInterval = 10; // sec - setAutoSaveDelay(emergencyAutoSaveInterval); + if (!started && !hadClonedDocument && d->autoSaveFailureCount >= 3) { + KisCloneDocumentStroke *stroke = new KisCloneDocumentStroke(this); + connect(stroke, SIGNAL(sigDocumentCloned(KisDocument*)), + this, SLOT(slotInitiateAsyncAutosaving(KisDocument*)), + Qt::BlockingQueuedConnection); + + KisStrokeId strokeId = d->image->startStroke(stroke); + d->image->endStroke(strokeId); + + setInfiniteAutoSaveInterval(); + + } else if (!started) { + setEmergencyAutoSaveInterval(); } else { d->modifiedAfterAutosave = false; } } +void KisDocument::slotAutoSave() +{ + slotAutoSaveImpl(std::unique_ptr()); +} + +void KisDocument::slotInitiateAsyncAutosaving(KisDocument *clonedDocument) +{ + slotAutoSaveImpl(std::unique_ptr(clonedDocument)); +} + void KisDocument::slotCompleteAutoSaving(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage) { Q_UNUSED(job); const QString fileName = QFileInfo(job.filePath).fileName(); if (status != KisImportExportFilter::OK) { - const int emergencyAutoSaveInterval = 10; // sec - setAutoSaveDelay(emergencyAutoSaveInterval); + setEmergencyAutoSaveInterval(); emit statusBarMessage(i18nc("%1 --- failing file name, %2 --- error message", "Error during autosaving %1: %2", fileName, exportErrorToUserMessage(status, errorMessage)), errorMessageTimeout); } else { KisConfig cfg; d->autoSaveDelay = cfg.autoSaveInterval(); if (!d->modifiedWhileSaving) { - d->autoSaveTimer.stop(); // until the next change + d->autoSaveTimer->stop(); // until the next change + d->autoSaveFailureCount = 0; } else { - setAutoSaveDelay(d->autoSaveDelay); // restart the timer + setNormalAutoSaveInterval(); } emit statusBarMessage(i18n("Finished autosaving %1", fileName), successMessageTimeout); } } bool KisDocument::startExportInBackground(const QString &actionName, const QString &location, const QString &realLocation, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { d->savingImage = d->image; KisMainWindow *window = KisPart::instance()->currentMainwindow(); if (window) { if (window->viewManager()) { d->savingUpdater = window->viewManager()->createThreadedUpdater(actionName); d->importExportManager->setUpdater(d->savingUpdater); } } KisImportExportFilter::ConversionStatus initializationStatus; d->childSavingFuture = d->importExportManager->exportDocumentAsyc(location, realLocation, mimeType, initializationStatus, showWarnings, exportConfiguration); if (initializationStatus != KisImportExportFilter::ConversionStatus::OK) { if (d->savingUpdater) { d->savingUpdater->cancel(); } d->savingImage.clear(); emit sigBackgroundSavingFinished(initializationStatus, this->errorMessage()); return false; } typedef QFutureWatcher StatusWatcher; StatusWatcher *watcher = new StatusWatcher(); watcher->setFuture(d->childSavingFuture); connect(watcher, SIGNAL(finished()), SLOT(finishExportInBackground())); connect(watcher, SIGNAL(finished()), watcher, SLOT(deleteLater())); return true; } void KisDocument::finishExportInBackground() { KIS_SAFE_ASSERT_RECOVER(d->childSavingFuture.isFinished()) { emit sigBackgroundSavingFinished(KisImportExportFilter::InternalError, ""); return; } KisImportExportFilter::ConversionStatus status = d->childSavingFuture.result(); const QString errorMessage = this->errorMessage(); d->savingImage.clear(); d->childSavingFuture = QFuture(); d->lastErrorMessage.clear(); if (d->savingUpdater) { d->savingUpdater->setProgress(100); } emit sigBackgroundSavingFinished(status, errorMessage); } void KisDocument::setReadWrite(bool readwrite) { d->readwrite = readwrite; - setAutoSaveDelay(d->autoSaveDelay); + setNormalAutoSaveInterval(); Q_FOREACH (KisMainWindow *mainWindow, KisPart::instance()->mainWindows()) { mainWindow->setReadWrite(readwrite); } } void KisDocument::setAutoSaveDelay(int delay) { - //qDebug() << "setting autosave delay from" << d->autoSaveDelay << "to" << delay; - d->autoSaveDelay = delay; - if (isReadWrite() && d->autoSaveDelay > 0) { - d->autoSaveTimer.start(d->autoSaveDelay * 1000); - } - else { - d->autoSaveTimer.stop(); + if (isReadWrite() && delay > 0) { + d->autoSaveTimer->start(delay * 1000); + } else { + d->autoSaveTimer->stop(); } } +void KisDocument::setNormalAutoSaveInterval() +{ + setAutoSaveDelay(d->autoSaveDelay); + d->autoSaveFailureCount = 0; +} + +void KisDocument::setEmergencyAutoSaveInterval() +{ + const int emergencyAutoSaveInterval = 10; /* sec */ + setAutoSaveDelay(emergencyAutoSaveInterval); + d->autoSaveFailureCount++; +} + +void KisDocument::setInfiniteAutoSaveInterval() +{ + setAutoSaveDelay(-1); +} + KoDocumentInfo *KisDocument::documentInfo() const { return d->docInfo; } bool KisDocument::isModified() const { return d->modified; } QPixmap KisDocument::generatePreview(const QSize& size) { KisImageSP image = d->image; if (d->savingImage) image = d->savingImage; if (image) { QRect bounds = image->bounds(); QSize newSize = bounds.size(); newSize.scale(size, Qt::KeepAspectRatio); QPixmap px = QPixmap::fromImage(image->convertToQImage(newSize, 0)); if (px.size() == QSize(0,0)) { px = QPixmap(newSize); QPainter gc(&px); QBrush checkBrush = QBrush(KisCanvasWidgetBase::createCheckersImage(newSize.width() / 5)); gc.fillRect(px.rect(), checkBrush); gc.end(); } return px; } return QPixmap(size); } QString KisDocument::generateAutoSaveFileName(const QString & path) const { QString retval; // Using the extension allows to avoid relying on the mime magic when opening const QString extension (".kra"); QRegularExpression autosavePattern("^\\..+-autosave.kra$"); QFileInfo fi(path); QString dir = fi.absolutePath(); QString filename = fi.fileName(); if (path.isEmpty() || autosavePattern.match(filename).hasMatch()) { // Never saved? #ifdef Q_OS_WIN // On Windows, use the temp location (https://bugs.kde.org/show_bug.cgi?id=314921) retval = QString("%1%2.%3-%4-%5-autosave%6").arg(QDir::tempPath()).arg(QDir::separator()).arg("krita").arg(qApp->applicationPid()).arg(objectName()).arg(extension); #else // On Linux, use a temp file in $HOME then. Mark it with the pid so two instances don't overwrite each other's autosave file retval = QString("%1%2.%3-%4-%5-autosave%6").arg(QDir::homePath()).arg(QDir::separator()).arg("krita").arg(qApp->applicationPid()).arg(objectName()).arg(extension); #endif } else { retval = QString("%1%2.%3-autosave%4").arg(dir).arg(QDir::separator()).arg(filename).arg(extension); } //qDebug() << "generateAutoSaveFileName() for path" << path << ":" << retval; return retval; } bool KisDocument::importDocument(const QUrl &_url) { bool ret; dbgUI << "url=" << _url.url(); // open... ret = openUrl(_url); // reset url & m_file (kindly? set by KisParts::openUrl()) to simulate a // File --> Import if (ret) { dbgUI << "success, resetting url"; resetURL(); setTitleModified(); } return ret; } bool KisDocument::openUrl(const QUrl &_url, OpenFlags flags) { if (!_url.isLocalFile()) { return false; } dbgUI << "url=" << _url.url(); d->lastErrorMessage.clear(); // Reimplemented, to add a check for autosave files and to improve error reporting if (!_url.isValid()) { d->lastErrorMessage = i18n("Malformed URL\n%1", _url.url()); // ## used anywhere ? return false; } QUrl url(_url); bool autosaveOpened = false; if (url.isLocalFile() && !fileBatchMode()) { QString file = url.toLocalFile(); QString asf = generateAutoSaveFileName(file); if (QFile::exists(asf)) { KisApplication *kisApp = static_cast(qApp); kisApp->hideSplashScreen(); //dbgUI <<"asf=" << asf; // ## TODO compare timestamps ? int res = QMessageBox::warning(0, i18nc("@title:window", "Krita"), i18n("An autosaved file exists for this document.\nDo you want to open the autosaved file instead?"), QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::Yes); switch (res) { case QMessageBox::Yes : url.setPath(asf); autosaveOpened = true; break; case QMessageBox::No : QFile::remove(asf); break; default: // Cancel return false; } } } bool ret = openUrlInternal(url); if (autosaveOpened || flags & RecoveryFile) { setReadWrite(true); // enable save button setModified(true); setRecovered(true); } else { if (!(flags & DontAddToRecent)) { KisPart::instance()->addRecentURLToAllMainWindows(_url); } if (ret) { // Detect readonly local-files; remote files are assumed to be writable QFileInfo fi(url.toLocalFile()); setReadWrite(fi.isWritable()); } setRecovered(false); } return ret; } class DlgLoadMessages : public KoDialog { public: DlgLoadMessages(const QString &title, const QString &message, const QStringList &warnings) { setWindowTitle(title); setWindowIcon(KisIconUtils::loadIcon("warning")); QWidget *page = new QWidget(this); QVBoxLayout *layout = new QVBoxLayout(page); QHBoxLayout *hlayout = new QHBoxLayout(); QLabel *labelWarning= new QLabel(); labelWarning->setPixmap(KisIconUtils::loadIcon("warning").pixmap(32, 32)); hlayout->addWidget(labelWarning); hlayout->addWidget(new QLabel(message)); layout->addLayout(hlayout); QTextBrowser *browser = new QTextBrowser(); QString warning = "

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

"; } else { warning += " Reasons:

"; } warning += "

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