diff --git a/3rdparty/CMakeLists.txt b/3rdparty/CMakeLists.txt index 6994345313..07777a2ff2 100644 --- a/3rdparty/CMakeLists.txt +++ b/3rdparty/CMakeLists.txt @@ -1,225 +1,229 @@ 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() 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 () endif () endif (MINGW) if (WIN32) option(USE_QT_TABLET_WINDOWS "Do not use Krita's forked Wintab and Windows Ink support on Windows, but leave everything to Qt." ON) endif () 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 () else () message(WARNING "Linker Security Flags not enabled!") endif () endif () if (DEFINED EP_PREFIX) set_directory_properties(PROPERTIES EP_PREFIX ${EP_PREFIX}) endif () if (MSVC) message(FATAL_ERROR "Krita cannot be built with MSVC. See the README.md file!") 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 (ANDROID) set (GLOBAL_PROFILE ${GLOBAL_PROFILE} -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE} -DANDROID_PLATFORM=${ANDROID_PLATFORM} -DANDROID_ABI=${ANDROID_ABI}) 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 () 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() 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 () else (PYTHONLIBS_FOUND AND PYTHONINTERP_FOUND) message(FATAL_ERROR "Python requirements not met. To disable Python deps, set ENABLE_PYTHON_DEPS to OFF.") endif () endif () endif () # this list must be dependency-ordered if (ENABLE_PYTHON_DEPS OR NOT MINGW) add_subdirectory( ext_python ) endif () if (MINGW) add_subdirectory( ext_patch ) add_subdirectory( ext_png2ico ) endif () add_subdirectory( ext_lzma ) 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_openssl ) add_subdirectory( ext_vc ) add_subdirectory( ext_gsl ) add_subdirectory( ext_fftw3 ) add_subdirectory( ext_ocio ) +add_subdirectory( ext_openjpeg ) +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 () if (MINGW) add_subdirectory( ext_drmingw ) # add_subdirectory( ext_ffmpeg ) endif () if (NOT APPLE) add_subdirectory( ext_gmic ) endif () if(UNIX) add_subdirectory( ext_pkgconfig ) add_subdirectory( ext_heif ) endif() add_subdirectory(ext_giflib) add_subdirectory(ext_quazip) diff --git a/3rdparty/README.md b/3rdparty/README.md index f72f2b94c2..25675c7f48 100644 --- a/3rdparty/README.md +++ b/3rdparty/README.md @@ -1,269 +1,269 @@ # 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 build-tools/windows/build.cmd or KDE's craft * you develop on OSX and aren't using the scripts in krita/packaging/osx or Homebrew * you want to build a generic, distro-agnostic version of Krita for Linux and aren't using the scripts in packaging/linux/appimage * you develop on Linux, but some dependencies aren't available for your distribution and aren't using the scripts in packaging/linux/appimage 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 or 3.10 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.3 (by mingw-builds): https://sourceforge.net/projects/mingw-w64/ * The Files can be found under "Toolchains targetting Win64/Win32"/"Personal Builds". * For threading, select posix. * For exceptions, select seh (64-bit) or dwarf (32-bit). * Install mingw to something like C:\mingw; the full path must not contain any spaces. * 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 a release of Python 3.6 (*not* 3.7 or any other versions): 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 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. If building for 32-bit target, you need the 32-bit release of Python. 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 a bare 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% 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 this 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). For other jobs, you might need to manually add a -- -j N option, where N is the number of jobs. - 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: On Windows: ``` cmake --build . --config RelWithDebInfo --target ext_patch cmake --build . --config RelWithDebInfo --target ext_png2ico ``` On OSX and Windows: ``` cmake --build . --config RelWithDebInfo --target ext_gettext cmake --build . --config RelWithDebInfo --target ext_openssl ``` 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 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 ``` 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 ``` 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 - ``` + cmake --build . --config RelWithDebInfo --target ext_openjpeg 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 ``` 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) ``` 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 include gmic-qt) ``` cmake --build . --config RelWithDebInfo --target ext_gmic ``` 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. 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 and OSX ``` cmake --build . --config RelWithDebInfo --target ext_kwindowsystem ``` ## Build Krita 1. Make a krita build directory: mkdir BUILDROOT/build 2. Enter the BUILDROOT/build 3. Configure the build: On Windows ``` 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 the build: 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 run the packaging/windows/package-complete.cmd script. 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_fftw3/fftw-3.2.2-20111221.diff b/3rdparty/ext_fftw3/fftw-3.2.2-20111221.diff deleted file mode 100644 index c9a7ae1f63..0000000000 --- a/3rdparty/ext_fftw3/fftw-3.2.2-20111221.diff +++ /dev/null @@ -1,2725 +0,0 @@ -diff -Nru fftw-3.2.2.orig/api/CMakeLists.txt fftw-3.2.2/api/CMakeLists.txt ---- fftw-3.2.2.orig/api/CMakeLists.txt 1970-01-01 01:00:00.000000000 +0100 -+++ fftw-3.2.2/api/CMakeLists.txt 2011-12-21 11:36:18.165869700 +0100 -@@ -0,0 +1,41 @@ -+set(api_SRCS apiplan.c configure.c execute-dft-c2r.c -+execute-dft-r2c.c execute-dft.c execute-r2r.c execute-split-dft-c2r.c -+execute-split-dft-r2c.c execute-split-dft.c execute.c -+export-wisdom-to-file.c export-wisdom-to-string.c export-wisdom.c -+f77api.c flops.c forget-wisdom.c import-system-wisdom.c -+import-wisdom-from-file.c import-wisdom-from-string.c import-wisdom.c -+malloc.c map-r2r-kind.c mapflags.c mkprinter-file.c mktensor-iodims.c -+mktensor-rowmajor.c plan-dft-1d.c plan-dft-2d.c plan-dft-3d.c -+plan-dft-c2r-1d.c plan-dft-c2r-2d.c plan-dft-c2r-3d.c plan-dft-c2r.c -+plan-dft-r2c-1d.c plan-dft-r2c-2d.c plan-dft-r2c-3d.c plan-dft-r2c.c -+plan-dft.c plan-guru-dft-c2r.c plan-guru-dft-r2c.c plan-guru-dft.c -+plan-guru-r2r.c plan-guru-split-dft-c2r.c plan-guru-split-dft-r2c.c -+plan-guru-split-dft.c plan-many-dft-c2r.c plan-many-dft-r2c.c -+plan-many-dft.c plan-many-r2r.c plan-r2r-1d.c plan-r2r-2d.c -+plan-r2r-3d.c plan-r2r.c print-plan.c rdft2-pad.c the-planner.c -+version.c plan-guru64-dft-c2r.c -+plan-guru64-dft-r2c.c plan-guru64-dft.c plan-guru64-r2r.c -+plan-guru64-split-dft-c2r.c plan-guru64-split-dft-r2c.c -+plan-guru64-split-dft.c mktensor-iodims64.c -+) -+ -+if(BUILD_ALL_STATIC) -+ add_library(api STATIC ${api_SRCS}) -+else(BUILD_ALL_STATIC) -+ prepend_prefix(api_SRCS) -+ set(fftw_SRCS ${fftw_SRCS} ${api_SRCS} PARENT_SCOPE) -+endif(BUILD_ALL_STATIC) -+ -+ -+#BUILT_SOURCES = fftw3.f -+ -+#if MAINTAINER_MODE -+# -+# convert constants to F77 PARAMETER statements -+#fftw3.f: -+# rm -f $@ -+# perl -pe 's/([A-Z0-9_]+)=([0-9]+)/\n INTEGER \1\n PARAMETER (\1=\2)\n/g' $< |egrep 'PARAMETER|INTEGER' > $@ -+# perl -pe 's/#define +([A-Z0-9_]+) +\(([+-]?[0-9]+)U?\)/\n INTEGER \1\n PARAMETER (\1=\2)\n/g' $< |egrep 'PARAMETER|INTEGER' >> $@ -+# perl -pe 'if (/#define +([A-Z0-9_]+) +\(([0-9]+)U? *<< *([0-9]+)\)/) { print "\n INTEGER $$1\n PARAMETER ($$1=",$$2 << $$3,")\n"; }' $< |egrep 'PARAMETER|INTEGER' >> $@ -+# -+#endif # MAINTAINER_MODE -diff -Nru fftw-3.2.2.orig/CheckMingwVersion.cmake fftw-3.2.2/CheckMingwVersion.cmake ---- fftw-3.2.2.orig/CheckMingwVersion.cmake 1970-01-01 01:00:00.000000000 +0100 -+++ fftw-3.2.2/CheckMingwVersion.cmake 2011-12-21 11:36:18.167869900 +0100 -@@ -0,0 +1,26 @@ -+# -+# check mingw compiler version -+# -+# Copyright (c) 2010, Ralf Habacker -+# -+# Redistribution and use is allowed according to the terms of the BSD license. -+# -+if (NOT MINGW32 AND NOT MINGW64) -+ exec_program( -+ ${CMAKE_C_COMPILER} -+ ARGS ${CMAKE_C_COMPILER_ARG1} -dumpmachine -+ OUTPUT_VARIABLE _machine -+ ) -+ if (_machine STREQUAL mingw32) -+ set (MINGW32 1) -+ message(STATUS "found mingw 32 bit compiler") -+ elseif (_machine STREQUAL i686-w64-mingw32) -+ set (MINGW32 1) -+ set (MINGW_W32 1) -+ message(STATUS "found mingw 64 bit compiler") -+ elseif (_machine STREQUAL x86_64-w64-mingw32) -+ set (MINGW64 1) -+ set (MINGW_W64 1) -+ message(STATUS "found mingw 64 bit compiler") -+ endif (_machine STREQUAL mingw32) -+endif (NOT MINGW32 AND NOT MINGW64) -diff -Nru fftw-3.2.2.orig/CMakeLists.txt fftw-3.2.2/CMakeLists.txt ---- fftw-3.2.2.orig/CMakeLists.txt 1970-01-01 01:00:00.000000000 +0100 -+++ fftw-3.2.2/CMakeLists.txt 2011-12-21 12:21:50.225789400 +0100 -@@ -0,0 +1,123 @@ -+project(libfftw) -+ -+cmake_minimum_required(VERSION 2.6) -+ -+include(${CMAKE_SOURCE_DIR}/CheckMingwVersion.cmake) -+ -+option(DISABLE_FORTRAN "disable fortran wrappers" ON) -+option(FFTW_SINGLE "compile in single precision" OFF) -+option(FFTW_DOUBLE "compile in double precision" ON) -+option(FFTW_LDOUBLE "compile in long double precision" OFF) -+option(ENABLE_OPENMP "compile in openmp extensions" OFF) -+option(BUILD_ALL_STATIC "build each and every library as a small static library" OFF) -+option(BUILD_STATIC "build one static library" OFF) -+option(BUILD_BENCHMARKS "build benchmarks" ON) -+ -+add_definitions(-DHAVE_CONFIG_H) -+if(MSVC) -+ add_definitions(-D_CRT_SECURE_NO_WARNINGS -wd4700) -+endif(MSVC) -+ -+if(FFTW_SINGLE) -+ set(FFTW_DOUBLE OFF) -+ set(FFTW_LDOUBLE OFF) -+elseif(FFTW_DOUBLE) -+ set(FFTW_LDOUBLE OFF) -+endif(FFTW_SINGLE) -+ -+if(MINGW32) -+ add_definitions(-march=pentium4) -+endif(MINGW32) -+ -+if(ENABLE_OPENMP) -+ add_definitions(-openmp) -+endif(ENABLE_OPENMP) -+ -+set(PACKAGE "\"fftw\"") -+set(VERSION "\"3.2.2\"") -+set(PACKAGE_VERSION "${VERSION}") -+set(FFTW_CC "\"${CMAKE_C_COMPILER}\"") -+set(CODELET_OPTIM "\"\"") -+ -+set(fftw_SRCS) -+ -+include(ConfigureChecks.cmake) -+ -+macro(prepend_prefix outList) -+ FILE(RELATIVE_PATH prefix ${CMAKE_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}) -+ foreach(element ${${outList}}) -+ set(_outList ${_outList} ${prefix}/${element}) -+ endforeach(element ${${outList}}) -+ set(${outList} ${_outList}) -+endmacro(prepend_prefix outList) -+ -+include_directories( -+ ${CMAKE_CURRENT_SOURCE_DIR} -+ ${CMAKE_CURRENT_BINARY_DIR} -+ ${CMAKE_CURRENT_SOURCE_DIR}/kernel -+ ${CMAKE_CURRENT_SOURCE_DIR}/simd -+ ${CMAKE_CURRENT_SOURCE_DIR}/dft -+ ${CMAKE_CURRENT_SOURCE_DIR}/dft/scalar -+ ${CMAKE_CURRENT_SOURCE_DIR}/dft/scalar/codelets -+ ${CMAKE_CURRENT_BINARY_DIR}/dft/scalar/codelets -+ ${CMAKE_CURRENT_SOURCE_DIR}/dft/simd -+ ${CMAKE_CURRENT_SOURCE_DIR}/dft/simd/codelets -+ ${CMAKE_CURRENT_BINARY_DIR}/dft/simd/codelets -+ ${CMAKE_CURRENT_SOURCE_DIR}/rdft -+ ${CMAKE_CURRENT_SOURCE_DIR}/rdft/scalar -+ ${CMAKE_CURRENT_SOURCE_DIR}/rdft/scalar/r2cb -+ ${CMAKE_CURRENT_BINARY_DIR}/rdft/scalar/r2cb -+ ${CMAKE_CURRENT_SOURCE_DIR}/rdft/scalar/r2cf -+ ${CMAKE_CURRENT_BINARY_DIR}/rdft/scalar/r2cf -+ ${CMAKE_CURRENT_SOURCE_DIR}/rdft/scalar/r2r -+ ${CMAKE_CURRENT_BINARY_DIR}/rdft/scalar/r2r -+ ${CMAKE_CURRENT_SOURCE_DIR}/rdft/simd -+ ${CMAKE_CURRENT_SOURCE_DIR}/rdft/simd/codelets -+ ${CMAKE_CURRENT_BINARY_DIR}/rdft/simd/codelets -+ ${CMAKE_CURRENT_SOURCE_DIR}/reodft -+ ${CMAKE_CURRENT_SOURCE_DIR}/api -+ ${CMAKE_CURRENT_SOURCE_DIR}/threads -+ ${CMAKE_CURRENT_SOURCE_DIR}/cell -+ ${CMAKE_CURRENT_SOURCE_DIR}/libbench2 -+ ${CMAKE_CURRENT_SOURCE_DIR}/tests -+ ${CMAKE_CURRENT_SOURCE_DIR}/tools -+) -+add_subdirectory(kernel) -+add_subdirectory(simd) -+add_subdirectory(dft) -+add_subdirectory(rdft) -+add_subdirectory(reodft) -+add_subdirectory(api) -+add_subdirectory(threads) -+ -+if(NOT BUILD_ALL_STATIC) -+ if(NOT BUILD_STATIC) -+ set(_shared SHARED) -+ set(fftw_SRCS ${fftw_SRCS} fftw.def) -+ else(NOT BUILD_STATIC) -+ set(_shared STATIC) -+ endif(NOT BUILD_STATIC) -+ -+ if(FFTW_SINGLE) -+ set(FFTW_OUTPUT_NAME fftw3f-3) -+ elseif(FFTW_DOUBLE) -+ set(FFTW_OUTPUT_NAME fftw3-3) -+ elseif(FFTW_LDOUBLE) -+ set(FFTW_OUTPUT_NAME fftw3l-3) -+ endif(FFTW_SINGLE) -+ -+ add_library(fftw ${_shared} ${fftw_SRCS}) -+ set_target_properties(fftw PROPERTIES DEFINE_SYMBOL DLL_EXPORT -+ OUTPUT_NAME ${FFTW_OUTPUT_NAME}) -+ install(TARGETS fftw RUNTIME DESTINATION bin -+ LIBRARY DESTINATION lib -+ ARCHIVE DESTINATION lib) -+ -+ install(FILES api/fftw3.h api/fftw3.f DESTINATION include) -+endif(NOT BUILD_ALL_STATIC) -+ -+if(BUILD_BENCHMARKS) -+ add_subdirectory(libbench2) -+ add_subdirectory(tests) -+endif(BUILD_BENCHMARKS) -+#add_subdirectory(tools) -diff -Nru fftw-3.2.2.orig/config.h.cmake fftw-3.2.2/config.h.cmake ---- fftw-3.2.2.orig/config.h.cmake 1970-01-01 01:00:00.000000000 +0100 -+++ fftw-3.2.2/config.h.cmake 2011-12-21 11:36:18.172870400 +0100 -@@ -0,0 +1,380 @@ -+/* config.h.in. Generated from configure.ac by autoheader. */ -+ -+/* Define to compile in long-double precision. */ -+#cmakedefine BENCHFFT_LDOUBLE -+ -+/* Define to compile in single precision. */ -+#cmakedefine BENCHFFT_SINGLE -+ -+/* extra CFLAGS for codelets */ -+#cmakedefine CODELET_OPTIM @CODELET_OPTIM@ -+ -+/* Define to one of `_getb67', `GETB67', `getb67' for Cray-2 and Cray-YMP -+ systems. This function is required for `alloca.c' support on those systems. -+ */ -+#cmakedefine CRAY_STACKSEG_END -+ -+/* Define to 1 if using `alloca.c'. */ -+#cmakedefine C_ALLOCA -+ -+/* Define to disable Fortran wrappers. */ -+#cmakedefine DISABLE_FORTRAN -+ -+/* Define to dummy `main' function (if any) required to link to the Fortran -+ libraries. */ -+#cmakedefine F77_DUMMY_MAIN -+ -+/* Define to a macro mangling the given C identifier (in lower and upper -+ case), which must not contain underscores, for linking with Fortran. */ -+#cmakedefine F77_FUNC -+ -+/* As F77_FUNC, but for C identifiers containing underscores. */ -+#cmakedefine F77_FUNC_ -+ -+/* Define if F77_FUNC and F77_FUNC_ are equivalent. */ -+#cmakedefine F77_FUNC_EQUIV -+ -+/* Define if F77 and FC dummy `main' functions are identical. */ -+#cmakedefine FC_DUMMY_MAIN_EQ_F77 -+ -+/* C compiler name and flags */ -+#cmakedefine FFTW_CC @FFTW_CC@ -+ -+/* Define to enable extra FFTW debugging code. */ -+#cmakedefine FFTW_DEBUG -+ -+/* Define to enable alignment debugging hacks. */ -+#cmakedefine FFTW_DEBUG_ALIGNMENT -+ -+/* Define to enable debugging malloc. */ -+#cmakedefine FFTW_DEBUG_MALLOC -+ -+/* Define to enable the use of alloca(). */ -+#cmakedefine FFTW_ENABLE_ALLOCA -+ -+/* Define to compile in long-double precision. */ -+#cmakedefine FFTW_LDOUBLE -+ -+/* Define to compile in single precision. */ -+#cmakedefine FFTW_SINGLE -+ -+/* Define to 1 if you have the `abort' function. */ -+#cmakedefine HAVE_ABORT @HAVE_ABORT@ -+ -+/* Define to 1 if you have `alloca', as a function or macro. */ -+#cmakedefine HAVE_ALLOCA @HAVE_ALLOCA@ -+ -+/* Define to 1 if you have and it should be used (not on Ultrix). -+ */ -+#cmakedefine HAVE_ALLOCA_H @HAVE_ALLOCA_H@ -+ -+/* Define to enable Altivec optimizations. */ -+#cmakedefine HAVE_ALTIVEC @HAVE_ALTIVEC@ -+ -+/* Define to 1 if you have the header file. */ -+#cmakedefine HAVE_ALTIVEC_H @HAVE_ALTIVEC_H@ -+ -+/* Define to 1 if you have the `BSDgettimeofday' function. */ -+#cmakedefine HAVE_BSDGETTIMEOFDAY @HAVE_BSDGETTIMEOFDAY@ -+ -+/* Define to enable optimizations for the Cell Broadband Engine */ -+#cmakedefine HAVE_CELL @HAVE_CELL@ -+ -+/* Define to 1 if you have the `clock_gettime' function. */ -+#cmakedefine HAVE_CLOCK_GETTIME @HAVE_CLOCK_GETTIME@ -+ -+/* Define to 1 if you have the `cosl' function. */ -+#cmakedefine HAVE_COSL @HAVE_COSL@ -+ -+/* Define to 1 if you have the header file. */ -+#cmakedefine HAVE_C_ASM_H @HAVE_C_ASM_H@ -+ -+/* Define to 1 if you have the declaration of `cosl', and to 0 if you don't. -+ */ -+#cmakedefine HAVE_DECL_COSL @HAVE_DECL_COSL@ -+ -+/* Define to 1 if you have the declaration of `drand48', and to 0 if you -+ don't. */ -+#cmakedefine HAVE_DECL_DRAND48 @HAVE_DECL_DRAND48@ -+ -+/* Define to 1 if you have the declaration of `memalign', and to 0 if you -+ don't. */ -+#cmakedefine HAVE_DECL_MEMALIGN @HAVE_DECL_MEMALIGN@ -+ -+/* Define to 1 if you have the declaration of `posix_memalign', and to 0 if -+ you don't. */ -+#cmakedefine HAVE_DECL_POSIX_MEMALIGN @HAVE_DECL_POSIX_MEMALIGN@ -+ -+/* Define to 1 if you have the declaration of `sinl', and to 0 if you don't. -+ */ -+#cmakedefine HAVE_DECL_SINL @HAVE_DECL_SINL@ -+ -+/* Define to 1 if you have the header file. */ -+#cmakedefine HAVE_DLFCN_H @HAVE_DLFCN_H@ -+ -+/* Define to 1 if you don't have `vprintf' but do have `_doprnt.' */ -+#cmakedefine HAVE_DOPRNT @HAVE_DOPRNT@ -+ -+/* Define to 1 if you have the `drand48' function. */ -+#cmakedefine HAVE_DRAND48 @HAVE_DRAND48@ -+ -+/* Define if you have a machine with fused multiply-add */ -+#cmakedefine HAVE_FMA @HAVE_FMA@ -+ -+/* Define to 1 if you have the `gethrtime' function. */ -+#cmakedefine HAVE_GETHRTIME @HAVE_GETHRTIME@ -+ -+/* Define to 1 if you have the `gettimeofday' function. */ -+#cmakedefine HAVE_GETTIMEOFDAY @HAVE_GETTIMEOFDAY@ -+ -+/* Define to 1 if hrtime_t is defined in */ -+#cmakedefine HAVE_HRTIME_T @HAVE_HRTIME_T@ -+ -+/* Define to 1 if you have the header file. */ -+#cmakedefine HAVE_INTRINSICS_H @HAVE_INTRINSICS_H@ -+ -+/* Define to 1 if you have the header file. */ -+#cmakedefine HAVE_INTTYPES_H @HAVE_INTTYPES_H@ -+ -+/* Define if the isnan() function/macro is available. */ -+#cmakedefine HAVE_ISNAN @HAVE_ISNAN@ -+ -+/* Define to 1 if you have the header file. */ -+#cmakedefine HAVE_LIBINTL_H @HAVE_LIBINTL_H@ -+ -+/* Define to 1 if you have the `m' library (-lm). */ -+#cmakedefine HAVE_LIBM @HAVE_LIBM@ -+ -+/* Define to 1 if you have the `spe' library (-lspe). */ -+#cmakedefine HAVE_LIBSPE @HAVE_LIBSPE@ -+ -+/* Define to 1 if you have the `spe2' library (-lspe2). */ -+#cmakedefine HAVE_LIBSPE2 @HAVE_LIBSPE2@ -+ -+/* Define to 1 if you have the header file. */ -+#cmakedefine HAVE_LIMITS_H @HAVE_LIMITS_H@ -+ -+/* Define to 1 if the compiler supports `long double' */ -+#cmakedefine HAVE_LONG_DOUBLE @HAVE_LONG_DOUBLE@ -+ -+/* Define to 1 if you have the `mach_absolute_time' function. */ -+#cmakedefine HAVE_MACH_ABSOLUTE_TIME @HAVE_MACH_ABSOLUTE_TIME@ -+ -+/* Define to 1 if you have the header file. */ -+#cmakedefine HAVE_MACH_MACH_TIME_H @HAVE_MACH_MACH_TIME_H@ -+ -+/* Define to 1 if you have the header file. */ -+#cmakedefine HAVE_MALLOC_H @HAVE_MALLOC_H@ -+ -+/* Define to 1 if you have the `memalign' function. */ -+#cmakedefine HAVE_MEMALIGN @HAVE_MEMALIGN@ -+ -+/* Define to 1 if you have the header file. */ -+#cmakedefine HAVE_MEMORY_H @HAVE_MEMORY_H@ -+ -+/* Define to 1 if you have the `memset' function. */ -+#cmakedefine HAVE_MEMSET @HAVE_MEMSET@ -+ -+/* Define to enable MIPS paired-single optimizations. */ -+#cmakedefine HAVE_MIPS_PS @HAVE_MIPS_PS@ -+ -+/* Define to enable use of MIPS ZBus cycle-counter. */ -+#cmakedefine HAVE_MIPS_ZBUS_TIMER @HAVE_MIPS_ZBUS_TIMER@ -+ -+/* Define if you have the MPI library. */ -+#cmakedefine HAVE_MPI @HAVE_MPI@ -+ -+/* Define to enable OpenMP */ -+#cmakedefine HAVE_OPENMP @HAVE_OPENMP@ -+ -+/* Define to 1 if you have the `posix_memalign' function. */ -+#cmakedefine HAVE_POSIX_MEMALIGN @HAVE_POSIX_MEMALIGN@ -+ -+/* Define if you have POSIX threads libraries and header files. */ -+#cmakedefine HAVE_PTHREAD @HAVE_PTHREAD@ -+ -+/* Define to 1 if you have the `read_real_time' function. */ -+#cmakedefine HAVE_READ_REAL_TIME @HAVE_READ_REAL_TIME@ -+ -+/* Define to 1 if you have the `sinl' function. */ -+#cmakedefine HAVE_SINL @HAVE_SINL@ -+ -+/* Define to 1 if you have the `snprintf' function. */ -+#cmakedefine HAVE_SNPRINTF @HAVE_SNPRINTF@ -+ -+/* Define to 1 if you have the `sqrt' function. */ -+#cmakedefine HAVE_SQRT @HAVE_SQRT@ -+ -+/* Define to enable SSE optimizations. */ -+#cmakedefine HAVE_SSE @HAVE_SSE@ -+ -+/* Define to enable SSE2 optimizations. */ -+#cmakedefine HAVE_SSE2 @HAVE_SSE2@ -+ -+/* Define to 1 if you have the header file. */ -+#cmakedefine HAVE_STDDEF_H @HAVE_STDDEF_H@ -+ -+/* Define to 1 if you have the header file. */ -+#cmakedefine HAVE_STDINT_H @HAVE_STDINT_H@ -+ -+/* Define to 1 if you have the header file. */ -+#cmakedefine HAVE_STDLIB_H @HAVE_STDLIB_H@ -+ -+/* Define to 1 if you have the header file. */ -+#cmakedefine HAVE_STRINGS_H @HAVE_STRINGS_H@ -+ -+/* Define to 1 if you have the header file. */ -+#cmakedefine HAVE_STRING_H @HAVE_STRING_H@ -+ -+/* Define to 1 if you have the `sysctl' function. */ -+#cmakedefine HAVE_SYSCTL @HAVE_SYSCTL@ -+ -+/* Define to 1 if you have the header file. */ -+#cmakedefine HAVE_SYS_STAT_H @HAVE_SYS_STAT_H@ -+ -+/* Define to 1 if you have the header file. */ -+#cmakedefine HAVE_SYS_SYSCTL_H @HAVE_SYS_SYSCTL_H@ -+ -+/* Define to 1 if you have the header file. */ -+#cmakedefine HAVE_SYS_TIME_H @HAVE_SYS_TIME_H@ -+ -+/* Define to 1 if you have the header file. */ -+#cmakedefine HAVE_SYS_TYPES_H @HAVE_SYS_TYPES_H@ -+ -+/* Define to 1 if you have the `tanl' function. */ -+#cmakedefine HAVE_TANL @HAVE_TANL@ -+ -+/* Define if we have a threads library. */ -+#cmakedefine HAVE_THREADS @HAVE_THREADS@ -+ -+/* Define to 1 if you have the `time_base_to_time' function. */ -+#cmakedefine HAVE_TIME_BASE_TO_TIME @HAVE_TIME_BASE_TO_TIME@ -+ -+/* Define to 1 if the system has the type `uintptr_t'. */ -+#cmakedefine HAVE_UINTPTR_T @HAVE_UINTPTR_T@ -+ -+/* Define to 1 if you have the header file. */ -+#cmakedefine HAVE_UNISTD_H @HAVE_UNISTD_H@ -+ -+/* Define to 1 if you have the `vprintf' function. */ -+#cmakedefine HAVE_VPRINTF @HAVE_VPRINTF@ -+ -+/* Define to 1 if you have the header file. */ -+#cmakedefine HAVE_XMMINTRIN_H @HAVE_XMMINTRIN_H@ -+ -+/* Define to 1 if you have the `_mm_free' function. */ -+#cmakedefine HAVE__MM_FREE @HAVE__MM_FREE@ -+ -+/* Define to 1 if you have the `_mm_malloc' function. */ -+#cmakedefine HAVE__MM_MALLOC @HAVE__MM_MALLOC@ -+ -+/* Define if you have the UNICOS _rtc() intrinsic. */ -+#cmakedefine HAVE__RTC @HAVE__RTC@ -+ -+/* Define to the sub-directory in which libtool stores uninstalled libraries. -+ */ -+#cmakedefine LT_OBJDIR -+ -+/* Name of package */ -+#cmakedefine PACKAGE @PACKAGE@ -+ -+/* Define to the address where bug reports for this package should be sent. */ -+#cmakedefine PACKAGE_BUGREPORT -+ -+/* Define to the full name of this package. */ -+#cmakedefine PACKAGE_NAME -+ -+/* Define to the full name and version of this package. */ -+#cmakedefine PACKAGE_STRING -+ -+/* Define to the one symbol short name of this package. */ -+#cmakedefine PACKAGE_TARNAME -+ -+/* Define to the version of this package. */ -+#cmakedefine PACKAGE_VERSION @PACKAGE_VERSION@ -+ -+/* Define to necessary symbol if this constant uses a non-standard name on -+ your system. */ -+#cmakedefine PTHREAD_CREATE_JOINABLE -+ -+/* The size of `double', as computed by sizeof. */ -+#cmakedefine SIZEOF_DOUBLE @SIZEOF_DOUBLE@ -+ -+/* The size of `float', as computed by sizeof. */ -+#cmakedefine SIZEOF_FLOAT @SIZEOF_FLOAT@ -+ -+/* The size of `int', as computed by sizeof. */ -+#cmakedefine SIZEOF_INT @SIZEOF_INT@ -+ -+/* The size of `long', as computed by sizeof. */ -+#cmakedefine SIZEOF_LONG @SIZEOF_LONG@ -+ -+/* The size of `long long', as computed by sizeof. */ -+#cmakedefine SIZEOF_LONG_LONG @SIZEOF_LONG_LONG@ -+ -+/* The size of `ptrdiff_t', as computed by sizeof. */ -+#cmakedefine SIZEOF_PTRDIFF_T @SIZEOF_PTRDIFF_T@ -+ -+/* The size of `size_t', as computed by sizeof. */ -+#cmakedefine SIZEOF_SIZE_T @SIZEOF_SIZE_T@ -+ -+/* The size of `unsigned int', as computed by sizeof. */ -+#cmakedefine SIZEOF_UNSIGNED_INT @SIZEOF_UNSIGNED_INT@ -+ -+/* The size of `unsigned long', as computed by sizeof. */ -+#cmakedefine SIZEOF_UNSIGNED_LONG @SIZEOF_UNSIGNED_LONG@ -+ -+/* The size of `unsigned long long', as computed by sizeof. */ -+#cmakedefine SIZEOF_UNSIGNED_LONG_LONG @SIZEOF_UNSIGNED_LONG_LONG@ -+ -+/* The size of `void *', as computed by sizeof. */ -+#cmakedefine SIZEOF_VOID_P @SIZEOF_VOID_P@ -+ -+/* If using the C implementation of alloca, define if you know the -+ direction of stack growth for your system; otherwise it will be -+ automatically deduced at runtime. -+ STACK_DIRECTION > 0 => grows toward higher addresses -+ STACK_DIRECTION < 0 => grows toward lower addresses -+ STACK_DIRECTION = 0 => direction of growth unknown */ -+#cmakedefine STACK_DIRECTION -+ -+/* Define to 1 if you have the ANSI C header files. */ -+#cmakedefine STDC_HEADERS -+ -+/* Define to 1 if you can safely include both and . */ -+#cmakedefine TIME_WITH_SYS_TIME -+ -+/* Define if we have and are using OpenMP multithreading directives */ -+#cmakedefine USING_OPENMP_THREADS -+ -+/* Define if we have and are using POSIX threads. */ -+#cmakedefine USING_POSIX_THREADS -+ -+/* Version number of package */ -+#cmakedefine VERSION @VERSION@ -+ -+/* Use common Windows Fortran mangling styles for the Fortran interfaces. */ -+#cmakedefine WINDOWS_F77_MANGLING -+ -+/* Include g77-compatible wrappers in addition to any other Fortran wrappers. -+ */ -+#cmakedefine WITH_G77_WRAPPERS -+ -+/* Use our own 16-byte aligned malloc routine; mainly helpful for Windows -+ systems lacking aligned allocation system-library routines. */ -+#cmakedefine WITH_OUR_MALLOC16 -+ -+/* Use low-precision timers, making planner very slow */ -+#cmakedefine WITH_SLOW_TIMER -+ -+/* Define to empty if `const' does not conform to ANSI C. */ -+#cmakedefine const -+ -+/* Define to `__inline__' or `__inline' if that's what the C compiler -+ calls it, or to nothing if 'inline' is not supported under any name. */ -+#ifndef __cplusplus -+#cmakedefine inline @inline@ -+#endif -+ -+/* Define to `unsigned int' if does not define. */ -+#cmakedefine size_t -diff -Nru fftw-3.2.2.orig/ConfigureChecks.cmake fftw-3.2.2/ConfigureChecks.cmake ---- fftw-3.2.2.orig/ConfigureChecks.cmake 1970-01-01 01:00:00.000000000 +0100 -+++ fftw-3.2.2/ConfigureChecks.cmake 2011-12-21 12:05:01.943536000 +0100 -@@ -0,0 +1,273 @@ -+if(MSVC) -+ set(inline __inline) -+endif(MSVC) -+set(HAVE_THREADS 1) -+set(WITH_OUR_MALLOC16 1) -+set(STDC_HEADERS 1) -+set(HAVE_UINTPTR_T 1) -+set(HAVE_UINTPTR_T 1) -+set(HAVE_SIMD 1) -+set(HAVE_LONG_DOUBLE 1) -+set(HAVE_OPENMP ${ENABLE_OPENMP}) -+ -+if(FFTW_SINGLE) -+ set(HAVE_SSE 1) -+elseif(FFTW_DOUBLE) -+ set(HAVE_SSE2 1) # only works in double precision -+endif(FFTW_SINGLE) -+ -+if(FFTW_SINGLE) -+ set(FFTW_SYMBOL_PREFIX "fftwf_") -+elseif(FFTW_DOUBLE) -+ set(FFTW_SYMBOL_PREFIX "fftw_") -+elseif(FFTW_LDOUBLE) -+ set(FFTW_SYMBOL_PREFIX "fftwl_") -+endif(FFTW_SINGLE) -+ -+if(BUILD_BENCHMARKS) -+ set(BENCHFFT_SINGLE ${FFTW_SINGLE}) -+ set(BENCHFFT_DOUBLE ${FFTW_DOUBLE}) -+ set(BENCHFFT_LDOUBLE ${FFTW_LDOUBLE}) -+endif(BUILD_BENCHMARKS) -+#/* extra CFLAGS for codelets */ -+#cmakedefine CODELET_OPTIM @CODELET_OPTIM@ -+ -+include(CheckIncludeFiles) -+include(CheckSymbolExists) -+include(CheckTypeSize) -+ -+check_include_files(alloca.h HAVE_ALLOCA_H) -+check_include_files(stdlib.h HAVE_STDLIB_H) -+check_include_files(stdint.h HAVE_STDINT_H) -+check_include_files(stddef.h HAVE_STDDEF_H) -+check_include_files(altivec.h HAVE_ALTIVEC_H) -+check_include_files(c_asm.h HAVE_C_ASM_H) -+check_include_files(dlfcn.h HAVE_DLFCN_H) -+check_include_files(intrinsics.h HAVE_INTRINSICS_H) -+check_include_files(inttypes.h HAVE_INTTYPES_H) -+check_include_files(libintl.h HAVE_LIBINTL_H) -+check_include_files(limits.h HAVE_LIMITS_H) -+check_include_files(mach/mach_time.h HAVE_MACH_MACH_TIME_H) -+check_include_files(malloc.h HAVE_MALLOC_H) -+check_include_files(memory.h HAVE_MEMORY_H) -+check_include_files(stddef.h HAVE_STDDEF_H) -+check_include_files(stdint.h HAVE_STDINT_H) -+check_include_files(stdlib.h HAVE_STDLIB_H) -+check_include_files(strings.h HAVE_STRINGS_H) -+check_include_files(string.h HAVE_STRING_H) -+check_include_files(sys/stat.h HAVE_SYS_STAT_H) -+check_include_files(sys/sysctl.h HAVE_SYS_SYSCTL_H) -+check_include_files(sys/time.h HAVE_SYS_TIME_H) -+check_include_files(sys/types.h HAVE_SYS_TYPES_H) -+check_include_files(unistd.h HAVE_UNISTD_H) -+check_include_files(xmmintrin.h HAVE_XMMINTRIN_H) -+ -+check_type_size("double" SIZEOF_DOUBLE) -+check_type_size("float" SIZEOF_FLOAT) -+check_type_size("int" SIZEOF_INT) -+check_type_size("long" SIZEOF_LONG) -+check_type_size("long long" SIZEOF_LONG_LONG) -+check_type_size("ptrdiff_t" SIZEOF_PTRDIFF_T) -+check_type_size("size_t" SIZEOF_SIZE_T) -+check_type_size("unsigned int" SIZEOF_UNSIGNED_INT) -+check_type_size("unsigned long" SIZEOF_UNSIGNED_LONG) -+check_type_size("unsigned long long" SIZEOF_UNSIGNED_LONG_LONG) -+check_type_size("void*" SIZEOF_VOID_P) -+ -+###################################################################################### -+ -+#/* Define to enable extra FFTW debugging code. */ -+#cmakedefine FFTW_DEBUG -+ -+#/* Define to enable alignment debugging hacks. */ -+#cmakedefine FFTW_DEBUG_ALIGNMENT -+ -+#/* Define to enable debugging malloc. */ -+#cmakedefine FFTW_DEBUG_MALLOC -+ -+ -+#/* Define to 1 if using `alloca.c'. */ -+#cmakedefine C_ALLOCA -+ -+#/* Define to enable the use of alloca(). */ -+#cmakedefine FFTW_ENABLE_ALLOCA -+ -+#/* Define to 1 if you have `alloca', as a function or macro. */ -+#cmakedefine HAVE_ALLOCA -+ -+#/* Define to 1 if you have the `abort' function. */ -+#cmakedefine HAVE_ABORT -+ -+#/* Define to enable Altivec optimizations. */ -+#cmakedefine HAVE_ALTIVEC -+ -+#/* Define to 1 if you have the `BSDgettimeofday' function. */ -+#cmakedefine HAVE_BSDGETTIMEOFDAY -+ -+#/* Define to enable optimizations for the Cell Broadband Engine */ -+#cmakedefine HAVE_CELL -+ -+#/* Define to 1 if you have the `clock_gettime' function. */ -+#cmakedefine HAVE_CLOCK_GETTIME -+ -+#/* Define to 1 if you have the `cosl' function. */ -+#cmakedefine HAVE_COSL -+ -+#/* Define to 1 if you have the declaration of `cosl', and to 0 if you don't. -+# */ -+#cmakedefine HAVE_DECL_COSL -+ -+#/* Define to 1 if you have the declaration of `drand48', and to 0 if you -+# don't. */ -+#cmakedefine HAVE_DECL_DRAND48 -+ -+#/* Define to 1 if you have the declaration of `memalign', and to 0 if you -+# don't. */ -+#cmakedefine HAVE_DECL_MEMALIGN -+ -+#/* Define to 1 if you have the declaration of `posix_memalign', and to 0 if -+# you don't. */ -+#cmakedefine HAVE_DECL_POSIX_MEMALIGN -+ -+#/* Define to 1 if you have the declaration of `sinl', and to 0 if you don't. -+# */ -+#cmakedefine HAVE_DECL_SINL -+ -+#/* Define to 1 if you don't have `vprintf' but do have `_doprnt.' */ -+#cmakedefine HAVE_DOPRNT -+ -+#/* Define to 1 if you have the `drand48' function. */ -+#cmakedefine HAVE_DRAND48 -+ -+#/* Define if you have a machine with fused multiply-add */ -+#cmakedefine HAVE_FMA -+ -+#/* Define to 1 if you have the `gethrtime' function. */ -+#cmakedefine HAVE_GETHRTIME -+ -+#/* Define to 1 if you have the `gettimeofday' function. */ -+#cmakedefine HAVE_GETTIMEOFDAY -+ -+#/* Define to 1 if hrtime_t is defined in */ -+#cmakedefine HAVE_HRTIME_T -+ -+#/* Define if the isnan() function/macro is available. */ -+#cmakedefine HAVE_ISNAN -+ -+#/* Define to 1 if you have the `m' library (-lm). */ -+#cmakedefine HAVE_LIBM -+ -+#/* Define to 1 if you have the `spe' library (-lspe). */ -+#cmakedefine HAVE_LIBSPE -+ -+#/* Define to 1 if you have the `spe2' library (-lspe2). */ -+#cmakedefine HAVE_LIBSPE2 -+ -+#/* Define to 1 if you have the `mach_absolute_time' function. */ -+#cmakedefine HAVE_MACH_ABSOLUTE_TIME -+ -+#/* Define to 1 if you have the `memalign' function. */ -+#cmakedefine HAVE_MEMALIGN -+ -+#/* Define to 1 if you have the `memset' function. */ -+#cmakedefine HAVE_MEMSET -+ -+#/* Define to enable MIPS paired-single optimizations. */ -+#cmakedefine HAVE_MIPS_PS -+ -+#/* Define to enable use of MIPS ZBus cycle-counter. */ -+#cmakedefine HAVE_MIPS_ZBUS_TIMER -+ -+#/* Define if you have the MPI library. */ -+#cmakedefine HAVE_MPI -+ -+#/* Define if we have and are using OpenMP multithreading directives */ -+#cmakedefine USING_OPENMP_THREADS -+ -+#/* Define to 1 if you have the `posix_memalign' function. */ -+#cmakedefine HAVE_POSIX_MEMALIGN -+ -+#/* Define if you have POSIX threads libraries and header files. */ -+#cmakedefine HAVE_PTHREAD -+ -+#/* Define to 1 if you have the `read_real_time' function. */ -+#cmakedefine HAVE_READ_REAL_TIME -+ -+#/* Define to 1 if you have the `sinl' function. */ -+#cmakedefine HAVE_SINL -+ -+#/* Define to 1 if you have the `snprintf' function. */ -+#cmakedefine HAVE_SNPRINTF -+ -+#/* Define to 1 if you have the `sqrt' function. */ -+#cmakedefine HAVE_SQRT -+ -+#/* Define to enable SSE optimizations. */ -+#cmakedefine HAVE_SSE @HAVE_SSE@ -+ -+#/* Define to enable SSE2 optimizations. */ -+#cmakedefine HAVE_SSE2 @HAVE_SSE2@ -+ -+#/* Define to 1 if you have the `sysctl' function. */ -+#cmakedefine HAVE_SYSCTL -+ -+#/* Define to 1 if you have the `tanl' function. */ -+#cmakedefine HAVE_TANL -+ -+#/* Define if we have a threads library. */ -+#cmakedefine HAVE_THREADS -+ -+#/* Define to 1 if you have the `time_base_to_time' function. */ -+#cmakedefine HAVE_TIME_BASE_TO_TIME -+ -+#/* Define to 1 if the system has the type `uintptr_t'. */ -+#cmakedefine HAVE_UINTPTR_T -+ -+#/* Define to 1 if you have the `vprintf' function. */ -+#cmakedefine HAVE_VPRINTF -+ -+#/* Define to 1 if you have the `_mm_free' function. */ -+#cmakedefine HAVE__MM_FREE -+ -+#/* Define to 1 if you have the `_mm_malloc' function. */ -+#cmakedefine HAVE__MM_MALLOC -+ -+#/* Define if you have the UNICOS _rtc() intrinsic. */ -+#cmakedefine HAVE__RTC -+ -+#/* Define to necessary symbol if this constant uses a non-standard name on -+# your system. */ -+#cmakedefine PTHREAD_CREATE_JOINABLE -+ -+#/* If using the C implementation of alloca, define if you know the -+# direction of stack growth for your system; otherwise it will be -+# automatically deduced at runtime. -+# STACK_DIRECTION > 0 => grows toward higher addresses -+# STACK_DIRECTION < 0 => grows toward lower addresses -+# STACK_DIRECTION = 0 => direction of growth unknown */ -+#cmakedefine STACK_DIRECTION -+ -+#/* Define to 1 if you can safely include both and . */ -+#cmakedefine TIME_WITH_SYS_TIME -+ -+########################################################################## -+# stuff I don't know what to do with: -+#/* Use low-precision timers, making planner very slow */ -+#cmakedefine WITH_SLOW_TIMER -+#/* Define if we have and are using POSIX threads. */ -+#cmakedefine USING_POSIX_THREADS -+ -+# Fortran stuff: not implemented -+#cmakedefine DISABLE_FORTRAN -+#cmakedefine F77_DUMMY_MAIN -+#cmakedefine F77_FUNC -+#cmakedefine F77_FUNC_ -+#cmakedefine F77_FUNC_EQUIV -+#cmakedefine FC_DUMMY_MAIN_EQ_F77 -+#cmakedefine WINDOWS_F77_MANGLING -+#cmakedefine WITH_G77_WRAPPERS -+ -+ -+ -+configure_file(config.h.cmake config.h) -+configure_file(fftw.def.cmake fftw.def) -diff -Nru fftw-3.2.2.orig/dft/CMakeLists.txt fftw-3.2.2/dft/CMakeLists.txt ---- fftw-3.2.2.orig/dft/CMakeLists.txt 1970-01-01 01:00:00.000000000 +0100 -+++ fftw-3.2.2/dft/CMakeLists.txt 2011-12-21 11:36:18.175870700 +0100 -@@ -0,0 +1,19 @@ -+#SUBDIRS = scalar simd -+add_subdirectory(scalar) -+add_subdirectory(simd) -+ -+# pkgincludedir = $(includedir)/fftw3@PREC_SUFFIX@ -+# pkginclude_HEADERS = codelet-dft.h dft.h -+ -+set(dft_SRCS bluestein.c buffered.c conf.c ct.c dftw-direct.c -+dftw-directsq.c dftw-generic.c dftw-genericbuf.c direct.c generic.c -+indirect.c indirect-transpose.c kdft-dif.c kdft-difsq.c kdft-dit.c -+kdft.c nop.c plan.c problem.c rader.c rank-geq2.c solve.c vrank-geq1.c -+zero.c) -+ -+if(BUILD_ALL_STATIC) -+ add_library(dft STATIC ${dft_SRCS}) -+else(BUILD_ALL_STATIC) -+ prepend_prefix(dft_SRCS) -+ set(fftw_SRCS ${fftw_SRCS} ${dft_SRCS} ${_fftw_SRCS} PARENT_SCOPE) -+endif(BUILD_ALL_STATIC) -diff -Nru fftw-3.2.2.orig/dft/scalar/CMakeLists.txt fftw-3.2.2/dft/scalar/CMakeLists.txt ---- fftw-3.2.2.orig/dft/scalar/CMakeLists.txt 1970-01-01 01:00:00.000000000 +0100 -+++ fftw-3.2.2/dft/scalar/CMakeLists.txt 2011-12-21 11:36:18.177870900 +0100 -@@ -0,0 +1,10 @@ -+add_subdirectory(codelets) -+ -+set(dft_scalar_SRCS ${dft_scalar_SRCS} n.c t.c) -+ -+if(BUILD_ALL_STATIC) -+ add_library(dft_scalar STATIC ${dft_scalar_SRCS}) -+else(BUILD_ALL_STATIC) -+ prepend_prefix(dft_scalar_SRCS) -+ set(_fftw_SRCS ${_fftw_SRCS} ${dft_scalar_SRCS} ${__fftw_SRCS} PARENT_SCOPE) -+endif(BUILD_ALL_STATIC) -diff -Nru fftw-3.2.2.orig/dft/scalar/codelets/CMakeLists.txt fftw-3.2.2/dft/scalar/codelets/CMakeLists.txt ---- fftw-3.2.2.orig/dft/scalar/codelets/CMakeLists.txt 1970-01-01 01:00:00.000000000 +0100 -+++ fftw-3.2.2/dft/scalar/codelets/CMakeLists.txt 2011-12-21 11:36:18.179871100 +0100 -@@ -0,0 +1,113 @@ -+# This Makefile.am specifies a set of codelets, efficient transforms -+# of small sizes, that are used as building blocks (kernels) by FFTW -+# to build up large transforms, as well as the options for generating -+# and compiling them. -+ -+# You can customize FFTW for special needs, e.g. to handle certain -+# sizes more efficiently, by adding new codelets to the lists of those -+# included by default. If you change the list of codelets, any new -+# ones you added will be automatically generated when you run the -+# bootstrap script (see "Generating your own code" in the FFTW -+# manual). -+ -+########################################################################### -+ -+########################################################################### -+# n1_ is a hard-coded FFT of size (base cases of FFT recursion) -+set(N1 n1_2.c n1_3.c n1_4.c n1_5.c n1_6.c n1_7.c n1_8.c n1_9.c n1_10.c -+n1_11.c n1_12.c n1_13.c n1_14.c n1_15.c n1_16.c n1_32.c n1_64.c -+n1_20.c n1_25.c # n1_30.c n1_40.c n1_50.c -+) -+ -+########################################################################### -+# t1_ is a "twiddle" FFT of size , implementing a radix-r DIT step -+set(T1 t1_2.c t1_3.c t1_4.c t1_5.c t1_6.c t1_7.c t1_8.c t1_9.c -+t1_10.c t1_12.c t1_15.c t1_16.c t1_32.c t1_64.c -+t1_20.c t1_25.c # t1_30.c t1_40.c t1_50.c -+) -+ -+# t2_ is also a twiddle FFT, but instead of using a complete lookup table -+# of trig. functions, it partially generates the trig. values on the fly -+# (this is faster for large sizes). -+set(T2 t2_4.c t2_8.c t2_16.c t2_32.c t2_64.c -+ t2_5.c t2_10.c t2_20.c t2_25.c -+) -+ -+########################################################################### -+# The F (DIF) codelets are used for a kind of in-place transform algorithm, -+# but the planner seems to never (or hardly ever) use them on the machines -+# we have access to, preferring the Q codelets and the use of buffers -+# for sub-transforms. So, we comment them out, at least for now. -+ -+# f1_ is a "twiddle" FFT of size , implementing a radix-r DIF step -+#F1 = # f1_2.c f1_3.c f1_4.c f1_5.c f1_6.c f1_7.c f1_8.c f1_9.c f1_10.c f1_12.c f1_15.c f1_16.c f1_32.c f1_64.c -+ -+# like f1, but partially generates its trig. table on the fly -+#F2 = # f2_4.c f2_8.c f2_16.c f2_32.c f2_64.c -+ -+########################################################################### -+# q1_ is twiddle FFTs of size (DIF step), where the output is -+# transposed. This is used for in-place transposes in sizes that are -+# divisible by ^2. These codelets have size ~ ^2, so you should -+# probably not use bigger than 8 or so. -+set(Q1 q1_2.c q1_4.c q1_8.c q1_3.c q1_5.c q1_6.c) -+ -+########################################################################### -+set(ALL_CODELETS ${N1} ${T1} ${T2} ${F1} ${F2} ${Q1}) -+ -+include(${CMAKE_SOURCE_DIR}/support/codelets.cmake) -+ -+set(SOLVTAB_NAME "X(solvtab_dft_standard)") -+ -+set(CODLIST ${CMAKE_CURRENT_BINARY_DIR}/codlist.c) -+set(CODELET_NAME codelet_) -+ -+write_codelet_list(${CODLIST} ${CODELET_NAME} ${SOLVTAB_NAME} ${ALL_CODELETS}) -+ -+set(dft_scalar_codelets_SRCS ${ALL_CODELETS}) -+ -+if(BUILD_ALL_STATIC) -+ add_library(dft_scalar_codelets STATIC ${dft_scalar_codelets_SRCS}) -+else(BUILD_ALL_STATIC) -+ prepend_prefix(dft_scalar_codelets_SRCS) -+ set(__fftw_SRCS ${__fftw_SRCS} ${dft_scalar_codelets_SRCS} ${CODLIST} PARENT_SCOPE) -+endif(BUILD_ALL_STATIC) -+ -+# this is old source code which can be changed later -+# only delete codlist.c in maintainer-mode, since it is included in the dist -+# FIXME: is there a way to delete in 'make clean' only when builddir != srcdir? -+#maintainer-clean-local: -+# rm -f $(CODLIST) -+ -+#if MAINTAINER_MODE -+#FLAGS_N1=$(DFT_FLAGS_COMMON) -+#FLAGS_T1=$(DFT_FLAGS_COMMON) -+#FLAGS_T2=$(DFT_FLAGS_COMMON) -twiddle-log3 -precompute-twiddles -+#FLAGS_F1=$(DFT_FLAGS_COMMON) -+#FLAGS_F2=$(DFT_FLAGS_COMMON) -twiddle-log3 -precompute-twiddles -+#FLAGS_Q1=$(DFT_FLAGS_COMMON) -reload-twiddle -+#FLAGS_Q2=$(DFT_FLAGS_COMMON) -twiddle-log3 -precompute-twiddles -+# -+#n1_%.c: $(CODELET_DEPS) $(GEN_NOTW) -+# ($(PRELUDE_COMMANDS_DFT); $(TWOVERS) $(GEN_NOTW) $(FLAGS_N1) -n $* -name n1_$* -include "n.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#t1_%.c: $(CODELET_DEPS) $(GEN_TWIDDLE) -+# ($(PRELUDE_COMMANDS_DFT); $(TWOVERS) $(GEN_TWIDDLE) $(FLAGS_T1) -n $* -name t1_$* -include "t.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#t2_%.c: $(CODELET_DEPS) $(GEN_TWIDDLE) -+# ($(PRELUDE_COMMANDS_DFT); $(TWOVERS) $(GEN_TWIDDLE) $(FLAGS_T2) -n $* -name t2_$* -include "t.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#f1_%.c: $(CODELET_DEPS) $(GEN_TWIDDLE) -+# ($(PRELUDE_COMMANDS_DFT); $(TWOVERS) $(GEN_TWIDDLE) $(FLAGS_F1) -dif -n $* -name f1_$* -include "f.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#f2_%.c: $(CODELET_DEPS) $(GEN_TWIDDLE) -+# ($(PRELUDE_COMMANDS_DFT); $(TWOVERS) $(GEN_TWIDDLE) $(FLAGS_F2) -dif -n $* -name f2_$* -include "f.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#q1_%.c: $(CODELET_DEPS) $(GEN_TWIDSQ) -+# ($(PRELUDE_COMMANDS_DFT); $(TWOVERS) $(GEN_TWIDSQ) $(FLAGS_Q1) -dif -n $* -name q1_$* -include "q.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#q2_%.c: $(CODELET_DEPS) $(GEN_TWIDSQ) -+# ($(PRELUDE_COMMANDS_DFT); $(TWOVERS) $(GEN_TWIDSQ) $(FLAGS_Q2) -dif -n $* -name q2_$* -include "q.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#endif # MAINTAINER_MODE -+ -diff -Nru fftw-3.2.2.orig/dft/simd/CMakeLists.txt fftw-3.2.2/dft/simd/CMakeLists.txt ---- fftw-3.2.2.orig/dft/simd/CMakeLists.txt 1970-01-01 01:00:00.000000000 +0100 -+++ fftw-3.2.2/dft/simd/CMakeLists.txt 2011-12-21 11:36:18.181871300 +0100 -@@ -0,0 +1,10 @@ -+add_subdirectory(codelets) -+ -+set(dft_simd_SRCS n1b.c n1f.c n2b.c n2f.c n2s.c q1b.c q1f.c t.c ts.c) -+ -+if(BUILD_ALL_STATIC) -+ add_library(dft_simd STATIC ${dft_simd_SRCS}) -+else(BUILD_ALL_STATIC) -+ prepend_prefix(dft_simd_SRCS) -+ set(_fftw_SRCS ${_fftw_SRCS} ${dft_simd_SRCS} ${__fftw_SRCS} PARENT_SCOPE) -+endif(BUILD_ALL_STATIC) -diff -Nru fftw-3.2.2.orig/dft/simd/codelets/CMakeLists.txt fftw-3.2.2/dft/simd/codelets/CMakeLists.txt ---- fftw-3.2.2.orig/dft/simd/codelets/CMakeLists.txt 1970-01-01 01:00:00.000000000 +0100 -+++ fftw-3.2.2/dft/simd/codelets/CMakeLists.txt 2011-12-21 11:36:18.183871500 +0100 -@@ -0,0 +1,189 @@ -+# This Makefile.am specifies a set of codelets, efficient transforms -+# of small sizes, that are used as building blocks (kernels) by FFTW -+# to build up large transforms, as well as the options for generating -+# and compiling them. -+ -+# You can customize FFTW for special needs, e.g. to handle certain -+# sizes more efficiently, by adding new codelets to the lists of those -+# included by default. If you change the list of codelets, any new -+# ones you added will be automatically generated when you run the -+# bootstrap script (see "Generating your own code" in the FFTW -+# manual). -+ -+########################################################################### -+#AM_CPPFLAGS = -I$(top_srcdir)/kernel -I$(top_srcdir)/dft \ -+#-I$(top_srcdir)/dft/simd -I$(top_srcdir)/simd -+#AM_CFLAGS = $(SIMD_CFLAGS) -+#noinst_LTLIBRARIES = libdft_simd_codelets.la -+ -+########################################################################### -+# n1fv_ is a hard-coded FFTW_FORWARD FFT of size , using SIMD -+set(N1F n1fv_2.c n1fv_3.c n1fv_4.c n1fv_5.c n1fv_6.c n1fv_7.c n1fv_8.c -+n1fv_9.c n1fv_10.c n1fv_11.c n1fv_12.c n1fv_13.c n1fv_14.c n1fv_15.c -+n1fv_16.c n1fv_32.c n1fv_64.c n1fv_128.c -+n1fv_20.c n1fv_25.c # n1fv_30.c n1fv_40.c n1fv_50.c -+) -+ -+# as above, with restricted input vector stride -+set(N2F n2fv_2.c n2fv_4.c n2fv_6.c n2fv_8.c n2fv_10.c n2fv_12.c -+n2fv_14.c n2fv_16.c n2fv_32.c n2fv_64.c -+n2fv_20.c # n2fv_30.c n2fv_40.c n2fv_50.c -+) -+ -+# as above, but FFTW_BACKWARD -+set(N1B n1bv_2.c n1bv_3.c n1bv_4.c n1bv_5.c n1bv_6.c n1bv_7.c n1bv_8.c -+n1bv_9.c n1bv_10.c n1bv_11.c n1bv_12.c n1bv_13.c n1bv_14.c n1bv_15.c -+n1bv_16.c n1bv_32.c n1bv_64.c n1bv_128.c -+n1bv_20.c n1bv_25.c # n1bv_30.c n1bv_40.c n1bv_50.c -+) -+ -+set(N2B n2bv_2.c n2bv_4.c n2bv_6.c n2bv_8.c n2bv_10.c n2bv_12.c -+n2bv_14.c n2bv_16.c n2bv_32.c n2bv_64.c -+n2bv_20.c # n2bv_30.c n2bv_40.c n2bv_50.c -+) -+ -+# split-complex codelets -+set(N2S n2sv_4.c n2sv_8.c n2sv_16.c n2sv_32.c n2sv_64.c -+) -+ -+########################################################################### -+# t1fv_ is a "twiddle" FFT of size , implementing a radix-r DIT step -+# for an FFTW_FORWARD transform, using SIMD -+set(T1F t1fv_2.c t1fv_3.c t1fv_4.c t1fv_5.c t1fv_6.c t1fv_7.c t1fv_8.c -+t1fv_9.c t1fv_10.c t1fv_12.c t1fv_15.c t1fv_16.c t1fv_32.c t1fv_64.c -+t1fv_20.c t1fv_25.c # t1fv_30.c t1fv_40.c t1fv_50.c -+) -+ -+# same as t1fv_*, but with different twiddle storage scheme -+set(T2F t2fv_2.c t2fv_4.c t2fv_8.c t2fv_16.c t2fv_32.c t2fv_64.c -+t2fv_5.c t2fv_10.c t2fv_20.c t2fv_25.c -+) -+set(T3F t3fv_4.c t3fv_8.c t3fv_16.c t3fv_32.c -+t3fv_5.c t3fv_10.c t3fv_20.c t3fv_25.c -+) -+set(T1FU t1fuv_2.c t1fuv_3.c t1fuv_4.c t1fuv_5.c t1fuv_6.c t1fuv_7.c -+t1fuv_8.c t1fuv_9.c t1fuv_10.c -+) -+ -+# as above, but FFTW_BACKWARD -+set(T1B t1bv_2.c t1bv_3.c t1bv_4.c t1bv_5.c t1bv_6.c t1bv_7.c t1bv_8.c -+t1bv_9.c t1bv_10.c t1bv_12.c t1bv_15.c t1bv_16.c t1bv_32.c t1bv_64.c -+t1bv_20.c t1bv_25.c # t1bv_30.c t1bv_40.c t1bv_50.c -+) -+ -+# same as t1bv_*, but with different twiddle storage scheme -+set(T2B t2bv_2.c t2bv_4.c t2bv_8.c t2bv_16.c t2bv_32.c t2bv_64.c -+t2bv_5.c t2bv_10.c t2bv_20.c t2bv_25.c -+) -+set(T3B t3bv_4.c t3bv_8.c t3bv_16.c t3bv_32.c -+t3bv_5.c t3bv_10.c t3bv_20.c t3bv_25.c -+) -+set(T1BU t1buv_2.c t1buv_3.c t1buv_4.c t1buv_5.c t1buv_6.c t1buv_7.c -+t1buv_8.c t1buv_9.c t1buv_10.c -+) -+ -+# split-complex codelets -+set(T1S t1sv_2.c t1sv_4.c t1sv_8.c t1sv_16.c t1sv_32.c #t1sv_64.c -+) -+set(T2S t2sv_4.c t2sv_8.c t2sv_16.c t2sv_32.c #t2sv_64.c -+) -+ -+########################################################################### -+# q1fv_ is twiddle FFTW_FORWARD FFTs of size (DIF step), -+# where the output is transposed, using SIMD. This is used for -+# in-place transposes in sizes that are divisible by ^2. These -+# codelets have size ~ ^2, so you should probably not use -+# bigger than 8 or so. -+set(Q1F q1fv_2.c q1fv_4.c q1fv_5.c q1fv_8.c ) -+ -+# as above, but FFTW_BACKWARD -+set(Q1B q1bv_2.c q1bv_4.c q1bv_5.c q1bv_8.c) -+ -+########################################################################### -+set(SIMD_CODELETS ${N1F} ${N1B} ${N2F} ${N2B} ${N2S} ${T1FU} ${T1F} ${T2F} ${T3F} ${T1BU} ${T1B} ${T2B} ${T3B} ${T1S} ${T2S} ${Q1F} ${Q1B}) -+ -+if(HAVE_SIMD) -+ set(ALL_CODELETS ${SIMD_CODELETS}) -+else(HAVE_SIMD) -+ set(ALL_CODELETS) -+endif(HAVE_SIMD) -+ -+set(SOLVTAB_NAME "X(solvtab_dft_simd)") -+set(CODLIST ${CMAKE_CURRENT_BINARY_DIR}/codlist.c) -+set(CODELET_NAME codelet_) -+ -+set(dft_simd_codelets_SRCS ${ALL_CODELETS}) -+ -+# special rules for regenerating codelets. -+include(${CMAKE_SOURCE_DIR}/support/codelets.cmake) -+ -+write_codelet_list(${CODLIST} ${CODELET_NAME} ${SOLVTAB_NAME} ${ALL_CODELETS}) -+ -+if(BUILD_ALL_STATIC) -+ add_library(dft_simd_codelets STATIC ${ALL_CODELETS}) -+else(BUILD_ALL_STATIC) -+ prepend_prefix(dft_simd_codelets_SRCS) -+ set(__fftw_SRCS ${__fftw_SRCS} ${dft_simd_codelets_SRCS} ${CODLIST} PARENT_SCOPE) -+endif(BUILD_ALL_STATIC) -+ -+#include $(top_srcdir)/support/Makefile.codelets -+ -+#if MAINTAINER_MODE -+#GFLAGS = -simd $(FLAGS_COMMON) -pipeline-latency 8 -+#FLAGS_T2S=-twiddle-log3 -precompute-twiddles -+#FLAGS_T3=-twiddle-log3 -precompute-twiddles -no-generate-bytw -+# -+#n1fv_%.c: $(CODELET_DEPS) $(GEN_NOTW_C) -+# ($(PRELUDE_COMMANDS_DFT); $(TWOVERS) $(GEN_NOTW_C) $(GFLAGS) -n $* -name n1fv_$* -include "n1f.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#n2fv_%.c: $(CODELET_DEPS) $(GEN_NOTW_C) -+# ($(PRELUDE_COMMANDS_DFT); $(TWOVERS) $(GEN_NOTW_C) $(GFLAGS) -n $* -name n2fv_$* -with-ostride 2 -include "n2f.h" -store-multiple 2) | $(ADD_DATE) | $(INDENT) >$@ -+# -+#n1bv_%.c: $(CODELET_DEPS) $(GEN_NOTW_C) -+# ($(PRELUDE_COMMANDS_DFT); $(TWOVERS) $(GEN_NOTW_C) $(GFLAGS) -sign 1 -n $* -name n1bv_$* -include "n1b.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#n2bv_%.c: $(CODELET_DEPS) $(GEN_NOTW_C) -+# ($(PRELUDE_COMMANDS_DFT); $(TWOVERS) $(GEN_NOTW_C) $(GFLAGS) -sign 1 -n $* -name n2bv_$* -with-ostride 2 -include "n2b.h" -store-multiple 2) | $(ADD_DATE) | $(INDENT) >$@ -+# -+#n2sv_%.c: $(CODELET_DEPS) $(GEN_NOTW) -+# ($(PRELUDE_COMMANDS_DFT); $(TWOVERS) $(GEN_NOTW) $(GFLAGS) -n $* -name n2sv_$* -with-ostride 1 -include "n2s.h" -store-multiple 4) | $(ADD_DATE) | $(INDENT) >$@ -+# -+#t1fv_%.c: $(CODELET_DEPS) $(GEN_TWIDDLE_C) -+# ($(PRELUDE_COMMANDS_DFT); $(TWOVERS) $(GEN_TWIDDLE_C) $(GFLAGS) -n $* -name t1fv_$* -include "t1f.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#t1fuv_%.c: $(CODELET_DEPS) $(GEN_TWIDDLE_C) -+# ($(PRELUDE_COMMANDS_DFT); $(TWOVERS) $(GEN_TWIDDLE_C) $(GFLAGS) -n $* -name t1fuv_$* -include "t1fu.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#t2fv_%.c: $(CODELET_DEPS) $(GEN_TWIDDLE_C) -+# ($(PRELUDE_COMMANDS_DFT); $(TWOVERS) $(GEN_TWIDDLE_C) $(GFLAGS) -n $* -name t2fv_$* -include "t2f.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#t3fv_%.c: $(CODELET_DEPS) $(GEN_TWIDDLE_C) -+# ($(PRELUDE_COMMANDS_DFT); $(TWOVERS) $(GEN_TWIDDLE_C) $(GFLAGS) $(FLAGS_T3) -n $* -name t3fv_$* -include "t3f.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#t1bv_%.c: $(CODELET_DEPS) $(GEN_TWIDDLE_C) -+# ($(PRELUDE_COMMANDS_DFT); $(TWOVERS) $(GEN_TWIDDLE_C) $(GFLAGS) -n $* -name t1bv_$* -include "t1b.h" -sign 1) | $(ADD_DATE) | $(INDENT) >$@ -+# -+#t1buv_%.c: $(CODELET_DEPS) $(GEN_TWIDDLE_C) -+# ($(PRELUDE_COMMANDS_DFT); $(TWOVERS) $(GEN_TWIDDLE_C) $(GFLAGS) -n $* -name t1buv_$* -include "t1bu.h" -sign 1) | $(ADD_DATE) | $(INDENT) >$@ -+# -+#t2bv_%.c: $(CODELET_DEPS) $(GEN_TWIDDLE_C) -+# ($(PRELUDE_COMMANDS_DFT); $(TWOVERS) $(GEN_TWIDDLE_C) $(GFLAGS) -n $* -name t2bv_$* -include "t2b.h" -sign 1) | $(ADD_DATE) | $(INDENT) >$@ -+# -+#t3bv_%.c: $(CODELET_DEPS) $(GEN_TWIDDLE_C) -+# ($(PRELUDE_COMMANDS_DFT); $(TWOVERS) $(GEN_TWIDDLE_C) $(GFLAGS) $(FLAGS_T3) -n $* -name t3bv_$* -include "t3b.h" -sign 1) | $(ADD_DATE) | $(INDENT) >$@ -+# -+#t1sv_%.c: $(CODELET_DEPS) $(GEN_TWIDDLE) -+# ($(PRELUDE_COMMANDS_DFT); $(TWOVERS) $(GEN_TWIDDLE) $(GFLAGS) -n $* -name t1sv_$* -include "ts.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#t2sv_%.c: $(CODELET_DEPS) $(GEN_TWIDDLE) -+# ($(PRELUDE_COMMANDS_DFT); $(TWOVERS) $(GEN_TWIDDLE) $(GFLAGS) $(FLAGS_T2S) -n $* -name t2sv_$* -include "ts.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#q1fv_%.c: $(CODELET_DEPS) $(GEN_TWIDSQ_C) -+# ($(PRELUDE_COMMANDS_DFT); $(TWOVERS) $(GEN_TWIDSQ_C) $(GFLAGS) -n $* -dif -name q1fv_$* -include "q1f.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#q1bv_%.c: $(CODELET_DEPS) $(GEN_TWIDSQ_C) -+# ($(PRELUDE_COMMANDS_DFT); $(TWOVERS) $(GEN_TWIDSQ_C) $(GFLAGS) -n $* -dif -name q1bv_$* -include "q1b.h" -sign 1) | $(ADD_DATE) | $(INDENT) >$@ -+# -+# -+#endif # MAINTAINER_MODE -+# -diff -Nru fftw-3.2.2.orig/fftw.def.cmake fftw-3.2.2/fftw.def.cmake ---- fftw-3.2.2.orig/fftw.def.cmake 1970-01-01 01:00:00.000000000 +0100 -+++ fftw-3.2.2/fftw.def.cmake 2011-12-21 11:36:18.187871900 +0100 -@@ -0,0 +1,808 @@ -+EXPORTS -+@FFTW_SYMBOL_PREFIX@alignment_of -+@FFTW_SYMBOL_PREFIX@assertion_failed -+@FFTW_SYMBOL_PREFIX@bufdist -+@FFTW_SYMBOL_PREFIX@check_alignment_of_sse_pmpm -+@FFTW_SYMBOL_PREFIX@choose_radix -+@FFTW_SYMBOL_PREFIX@cleanup -+@FFTW_SYMBOL_PREFIX@cleanup_threads -+@FFTW_SYMBOL_PREFIX@codelet_e01_8 -+@FFTW_SYMBOL_PREFIX@codelet_e10_8 -+@FFTW_SYMBOL_PREFIX@codelet_hb2_16 -+@FFTW_SYMBOL_PREFIX@codelet_hb2_20 -+@FFTW_SYMBOL_PREFIX@codelet_hb2_25 -+@FFTW_SYMBOL_PREFIX@codelet_hb2_32 -+@FFTW_SYMBOL_PREFIX@codelet_hb2_4 -+@FFTW_SYMBOL_PREFIX@codelet_hb2_5 -+@FFTW_SYMBOL_PREFIX@codelet_hb2_8 -+@FFTW_SYMBOL_PREFIX@codelet_hb_10 -+@FFTW_SYMBOL_PREFIX@codelet_hb_12 -+@FFTW_SYMBOL_PREFIX@codelet_hb_15 -+@FFTW_SYMBOL_PREFIX@codelet_hb_16 -+@FFTW_SYMBOL_PREFIX@codelet_hb_2 -+@FFTW_SYMBOL_PREFIX@codelet_hb_20 -+@FFTW_SYMBOL_PREFIX@codelet_hb_25 -+@FFTW_SYMBOL_PREFIX@codelet_hb_3 -+@FFTW_SYMBOL_PREFIX@codelet_hb_32 -+@FFTW_SYMBOL_PREFIX@codelet_hb_4 -+@FFTW_SYMBOL_PREFIX@codelet_hb_5 -+@FFTW_SYMBOL_PREFIX@codelet_hb_6 -+@FFTW_SYMBOL_PREFIX@codelet_hb_64 -+@FFTW_SYMBOL_PREFIX@codelet_hb_7 -+@FFTW_SYMBOL_PREFIX@codelet_hb_8 -+@FFTW_SYMBOL_PREFIX@codelet_hb_9 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cb2_16 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cb2_20 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cb2_32 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cb2_4 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cb2_8 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cb_10 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cb_12 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cb_16 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cb_2 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cb_20 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cb_32 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cb_4 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cb_6 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cb_8 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cbdft2_16 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cbdft2_20 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cbdft2_32 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cbdft2_4 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cbdft2_8 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cbdft_10 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cbdft_12 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cbdft_16 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cbdft_2 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cbdft_20 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cbdft_32 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cbdft_4 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cbdft_6 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cbdft_8 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cbdftv_10 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cbdftv_12 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cbdftv_16 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cbdftv_2 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cbdftv_20 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cbdftv_32 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cbdftv_4 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cbdftv_6 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cbdftv_8 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cf2_16 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cf2_20 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cf2_32 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cf2_4 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cf2_8 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cf_10 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cf_12 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cf_16 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cf_2 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cf_20 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cf_32 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cf_4 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cf_6 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cf_8 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cfdft2_16 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cfdft2_20 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cfdft2_32 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cfdft2_4 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cfdft2_8 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cfdft_10 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cfdft_12 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cfdft_16 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cfdft_2 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cfdft_20 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cfdft_32 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cfdft_4 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cfdft_6 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cfdft_8 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cfdftv_10 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cfdftv_12 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cfdftv_16 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cfdftv_2 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cfdftv_20 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cfdftv_32 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cfdftv_4 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cfdftv_6 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cfdftv_8 -+@FFTW_SYMBOL_PREFIX@codelet_hf2_16 -+@FFTW_SYMBOL_PREFIX@codelet_hf2_20 -+@FFTW_SYMBOL_PREFIX@codelet_hf2_25 -+@FFTW_SYMBOL_PREFIX@codelet_hf2_32 -+@FFTW_SYMBOL_PREFIX@codelet_hf2_4 -+@FFTW_SYMBOL_PREFIX@codelet_hf2_5 -+@FFTW_SYMBOL_PREFIX@codelet_hf2_8 -+@FFTW_SYMBOL_PREFIX@codelet_hf_10 -+@FFTW_SYMBOL_PREFIX@codelet_hf_12 -+@FFTW_SYMBOL_PREFIX@codelet_hf_15 -+@FFTW_SYMBOL_PREFIX@codelet_hf_16 -+@FFTW_SYMBOL_PREFIX@codelet_hf_2 -+@FFTW_SYMBOL_PREFIX@codelet_hf_20 -+@FFTW_SYMBOL_PREFIX@codelet_hf_25 -+@FFTW_SYMBOL_PREFIX@codelet_hf_3 -+@FFTW_SYMBOL_PREFIX@codelet_hf_32 -+@FFTW_SYMBOL_PREFIX@codelet_hf_4 -+@FFTW_SYMBOL_PREFIX@codelet_hf_5 -+@FFTW_SYMBOL_PREFIX@codelet_hf_6 -+@FFTW_SYMBOL_PREFIX@codelet_hf_64 -+@FFTW_SYMBOL_PREFIX@codelet_hf_7 -+@FFTW_SYMBOL_PREFIX@codelet_hf_8 -+@FFTW_SYMBOL_PREFIX@codelet_hf_9 -+@FFTW_SYMBOL_PREFIX@codelet_n1_10 -+@FFTW_SYMBOL_PREFIX@codelet_n1_11 -+@FFTW_SYMBOL_PREFIX@codelet_n1_12 -+@FFTW_SYMBOL_PREFIX@codelet_n1_13 -+@FFTW_SYMBOL_PREFIX@codelet_n1_14 -+@FFTW_SYMBOL_PREFIX@codelet_n1_15 -+@FFTW_SYMBOL_PREFIX@codelet_n1_16 -+@FFTW_SYMBOL_PREFIX@codelet_n1_2 -+@FFTW_SYMBOL_PREFIX@codelet_n1_20 -+@FFTW_SYMBOL_PREFIX@codelet_n1_25 -+@FFTW_SYMBOL_PREFIX@codelet_n1_3 -+@FFTW_SYMBOL_PREFIX@codelet_n1_32 -+@FFTW_SYMBOL_PREFIX@codelet_n1_4 -+@FFTW_SYMBOL_PREFIX@codelet_n1_5 -+@FFTW_SYMBOL_PREFIX@codelet_n1_6 -+@FFTW_SYMBOL_PREFIX@codelet_n1_64 -+@FFTW_SYMBOL_PREFIX@codelet_n1_7 -+@FFTW_SYMBOL_PREFIX@codelet_n1_8 -+@FFTW_SYMBOL_PREFIX@codelet_n1_9 -+@FFTW_SYMBOL_PREFIX@codelet_n1bv_10 -+@FFTW_SYMBOL_PREFIX@codelet_n1bv_11 -+@FFTW_SYMBOL_PREFIX@codelet_n1bv_12 -+@FFTW_SYMBOL_PREFIX@codelet_n1bv_128 -+@FFTW_SYMBOL_PREFIX@codelet_n1bv_13 -+@FFTW_SYMBOL_PREFIX@codelet_n1bv_14 -+@FFTW_SYMBOL_PREFIX@codelet_n1bv_15 -+@FFTW_SYMBOL_PREFIX@codelet_n1bv_16 -+@FFTW_SYMBOL_PREFIX@codelet_n1bv_2 -+@FFTW_SYMBOL_PREFIX@codelet_n1bv_20 -+@FFTW_SYMBOL_PREFIX@codelet_n1bv_25 -+@FFTW_SYMBOL_PREFIX@codelet_n1bv_3 -+@FFTW_SYMBOL_PREFIX@codelet_n1bv_32 -+@FFTW_SYMBOL_PREFIX@codelet_n1bv_4 -+@FFTW_SYMBOL_PREFIX@codelet_n1bv_5 -+@FFTW_SYMBOL_PREFIX@codelet_n1bv_6 -+@FFTW_SYMBOL_PREFIX@codelet_n1bv_64 -+@FFTW_SYMBOL_PREFIX@codelet_n1bv_7 -+@FFTW_SYMBOL_PREFIX@codelet_n1bv_8 -+@FFTW_SYMBOL_PREFIX@codelet_n1bv_9 -+@FFTW_SYMBOL_PREFIX@codelet_n1fv_10 -+@FFTW_SYMBOL_PREFIX@codelet_n1fv_11 -+@FFTW_SYMBOL_PREFIX@codelet_n1fv_12 -+@FFTW_SYMBOL_PREFIX@codelet_n1fv_128 -+@FFTW_SYMBOL_PREFIX@codelet_n1fv_13 -+@FFTW_SYMBOL_PREFIX@codelet_n1fv_14 -+@FFTW_SYMBOL_PREFIX@codelet_n1fv_15 -+@FFTW_SYMBOL_PREFIX@codelet_n1fv_16 -+@FFTW_SYMBOL_PREFIX@codelet_n1fv_2 -+@FFTW_SYMBOL_PREFIX@codelet_n1fv_20 -+@FFTW_SYMBOL_PREFIX@codelet_n1fv_25 -+@FFTW_SYMBOL_PREFIX@codelet_n1fv_3 -+@FFTW_SYMBOL_PREFIX@codelet_n1fv_32 -+@FFTW_SYMBOL_PREFIX@codelet_n1fv_4 -+@FFTW_SYMBOL_PREFIX@codelet_n1fv_5 -+@FFTW_SYMBOL_PREFIX@codelet_n1fv_6 -+@FFTW_SYMBOL_PREFIX@codelet_n1fv_64 -+@FFTW_SYMBOL_PREFIX@codelet_n1fv_7 -+@FFTW_SYMBOL_PREFIX@codelet_n1fv_8 -+@FFTW_SYMBOL_PREFIX@codelet_n1fv_9 -+@FFTW_SYMBOL_PREFIX@codelet_n2bv_10 -+@FFTW_SYMBOL_PREFIX@codelet_n2bv_12 -+@FFTW_SYMBOL_PREFIX@codelet_n2bv_14 -+@FFTW_SYMBOL_PREFIX@codelet_n2bv_16 -+@FFTW_SYMBOL_PREFIX@codelet_n2bv_2 -+@FFTW_SYMBOL_PREFIX@codelet_n2bv_20 -+@FFTW_SYMBOL_PREFIX@codelet_n2bv_32 -+@FFTW_SYMBOL_PREFIX@codelet_n2bv_4 -+@FFTW_SYMBOL_PREFIX@codelet_n2bv_6 -+@FFTW_SYMBOL_PREFIX@codelet_n2bv_64 -+@FFTW_SYMBOL_PREFIX@codelet_n2bv_8 -+@FFTW_SYMBOL_PREFIX@codelet_n2fv_10 -+@FFTW_SYMBOL_PREFIX@codelet_n2fv_12 -+@FFTW_SYMBOL_PREFIX@codelet_n2fv_14 -+@FFTW_SYMBOL_PREFIX@codelet_n2fv_16 -+@FFTW_SYMBOL_PREFIX@codelet_n2fv_2 -+@FFTW_SYMBOL_PREFIX@codelet_n2fv_20 -+@FFTW_SYMBOL_PREFIX@codelet_n2fv_32 -+@FFTW_SYMBOL_PREFIX@codelet_n2fv_4 -+@FFTW_SYMBOL_PREFIX@codelet_n2fv_6 -+@FFTW_SYMBOL_PREFIX@codelet_n2fv_64 -+@FFTW_SYMBOL_PREFIX@codelet_n2fv_8 -+@FFTW_SYMBOL_PREFIX@codelet_n2sv_16 -+@FFTW_SYMBOL_PREFIX@codelet_n2sv_32 -+@FFTW_SYMBOL_PREFIX@codelet_n2sv_4 -+@FFTW_SYMBOL_PREFIX@codelet_n2sv_64 -+@FFTW_SYMBOL_PREFIX@codelet_n2sv_8 -+@FFTW_SYMBOL_PREFIX@codelet_q1_2 -+@FFTW_SYMBOL_PREFIX@codelet_q1_3 -+@FFTW_SYMBOL_PREFIX@codelet_q1_4 -+@FFTW_SYMBOL_PREFIX@codelet_q1_5 -+@FFTW_SYMBOL_PREFIX@codelet_q1_6 -+@FFTW_SYMBOL_PREFIX@codelet_q1_8 -+@FFTW_SYMBOL_PREFIX@codelet_q1bv_2 -+@FFTW_SYMBOL_PREFIX@codelet_q1bv_4 -+@FFTW_SYMBOL_PREFIX@codelet_q1bv_5 -+@FFTW_SYMBOL_PREFIX@codelet_q1bv_8 -+@FFTW_SYMBOL_PREFIX@codelet_q1fv_2 -+@FFTW_SYMBOL_PREFIX@codelet_q1fv_4 -+@FFTW_SYMBOL_PREFIX@codelet_q1fv_5 -+@FFTW_SYMBOL_PREFIX@codelet_q1fv_8 -+@FFTW_SYMBOL_PREFIX@codelet_r2cbIII_10 -+@FFTW_SYMBOL_PREFIX@codelet_r2cbIII_12 -+@FFTW_SYMBOL_PREFIX@codelet_r2cbIII_15 -+@FFTW_SYMBOL_PREFIX@codelet_r2cbIII_16 -+@FFTW_SYMBOL_PREFIX@codelet_r2cbIII_2 -+@FFTW_SYMBOL_PREFIX@codelet_r2cbIII_20 -+@FFTW_SYMBOL_PREFIX@codelet_r2cbIII_25 -+@FFTW_SYMBOL_PREFIX@codelet_r2cbIII_3 -+@FFTW_SYMBOL_PREFIX@codelet_r2cbIII_32 -+@FFTW_SYMBOL_PREFIX@codelet_r2cbIII_4 -+@FFTW_SYMBOL_PREFIX@codelet_r2cbIII_5 -+@FFTW_SYMBOL_PREFIX@codelet_r2cbIII_6 -+@FFTW_SYMBOL_PREFIX@codelet_r2cbIII_64 -+@FFTW_SYMBOL_PREFIX@codelet_r2cbIII_7 -+@FFTW_SYMBOL_PREFIX@codelet_r2cbIII_8 -+@FFTW_SYMBOL_PREFIX@codelet_r2cbIII_9 -+@FFTW_SYMBOL_PREFIX@codelet_r2cb_10 -+@FFTW_SYMBOL_PREFIX@codelet_r2cb_11 -+@FFTW_SYMBOL_PREFIX@codelet_r2cb_12 -+@FFTW_SYMBOL_PREFIX@codelet_r2cb_128 -+@FFTW_SYMBOL_PREFIX@codelet_r2cb_13 -+@FFTW_SYMBOL_PREFIX@codelet_r2cb_14 -+@FFTW_SYMBOL_PREFIX@codelet_r2cb_15 -+@FFTW_SYMBOL_PREFIX@codelet_r2cb_16 -+@FFTW_SYMBOL_PREFIX@codelet_r2cb_2 -+@FFTW_SYMBOL_PREFIX@codelet_r2cb_20 -+@FFTW_SYMBOL_PREFIX@codelet_r2cb_25 -+@FFTW_SYMBOL_PREFIX@codelet_r2cb_3 -+@FFTW_SYMBOL_PREFIX@codelet_r2cb_32 -+@FFTW_SYMBOL_PREFIX@codelet_r2cb_4 -+@FFTW_SYMBOL_PREFIX@codelet_r2cb_5 -+@FFTW_SYMBOL_PREFIX@codelet_r2cb_6 -+@FFTW_SYMBOL_PREFIX@codelet_r2cb_64 -+@FFTW_SYMBOL_PREFIX@codelet_r2cb_7 -+@FFTW_SYMBOL_PREFIX@codelet_r2cb_8 -+@FFTW_SYMBOL_PREFIX@codelet_r2cb_9 -+@FFTW_SYMBOL_PREFIX@codelet_r2cfII_10 -+@FFTW_SYMBOL_PREFIX@codelet_r2cfII_12 -+@FFTW_SYMBOL_PREFIX@codelet_r2cfII_15 -+@FFTW_SYMBOL_PREFIX@codelet_r2cfII_16 -+@FFTW_SYMBOL_PREFIX@codelet_r2cfII_2 -+@FFTW_SYMBOL_PREFIX@codelet_r2cfII_20 -+@FFTW_SYMBOL_PREFIX@codelet_r2cfII_25 -+@FFTW_SYMBOL_PREFIX@codelet_r2cfII_3 -+@FFTW_SYMBOL_PREFIX@codelet_r2cfII_32 -+@FFTW_SYMBOL_PREFIX@codelet_r2cfII_4 -+@FFTW_SYMBOL_PREFIX@codelet_r2cfII_5 -+@FFTW_SYMBOL_PREFIX@codelet_r2cfII_6 -+@FFTW_SYMBOL_PREFIX@codelet_r2cfII_64 -+@FFTW_SYMBOL_PREFIX@codelet_r2cfII_7 -+@FFTW_SYMBOL_PREFIX@codelet_r2cfII_8 -+@FFTW_SYMBOL_PREFIX@codelet_r2cfII_9 -+@FFTW_SYMBOL_PREFIX@codelet_r2cf_10 -+@FFTW_SYMBOL_PREFIX@codelet_r2cf_11 -+@FFTW_SYMBOL_PREFIX@codelet_r2cf_12 -+@FFTW_SYMBOL_PREFIX@codelet_r2cf_128 -+@FFTW_SYMBOL_PREFIX@codelet_r2cf_13 -+@FFTW_SYMBOL_PREFIX@codelet_r2cf_14 -+@FFTW_SYMBOL_PREFIX@codelet_r2cf_15 -+@FFTW_SYMBOL_PREFIX@codelet_r2cf_16 -+@FFTW_SYMBOL_PREFIX@codelet_r2cf_2 -+@FFTW_SYMBOL_PREFIX@codelet_r2cf_20 -+@FFTW_SYMBOL_PREFIX@codelet_r2cf_25 -+@FFTW_SYMBOL_PREFIX@codelet_r2cf_3 -+@FFTW_SYMBOL_PREFIX@codelet_r2cf_32 -+@FFTW_SYMBOL_PREFIX@codelet_r2cf_4 -+@FFTW_SYMBOL_PREFIX@codelet_r2cf_5 -+@FFTW_SYMBOL_PREFIX@codelet_r2cf_6 -+@FFTW_SYMBOL_PREFIX@codelet_r2cf_64 -+@FFTW_SYMBOL_PREFIX@codelet_r2cf_7 -+@FFTW_SYMBOL_PREFIX@codelet_r2cf_8 -+@FFTW_SYMBOL_PREFIX@codelet_r2cf_9 -+@FFTW_SYMBOL_PREFIX@codelet_t1_10 -+@FFTW_SYMBOL_PREFIX@codelet_t1_12 -+@FFTW_SYMBOL_PREFIX@codelet_t1_15 -+@FFTW_SYMBOL_PREFIX@codelet_t1_16 -+@FFTW_SYMBOL_PREFIX@codelet_t1_2 -+@FFTW_SYMBOL_PREFIX@codelet_t1_20 -+@FFTW_SYMBOL_PREFIX@codelet_t1_25 -+@FFTW_SYMBOL_PREFIX@codelet_t1_3 -+@FFTW_SYMBOL_PREFIX@codelet_t1_32 -+@FFTW_SYMBOL_PREFIX@codelet_t1_4 -+@FFTW_SYMBOL_PREFIX@codelet_t1_5 -+@FFTW_SYMBOL_PREFIX@codelet_t1_6 -+@FFTW_SYMBOL_PREFIX@codelet_t1_64 -+@FFTW_SYMBOL_PREFIX@codelet_t1_7 -+@FFTW_SYMBOL_PREFIX@codelet_t1_8 -+@FFTW_SYMBOL_PREFIX@codelet_t1_9 -+@FFTW_SYMBOL_PREFIX@codelet_t1buv_10 -+@FFTW_SYMBOL_PREFIX@codelet_t1buv_2 -+@FFTW_SYMBOL_PREFIX@codelet_t1buv_3 -+@FFTW_SYMBOL_PREFIX@codelet_t1buv_4 -+@FFTW_SYMBOL_PREFIX@codelet_t1buv_5 -+@FFTW_SYMBOL_PREFIX@codelet_t1buv_6 -+@FFTW_SYMBOL_PREFIX@codelet_t1buv_7 -+@FFTW_SYMBOL_PREFIX@codelet_t1buv_8 -+@FFTW_SYMBOL_PREFIX@codelet_t1buv_9 -+@FFTW_SYMBOL_PREFIX@codelet_t1bv_10 -+@FFTW_SYMBOL_PREFIX@codelet_t1bv_12 -+@FFTW_SYMBOL_PREFIX@codelet_t1bv_15 -+@FFTW_SYMBOL_PREFIX@codelet_t1bv_16 -+@FFTW_SYMBOL_PREFIX@codelet_t1bv_2 -+@FFTW_SYMBOL_PREFIX@codelet_t1bv_20 -+@FFTW_SYMBOL_PREFIX@codelet_t1bv_25 -+@FFTW_SYMBOL_PREFIX@codelet_t1bv_3 -+@FFTW_SYMBOL_PREFIX@codelet_t1bv_32 -+@FFTW_SYMBOL_PREFIX@codelet_t1bv_4 -+@FFTW_SYMBOL_PREFIX@codelet_t1bv_5 -+@FFTW_SYMBOL_PREFIX@codelet_t1bv_6 -+@FFTW_SYMBOL_PREFIX@codelet_t1bv_64 -+@FFTW_SYMBOL_PREFIX@codelet_t1bv_7 -+@FFTW_SYMBOL_PREFIX@codelet_t1bv_8 -+@FFTW_SYMBOL_PREFIX@codelet_t1bv_9 -+@FFTW_SYMBOL_PREFIX@codelet_t1fuv_10 -+@FFTW_SYMBOL_PREFIX@codelet_t1fuv_2 -+@FFTW_SYMBOL_PREFIX@codelet_t1fuv_3 -+@FFTW_SYMBOL_PREFIX@codelet_t1fuv_4 -+@FFTW_SYMBOL_PREFIX@codelet_t1fuv_5 -+@FFTW_SYMBOL_PREFIX@codelet_t1fuv_6 -+@FFTW_SYMBOL_PREFIX@codelet_t1fuv_7 -+@FFTW_SYMBOL_PREFIX@codelet_t1fuv_8 -+@FFTW_SYMBOL_PREFIX@codelet_t1fuv_9 -+@FFTW_SYMBOL_PREFIX@codelet_t1fv_10 -+@FFTW_SYMBOL_PREFIX@codelet_t1fv_12 -+@FFTW_SYMBOL_PREFIX@codelet_t1fv_15 -+@FFTW_SYMBOL_PREFIX@codelet_t1fv_16 -+@FFTW_SYMBOL_PREFIX@codelet_t1fv_2 -+@FFTW_SYMBOL_PREFIX@codelet_t1fv_20 -+@FFTW_SYMBOL_PREFIX@codelet_t1fv_25 -+@FFTW_SYMBOL_PREFIX@codelet_t1fv_3 -+@FFTW_SYMBOL_PREFIX@codelet_t1fv_32 -+@FFTW_SYMBOL_PREFIX@codelet_t1fv_4 -+@FFTW_SYMBOL_PREFIX@codelet_t1fv_5 -+@FFTW_SYMBOL_PREFIX@codelet_t1fv_6 -+@FFTW_SYMBOL_PREFIX@codelet_t1fv_64 -+@FFTW_SYMBOL_PREFIX@codelet_t1fv_7 -+@FFTW_SYMBOL_PREFIX@codelet_t1fv_8 -+@FFTW_SYMBOL_PREFIX@codelet_t1fv_9 -+@FFTW_SYMBOL_PREFIX@codelet_t1sv_16 -+@FFTW_SYMBOL_PREFIX@codelet_t1sv_2 -+@FFTW_SYMBOL_PREFIX@codelet_t1sv_32 -+@FFTW_SYMBOL_PREFIX@codelet_t1sv_4 -+@FFTW_SYMBOL_PREFIX@codelet_t1sv_8 -+@FFTW_SYMBOL_PREFIX@codelet_t2_10 -+@FFTW_SYMBOL_PREFIX@codelet_t2_16 -+@FFTW_SYMBOL_PREFIX@codelet_t2_20 -+@FFTW_SYMBOL_PREFIX@codelet_t2_25 -+@FFTW_SYMBOL_PREFIX@codelet_t2_32 -+@FFTW_SYMBOL_PREFIX@codelet_t2_4 -+@FFTW_SYMBOL_PREFIX@codelet_t2_5 -+@FFTW_SYMBOL_PREFIX@codelet_t2_64 -+@FFTW_SYMBOL_PREFIX@codelet_t2_8 -+@FFTW_SYMBOL_PREFIX@codelet_t2bv_10 -+@FFTW_SYMBOL_PREFIX@codelet_t2bv_16 -+@FFTW_SYMBOL_PREFIX@codelet_t2bv_2 -+@FFTW_SYMBOL_PREFIX@codelet_t2bv_20 -+@FFTW_SYMBOL_PREFIX@codelet_t2bv_25 -+@FFTW_SYMBOL_PREFIX@codelet_t2bv_32 -+@FFTW_SYMBOL_PREFIX@codelet_t2bv_4 -+@FFTW_SYMBOL_PREFIX@codelet_t2bv_5 -+@FFTW_SYMBOL_PREFIX@codelet_t2bv_64 -+@FFTW_SYMBOL_PREFIX@codelet_t2bv_8 -+@FFTW_SYMBOL_PREFIX@codelet_t2fv_10 -+@FFTW_SYMBOL_PREFIX@codelet_t2fv_16 -+@FFTW_SYMBOL_PREFIX@codelet_t2fv_2 -+@FFTW_SYMBOL_PREFIX@codelet_t2fv_20 -+@FFTW_SYMBOL_PREFIX@codelet_t2fv_25 -+@FFTW_SYMBOL_PREFIX@codelet_t2fv_32 -+@FFTW_SYMBOL_PREFIX@codelet_t2fv_4 -+@FFTW_SYMBOL_PREFIX@codelet_t2fv_5 -+@FFTW_SYMBOL_PREFIX@codelet_t2fv_64 -+@FFTW_SYMBOL_PREFIX@codelet_t2fv_8 -+@FFTW_SYMBOL_PREFIX@codelet_t2sv_16 -+@FFTW_SYMBOL_PREFIX@codelet_t2sv_32 -+@FFTW_SYMBOL_PREFIX@codelet_t2sv_4 -+@FFTW_SYMBOL_PREFIX@codelet_t2sv_8 -+@FFTW_SYMBOL_PREFIX@codelet_t3bv_10 -+@FFTW_SYMBOL_PREFIX@codelet_t3bv_16 -+@FFTW_SYMBOL_PREFIX@codelet_t3bv_20 -+@FFTW_SYMBOL_PREFIX@codelet_t3bv_25 -+@FFTW_SYMBOL_PREFIX@codelet_t3bv_32 -+@FFTW_SYMBOL_PREFIX@codelet_t3bv_4 -+@FFTW_SYMBOL_PREFIX@codelet_t3bv_5 -+@FFTW_SYMBOL_PREFIX@codelet_t3bv_8 -+@FFTW_SYMBOL_PREFIX@codelet_t3fv_10 -+@FFTW_SYMBOL_PREFIX@codelet_t3fv_16 -+@FFTW_SYMBOL_PREFIX@codelet_t3fv_20 -+@FFTW_SYMBOL_PREFIX@codelet_t3fv_25 -+@FFTW_SYMBOL_PREFIX@codelet_t3fv_32 -+@FFTW_SYMBOL_PREFIX@codelet_t3fv_4 -+@FFTW_SYMBOL_PREFIX@codelet_t3fv_5 -+@FFTW_SYMBOL_PREFIX@codelet_t3fv_8 -+@FFTW_SYMBOL_PREFIX@compute_tilesz -+@FFTW_SYMBOL_PREFIX@configure_planner -+@FFTW_SYMBOL_PREFIX@cpy1d -+@FFTW_SYMBOL_PREFIX@cpy2d -+@FFTW_SYMBOL_PREFIX@cpy2d_ci -+@FFTW_SYMBOL_PREFIX@cpy2d_co -+@FFTW_SYMBOL_PREFIX@cpy2d_pair -+@FFTW_SYMBOL_PREFIX@cpy2d_pair_ci -+@FFTW_SYMBOL_PREFIX@cpy2d_pair_co -+@FFTW_SYMBOL_PREFIX@cpy2d_tiled -+@FFTW_SYMBOL_PREFIX@cpy2d_tiledbuf -+@FFTW_SYMBOL_PREFIX@ct_applicable -+@FFTW_SYMBOL_PREFIX@ct_generic_register -+@FFTW_SYMBOL_PREFIX@ct_genericbuf_register -+@FFTW_SYMBOL_PREFIX@ct_uglyp -+@FFTW_SYMBOL_PREFIX@destroy_plan -+@FFTW_SYMBOL_PREFIX@dft_bluestein_register -+@FFTW_SYMBOL_PREFIX@dft_buffered_register -+@FFTW_SYMBOL_PREFIX@dft_conf_standard -+@FFTW_SYMBOL_PREFIX@dft_generic_register -+@FFTW_SYMBOL_PREFIX@dft_indirect_register -+@FFTW_SYMBOL_PREFIX@dft_indirect_transpose_register -+@FFTW_SYMBOL_PREFIX@dft_nop_register -+@FFTW_SYMBOL_PREFIX@dft_r2hc_register -+@FFTW_SYMBOL_PREFIX@dft_rader_register -+@FFTW_SYMBOL_PREFIX@dft_rank_geq2_register -+@FFTW_SYMBOL_PREFIX@dft_solve -+@FFTW_SYMBOL_PREFIX@dft_thr_vrank_geq1_register -+@FFTW_SYMBOL_PREFIX@dft_vrank_geq1_register -+@FFTW_SYMBOL_PREFIX@dft_zerotens -+@FFTW_SYMBOL_PREFIX@dht_r2hc_register -+@FFTW_SYMBOL_PREFIX@dht_rader_register -+@FFTW_SYMBOL_PREFIX@dimcmp -+@FFTW_SYMBOL_PREFIX@elapsed_since -+@FFTW_SYMBOL_PREFIX@estimate_cost -+@FFTW_SYMBOL_PREFIX@execute -+@FFTW_SYMBOL_PREFIX@execute_dft -+@FFTW_SYMBOL_PREFIX@execute_dft_c2r -+@FFTW_SYMBOL_PREFIX@execute_dft_r2c -+@FFTW_SYMBOL_PREFIX@execute_r2r -+@FFTW_SYMBOL_PREFIX@execute_split_dft -+@FFTW_SYMBOL_PREFIX@execute_split_dft_c2r -+@FFTW_SYMBOL_PREFIX@execute_split_dft_r2c -+@FFTW_SYMBOL_PREFIX@export_wisdom -+@FFTW_SYMBOL_PREFIX@export_wisdom_to_file -+@FFTW_SYMBOL_PREFIX@export_wisdom_to_string -+@FFTW_SYMBOL_PREFIX@extract_reim -+@FFTW_SYMBOL_PREFIX@factors_into -+@FFTW_SYMBOL_PREFIX@find_generator -+@FFTW_SYMBOL_PREFIX@first_divisor -+@FFTW_SYMBOL_PREFIX@flops -+@FFTW_SYMBOL_PREFIX@forget_wisdom -+@FFTW_SYMBOL_PREFIX@fprint_plan -+@FFTW_SYMBOL_PREFIX@free -+@FFTW_SYMBOL_PREFIX@get_crude_time -+@FFTW_SYMBOL_PREFIX@guru64_kosherp -+@FFTW_SYMBOL_PREFIX@guru_kosherp -+@FFTW_SYMBOL_PREFIX@hash -+@FFTW_SYMBOL_PREFIX@have_sse -+@FFTW_SYMBOL_PREFIX@hc2c_applicable -+@FFTW_SYMBOL_PREFIX@hc2hc_applicable -+@FFTW_SYMBOL_PREFIX@hc2hc_generic_register -+@FFTW_SYMBOL_PREFIX@iabs -+@FFTW_SYMBOL_PREFIX@iestimate_cost -+@FFTW_SYMBOL_PREFIX@ifree -+@FFTW_SYMBOL_PREFIX@ifree0 -+@FFTW_SYMBOL_PREFIX@imax -+@FFTW_SYMBOL_PREFIX@imin -+@FFTW_SYMBOL_PREFIX@import_system_wisdom -+@FFTW_SYMBOL_PREFIX@import_wisdom -+@FFTW_SYMBOL_PREFIX@import_wisdom_from_file -+@FFTW_SYMBOL_PREFIX@import_wisdom_from_string -+@FFTW_SYMBOL_PREFIX@init_threads -+@FFTW_SYMBOL_PREFIX@is_prime -+@FFTW_SYMBOL_PREFIX@isqrt -+@FFTW_SYMBOL_PREFIX@ithreads_init -+@FFTW_SYMBOL_PREFIX@join_taint -+@FFTW_SYMBOL_PREFIX@kdft_dif_register -+@FFTW_SYMBOL_PREFIX@kdft_difsq_register -+@FFTW_SYMBOL_PREFIX@kdft_dit_register -+@FFTW_SYMBOL_PREFIX@kdft_register -+@FFTW_SYMBOL_PREFIX@kernel_free -+@FFTW_SYMBOL_PREFIX@kernel_malloc -+@FFTW_SYMBOL_PREFIX@khc2c_register -+@FFTW_SYMBOL_PREFIX@khc2hc_register -+@FFTW_SYMBOL_PREFIX@kr2c_register -+@FFTW_SYMBOL_PREFIX@kr2r_register -+@FFTW_SYMBOL_PREFIX@malloc -+@FFTW_SYMBOL_PREFIX@malloc_plain -+@FFTW_SYMBOL_PREFIX@many_kosherp -+@FFTW_SYMBOL_PREFIX@map_r2r_kind -+@FFTW_SYMBOL_PREFIX@mapflags -+@FFTW_SYMBOL_PREFIX@md5INT -+@FFTW_SYMBOL_PREFIX@md5begin -+@FFTW_SYMBOL_PREFIX@md5end -+@FFTW_SYMBOL_PREFIX@md5int -+@FFTW_SYMBOL_PREFIX@md5putb -+@FFTW_SYMBOL_PREFIX@md5putc -+@FFTW_SYMBOL_PREFIX@md5puts -+@FFTW_SYMBOL_PREFIX@md5unsigned -+@FFTW_SYMBOL_PREFIX@measure_execution_time -+@FFTW_SYMBOL_PREFIX@mkapiplan -+@FFTW_SYMBOL_PREFIX@mkplan -+@FFTW_SYMBOL_PREFIX@mkplan_d -+@FFTW_SYMBOL_PREFIX@mkplan_dft -+@FFTW_SYMBOL_PREFIX@mkplan_dftw -+@FFTW_SYMBOL_PREFIX@mkplan_f_d -+@FFTW_SYMBOL_PREFIX@mkplan_hc2c -+@FFTW_SYMBOL_PREFIX@mkplan_hc2hc -+@FFTW_SYMBOL_PREFIX@mkplan_rdft -+@FFTW_SYMBOL_PREFIX@mkplan_rdft2 -+@FFTW_SYMBOL_PREFIX@mkplanner -+@FFTW_SYMBOL_PREFIX@mkprinter -+@FFTW_SYMBOL_PREFIX@mkprinter_file -+@FFTW_SYMBOL_PREFIX@mkproblem -+@FFTW_SYMBOL_PREFIX@mkproblem_dft -+@FFTW_SYMBOL_PREFIX@mkproblem_dft_d -+@FFTW_SYMBOL_PREFIX@mkproblem_rdft -+@FFTW_SYMBOL_PREFIX@mkproblem_rdft2 -+@FFTW_SYMBOL_PREFIX@mkproblem_rdft2_d -+@FFTW_SYMBOL_PREFIX@mkproblem_rdft2_d_3pointers -+@FFTW_SYMBOL_PREFIX@mkproblem_rdft_0_d -+@FFTW_SYMBOL_PREFIX@mkproblem_rdft_1 -+@FFTW_SYMBOL_PREFIX@mkproblem_rdft_1_d -+@FFTW_SYMBOL_PREFIX@mkproblem_rdft_d -+@FFTW_SYMBOL_PREFIX@mkproblem_unsolvable -+@FFTW_SYMBOL_PREFIX@mkscanner -+@FFTW_SYMBOL_PREFIX@mksolver -+@FFTW_SYMBOL_PREFIX@mksolver_ct -+@FFTW_SYMBOL_PREFIX@mksolver_ct_threads -+@FFTW_SYMBOL_PREFIX@mksolver_dft_direct -+@FFTW_SYMBOL_PREFIX@mksolver_dft_directbuf -+@FFTW_SYMBOL_PREFIX@mksolver_hc2c -+@FFTW_SYMBOL_PREFIX@mksolver_hc2hc -+@FFTW_SYMBOL_PREFIX@mksolver_hc2hc_threads -+@FFTW_SYMBOL_PREFIX@mksolver_rdft2_direct -+@FFTW_SYMBOL_PREFIX@mksolver_rdft_r2c_direct -+@FFTW_SYMBOL_PREFIX@mksolver_rdft_r2c_directbuf -+@FFTW_SYMBOL_PREFIX@mksolver_rdft_r2r_direct -+@FFTW_SYMBOL_PREFIX@mkstride -+@FFTW_SYMBOL_PREFIX@mktensor -+@FFTW_SYMBOL_PREFIX@mktensor_0d -+@FFTW_SYMBOL_PREFIX@mktensor_1d -+@FFTW_SYMBOL_PREFIX@mktensor_2d -+@FFTW_SYMBOL_PREFIX@mktensor_3d -+@FFTW_SYMBOL_PREFIX@mktensor_4d -+@FFTW_SYMBOL_PREFIX@mktensor_5d -+@FFTW_SYMBOL_PREFIX@mktensor_iodims -+@FFTW_SYMBOL_PREFIX@mktensor_iodims64 -+@FFTW_SYMBOL_PREFIX@mktensor_rowmajor -+@FFTW_SYMBOL_PREFIX@mktriggen -+@FFTW_SYMBOL_PREFIX@modulo -+@FFTW_SYMBOL_PREFIX@nbuf -+@FFTW_SYMBOL_PREFIX@nbuf_redundant -+@FFTW_SYMBOL_PREFIX@next_prime -+@FFTW_SYMBOL_PREFIX@null_awake -+@FFTW_SYMBOL_PREFIX@ops_add -+@FFTW_SYMBOL_PREFIX@ops_add2 -+@FFTW_SYMBOL_PREFIX@ops_cpy -+@FFTW_SYMBOL_PREFIX@ops_madd -+@FFTW_SYMBOL_PREFIX@ops_madd2 -+@FFTW_SYMBOL_PREFIX@ops_other -+@FFTW_SYMBOL_PREFIX@ops_zero -+@FFTW_SYMBOL_PREFIX@pickdim -+@FFTW_SYMBOL_PREFIX@plan_awake -+@FFTW_SYMBOL_PREFIX@plan_destroy_internal -+@FFTW_SYMBOL_PREFIX@plan_dft -+@FFTW_SYMBOL_PREFIX@plan_dft_1d -+@FFTW_SYMBOL_PREFIX@plan_dft_2d -+@FFTW_SYMBOL_PREFIX@plan_dft_3d -+@FFTW_SYMBOL_PREFIX@plan_dft_c2r -+@FFTW_SYMBOL_PREFIX@plan_dft_c2r_1d -+@FFTW_SYMBOL_PREFIX@plan_dft_c2r_2d -+@FFTW_SYMBOL_PREFIX@plan_dft_c2r_3d -+@FFTW_SYMBOL_PREFIX@plan_dft_r2c -+@FFTW_SYMBOL_PREFIX@plan_dft_r2c_1d -+@FFTW_SYMBOL_PREFIX@plan_dft_r2c_2d -+@FFTW_SYMBOL_PREFIX@plan_dft_r2c_3d -+@FFTW_SYMBOL_PREFIX@plan_guru64_dft -+@FFTW_SYMBOL_PREFIX@plan_guru64_dft_c2r -+@FFTW_SYMBOL_PREFIX@plan_guru64_dft_r2c -+@FFTW_SYMBOL_PREFIX@plan_guru64_r2r -+@FFTW_SYMBOL_PREFIX@plan_guru64_split_dft -+@FFTW_SYMBOL_PREFIX@plan_guru64_split_dft_c2r -+@FFTW_SYMBOL_PREFIX@plan_guru64_split_dft_r2c -+@FFTW_SYMBOL_PREFIX@plan_guru_dft -+@FFTW_SYMBOL_PREFIX@plan_guru_dft_c2r -+@FFTW_SYMBOL_PREFIX@plan_guru_dft_r2c -+@FFTW_SYMBOL_PREFIX@plan_guru_r2r -+@FFTW_SYMBOL_PREFIX@plan_guru_split_dft -+@FFTW_SYMBOL_PREFIX@plan_guru_split_dft_c2r -+@FFTW_SYMBOL_PREFIX@plan_guru_split_dft_r2c -+@FFTW_SYMBOL_PREFIX@plan_many_dft -+@FFTW_SYMBOL_PREFIX@plan_many_dft_c2r -+@FFTW_SYMBOL_PREFIX@plan_many_dft_r2c -+@FFTW_SYMBOL_PREFIX@plan_many_r2r -+@FFTW_SYMBOL_PREFIX@plan_null_destroy -+@FFTW_SYMBOL_PREFIX@plan_r2r -+@FFTW_SYMBOL_PREFIX@plan_r2r_1d -+@FFTW_SYMBOL_PREFIX@plan_r2r_2d -+@FFTW_SYMBOL_PREFIX@plan_r2r_3d -+@FFTW_SYMBOL_PREFIX@plan_with_nthreads -+@FFTW_SYMBOL_PREFIX@planner_destroy -+@FFTW_SYMBOL_PREFIX@power_mod -+@FFTW_SYMBOL_PREFIX@print_plan -+@FFTW_SYMBOL_PREFIX@printer_destroy -+@FFTW_SYMBOL_PREFIX@problem_destroy -+@FFTW_SYMBOL_PREFIX@rader_tl_delete -+@FFTW_SYMBOL_PREFIX@rader_tl_find -+@FFTW_SYMBOL_PREFIX@rader_tl_insert -+@FFTW_SYMBOL_PREFIX@rdft2_buffered_register -+@FFTW_SYMBOL_PREFIX@rdft2_complex_n -+@FFTW_SYMBOL_PREFIX@rdft2_inplace_strides -+@FFTW_SYMBOL_PREFIX@rdft2_nop_register -+@FFTW_SYMBOL_PREFIX@rdft2_pad -+@FFTW_SYMBOL_PREFIX@rdft2_rank0_register -+@FFTW_SYMBOL_PREFIX@rdft2_rank_geq2_register -+@FFTW_SYMBOL_PREFIX@rdft2_rdft_register -+@FFTW_SYMBOL_PREFIX@rdft2_solve -+@FFTW_SYMBOL_PREFIX@rdft2_strides -+@FFTW_SYMBOL_PREFIX@rdft2_tensor_max_index -+@FFTW_SYMBOL_PREFIX@rdft2_thr_vrank_geq1_register -+@FFTW_SYMBOL_PREFIX@rdft2_vrank_geq1_register -+@FFTW_SYMBOL_PREFIX@rdft_buffered_register -+@FFTW_SYMBOL_PREFIX@rdft_conf_standard -+@FFTW_SYMBOL_PREFIX@rdft_dht_register -+@FFTW_SYMBOL_PREFIX@rdft_generic_register -+@FFTW_SYMBOL_PREFIX@rdft_indirect_register -+@FFTW_SYMBOL_PREFIX@rdft_kind_str -+@FFTW_SYMBOL_PREFIX@rdft_nop_register -+@FFTW_SYMBOL_PREFIX@rdft_rank0_register -+@FFTW_SYMBOL_PREFIX@rdft_rank_geq2_register -+@FFTW_SYMBOL_PREFIX@rdft_solve -+@FFTW_SYMBOL_PREFIX@rdft_thr_vrank_geq1_register -+@FFTW_SYMBOL_PREFIX@rdft_vrank3_transpose_register -+@FFTW_SYMBOL_PREFIX@rdft_vrank_geq1_register -+@FFTW_SYMBOL_PREFIX@rdft_zerotens -+@FFTW_SYMBOL_PREFIX@redft00e_r2hc_pad_register -+@FFTW_SYMBOL_PREFIX@regsolver_ct_directw -+@FFTW_SYMBOL_PREFIX@regsolver_ct_directwsq -+@FFTW_SYMBOL_PREFIX@regsolver_hc2c_direct -+@FFTW_SYMBOL_PREFIX@regsolver_hc2hc_direct -+@FFTW_SYMBOL_PREFIX@reodft00e_splitradix_register -+@FFTW_SYMBOL_PREFIX@reodft010e_r2hc_register -+@FFTW_SYMBOL_PREFIX@reodft11e_r2hc_odd_register -+@FFTW_SYMBOL_PREFIX@reodft11e_radix2_r2hc_register -+@FFTW_SYMBOL_PREFIX@reodft_conf_standard -+@FFTW_SYMBOL_PREFIX@rodft00e_r2hc_pad_register -+@FFTW_SYMBOL_PREFIX@safe_mulmod -+@FFTW_SYMBOL_PREFIX@scanner_destroy -+@FFTW_SYMBOL_PREFIX@set_timelimit -+@FFTW_SYMBOL_PREFIX@solver_destroy -+@FFTW_SYMBOL_PREFIX@solver_register -+@FFTW_SYMBOL_PREFIX@solver_use -+@FFTW_SYMBOL_PREFIX@solvtab_exec -+@FFTW_SYMBOL_PREFIX@spawn_loop -+@FFTW_SYMBOL_PREFIX@stride_destroy -+@FFTW_SYMBOL_PREFIX@taint -+@FFTW_SYMBOL_PREFIX@tensor_append -+@FFTW_SYMBOL_PREFIX@tensor_compress -+@FFTW_SYMBOL_PREFIX@tensor_compress_contiguous -+@FFTW_SYMBOL_PREFIX@tensor_copy -+@FFTW_SYMBOL_PREFIX@tensor_copy_except -+@FFTW_SYMBOL_PREFIX@tensor_copy_inplace -+@FFTW_SYMBOL_PREFIX@tensor_copy_sub -+@FFTW_SYMBOL_PREFIX@tensor_destroy -+@FFTW_SYMBOL_PREFIX@tensor_destroy2 -+@FFTW_SYMBOL_PREFIX@tensor_destroy4 -+@FFTW_SYMBOL_PREFIX@tensor_equal -+@FFTW_SYMBOL_PREFIX@tensor_inplace_locations -+@FFTW_SYMBOL_PREFIX@tensor_inplace_strides -+@FFTW_SYMBOL_PREFIX@tensor_inplace_strides2 -+@FFTW_SYMBOL_PREFIX@tensor_kosherp -+@FFTW_SYMBOL_PREFIX@tensor_max_index -+@FFTW_SYMBOL_PREFIX@tensor_md5 -+@FFTW_SYMBOL_PREFIX@tensor_min_istride -+@FFTW_SYMBOL_PREFIX@tensor_min_ostride -+@FFTW_SYMBOL_PREFIX@tensor_min_stride -+@FFTW_SYMBOL_PREFIX@tensor_print -+@FFTW_SYMBOL_PREFIX@tensor_split -+@FFTW_SYMBOL_PREFIX@tensor_strides_decrease -+@FFTW_SYMBOL_PREFIX@tensor_sz -+@FFTW_SYMBOL_PREFIX@tensor_tornk1 -+@FFTW_SYMBOL_PREFIX@the_planner -+@FFTW_SYMBOL_PREFIX@threads_cleanup -+@FFTW_SYMBOL_PREFIX@threads_conf_standard -+@FFTW_SYMBOL_PREFIX@tile2d -+@FFTW_SYMBOL_PREFIX@toobig -+@FFTW_SYMBOL_PREFIX@transpose -+@FFTW_SYMBOL_PREFIX@transpose_tiled -+@FFTW_SYMBOL_PREFIX@transpose_tiledbuf -+@FFTW_SYMBOL_PREFIX@triggen_destroy -+@FFTW_SYMBOL_PREFIX@twiddle_awake -+@FFTW_SYMBOL_PREFIX@twiddle_length -+;sfftw_cleanup_ -+;sfftw_cleanup__ -+;sfftw_cleanup_threads_ -+;sfftw_cleanup_threads__ -+;sfftw_destroy_plan_ -+;sfftw_destroy_plan__ -+;sfftw_execute_ -+;sfftw_execute__ -+;sfftw_execute_dft_ -+;sfftw_execute_dft__ -+;sfftw_execute_dft_c2r_ -+;sfftw_execute_dft_c2r__ -+;sfftw_execute_dft_r2c_ -+;sfftw_execute_dft_r2c__ -+;sfftw_execute_r2r_ -+;sfftw_execute_r2r__ -+;sfftw_execute_split_dft_ -+;sfftw_execute_split_dft__ -+;sfftw_execute_split_dft_c2r_ -+;sfftw_execute_split_dft_c2r__ -+;sfftw_execute_split_dft_r2c_ -+;sfftw_execute_split_dft_r2c__ -+;sfftw_export_wisdom_ -+;sfftw_export_wisdom__ -+;sfftw_flops_ -+;sfftw_flops__ -+;sfftw_forget_wisdom_ -+;sfftw_forget_wisdom__ -+;sfftw_import_system_wisdom_ -+;sfftw_import_system_wisdom__ -+;sfftw_import_wisdom_ -+;sfftw_import_wisdom__ -+;sfftw_init_threads_ -+;sfftw_init_threads__ -+;sfftw_plan_dft_ -+;sfftw_plan_dft_1d_ -+;sfftw_plan_dft_1d__ -+;sfftw_plan_dft_2d_ -+;sfftw_plan_dft_2d__ -+;sfftw_plan_dft_3d_ -+;sfftw_plan_dft_3d__ -+;sfftw_plan_dft__ -+;sfftw_plan_dft_c2r_ -+;sfftw_plan_dft_c2r_1d_ -+;sfftw_plan_dft_c2r_1d__ -+;sfftw_plan_dft_c2r_2d_ -+;sfftw_plan_dft_c2r_2d__ -+;sfftw_plan_dft_c2r_3d_ -+;sfftw_plan_dft_c2r_3d__ -+;sfftw_plan_dft_c2r__ -+;sfftw_plan_dft_r2c_ -+;sfftw_plan_dft_r2c_1d_ -+;sfftw_plan_dft_r2c_1d__ -+;sfftw_plan_dft_r2c_2d_ -+;sfftw_plan_dft_r2c_2d__ -+;sfftw_plan_dft_r2c_3d_ -+;sfftw_plan_dft_r2c_3d__ -+;sfftw_plan_dft_r2c__ -+;sfftw_plan_guru_dft_ -+;sfftw_plan_guru_dft__ -+;sfftw_plan_guru_dft_c2r_ -+;sfftw_plan_guru_dft_c2r__ -+;sfftw_plan_guru_dft_r2c_ -+;sfftw_plan_guru_dft_r2c__ -+;sfftw_plan_guru_r2r_ -+;sfftw_plan_guru_r2r__ -+;sfftw_plan_guru_split_dft_ -+;sfftw_plan_guru_split_dft__ -+;sfftw_plan_guru_split_dft_c2r_ -+;sfftw_plan_guru_split_dft_c2r__ -+;sfftw_plan_guru_split_dft_r2c_ -+;sfftw_plan_guru_split_dft_r2c__ -+;sfftw_plan_many_dft_ -+;sfftw_plan_many_dft__ -+;sfftw_plan_many_dft_c2r_ -+;sfftw_plan_many_dft_c2r__ -+;sfftw_plan_many_dft_r2c_ -+;sfftw_plan_many_dft_r2c__ -+;sfftw_plan_many_r2r_ -+;sfftw_plan_many_r2r__ -+;sfftw_plan_r2r_ -+;sfftw_plan_r2r_1d_ -+;sfftw_plan_r2r_1d__ -+;sfftw_plan_r2r_2d_ -+;sfftw_plan_r2r_2d__ -+;sfftw_plan_r2r_3d_ -+;sfftw_plan_r2r_3d__ -+;sfftw_plan_r2r__ -+;sfftw_plan_with_nthreads_ -+;sfftw_plan_with_nthreads__ -+;sfftw_print_plan_ -+;sfftw_print_plan__ -diff -Nru fftw-3.2.2.orig/kernel/CMakeLists.txt fftw-3.2.2/kernel/CMakeLists.txt ---- fftw-3.2.2.orig/kernel/CMakeLists.txt 1970-01-01 01:00:00.000000000 +0100 -+++ fftw-3.2.2/kernel/CMakeLists.txt 2011-12-21 11:36:18.188872000 +0100 -@@ -0,0 +1,17 @@ -+# pkgincludedir = $(includedir)/fftw3@PREC_SUFFIX@ -+# pkginclude_HEADERS = ifftw.h cycle.h -+ -+set(kernel_SRCS align.c alloc.c assert.c awake.c buffered.c -+cpy1d.c cpy2d-pair.c cpy2d.c ct.c debug.c extract-reim.c hash.c iabs.c -+kalloc.c md5-1.c md5.c minmax.c ops.c pickdim.c plan.c planner.c -+primes.c print.c problem.c rader.c scan.c solver.c solvtab.c stride.c -+tensor.c tensor1.c tensor2.c tensor3.c tensor4.c tensor5.c tensor7.c -+tensor8.c tensor9.c tile2d.c timer.c transpose.c trig.c twiddle.c -+) -+ -+if(BUILD_ALL_STATIC) -+ add_library(kernel STATIC ${kernel_SRCS}) -+else(BUILD_ALL_STATIC) -+ prepend_prefix(kernel_SRCS) -+ set(fftw_SRCS ${fftw_SRCS} ${kernel_SRCS} PARENT_SCOPE) -+endif(BUILD_ALL_STATIC) -diff -Nru fftw-3.2.2.orig/libbench2/CMakeLists.txt fftw-3.2.2/libbench2/CMakeLists.txt ---- fftw-3.2.2.orig/libbench2/CMakeLists.txt 1970-01-01 01:00:00.000000000 +0100 -+++ fftw-3.2.2/libbench2/CMakeLists.txt 2011-12-21 11:36:18.190872200 +0100 -@@ -0,0 +1,11 @@ -+set(bench2_SRCS after-ccopy-from.c after-ccopy-to.c -+after-hccopy-from.c after-hccopy-to.c after-rcopy-from.c -+after-rcopy-to.c aligned-main.c allocate.c aset.c -+bench-cost-postprocess.c bench-exit.c bench-main.c can-do.c caset.c -+dotens2.c info.c main.c mflops.c mp.c ovtpvt.c pow2.c problem.c -+report.c speed.c tensor.c timer.c useropt.c util.c verify-dft.c -+verify-lib.c verify-r2r.c verify-rdft2.c verify.c zero.c -+my-getopt.c -+) -+ -+add_library(bench2 STATIC ${bench2_SRCS}) -diff -Nru fftw-3.2.2.orig/rdft/CMakeLists.txt fftw-3.2.2/rdft/CMakeLists.txt ---- fftw-3.2.2.orig/rdft/CMakeLists.txt 1970-01-01 01:00:00.000000000 +0100 -+++ fftw-3.2.2/rdft/CMakeLists.txt 2011-12-21 11:36:18.192872400 +0100 -@@ -0,0 +1,23 @@ -+add_subdirectory(scalar) -+add_subdirectory(simd) -+ -+ -+set(RDFT2_SRCS buffered2.c direct2.c nop2.c rank0-rdft2.c rank-geq2-rdft2.c -+plan2.c problem2.c solve2.c vrank-geq1-rdft2.c rdft2-rdft.c -+rdft2-tensor-max-index.c rdft2-inplace-strides.c rdft2-strides.c -+khc2c.c ct-hc2c.c ct-hc2c-direct.c -+) -+ -+set(rdft_SRCS hc2hc.c dft-r2hc.c dht-r2hc.c dht-rader.c -+buffered.c conf.c direct-r2r.c direct-r2c.c generic.c -+hc2hc-direct.c hc2hc-generic.c khc2hc.c kr2c.c kr2r.c indirect.c nop.c -+plan.c problem.c rank0.c rank-geq2.c rdft-dht.c solve.c -+vrank-geq1.c vrank3-transpose.c ${RDFT2_SRCS} -+) -+ -+if(BUILD_ALL_STATIC) -+ add_library(rdft STATIC ${rdft_SRCS}) -+else(BUILD_ALL_STATIC) -+ prepend_prefix(rdft_SRCS) -+ set(fftw_SRCS ${fftw_SRCS} ${rdft_SRCS} ${_fftw_SRCS} PARENT_SCOPE) -+endif(BUILD_ALL_STATIC) -diff -Nru fftw-3.2.2.orig/rdft/scalar/CMakeLists.txt fftw-3.2.2/rdft/scalar/CMakeLists.txt ---- fftw-3.2.2.orig/rdft/scalar/CMakeLists.txt 1970-01-01 01:00:00.000000000 +0100 -+++ fftw-3.2.2/rdft/scalar/CMakeLists.txt 2011-12-21 11:36:18.194872600 +0100 -@@ -0,0 +1,12 @@ -+add_subdirectory(r2cb) -+add_subdirectory(r2cf) -+add_subdirectory(r2r) -+ -+set(rdft_scalar_SRCS ${rdft_scalar_SRCS} hfb.c r2c.c r2r.c hc2c.c) -+ -+if(BUILD_ALL_STATIC) -+ add_library(rdft_scalar STATIC ${rdft_scalar_SRCS}) -+else(BUILD_ALL_STATIC) -+ prepend_prefix(rdft_scalar_SRCS) -+ set(_fftw_SRCS ${_fftw_SRCS} ${rdft_scalar_SRCS} ${__fftw_SRCS} PARENT_SCOPE) -+endif(BUILD_ALL_STATIC) -diff -Nru fftw-3.2.2.orig/rdft/scalar/r2cb/CMakeLists.txt fftw-3.2.2/rdft/scalar/r2cb/CMakeLists.txt ---- fftw-3.2.2.orig/rdft/scalar/r2cb/CMakeLists.txt 1970-01-01 01:00:00.000000000 +0100 -+++ fftw-3.2.2/rdft/scalar/r2cb/CMakeLists.txt 2011-12-21 11:36:18.196872800 +0100 -@@ -0,0 +1,126 @@ -+# This Makefile.am specifies a set of codelets, efficient transforms -+# of small sizes, that are used as building blocks (kernels) by FFTW -+# to build up large transforms, as well as the options for generating -+# and compiling them. -+ -+# You can customize FFTW for special needs, e.g. to handle certain -+# sizes more efficiently, by adding new codelets to the lists of those -+# included by default. If you change the list of codelets, any new -+# ones you added will be automatically generated when you run the -+# bootstrap script (see "Generating your own code" in the FFTW -+# manual). -+ -+########################################################################### -+# r2cb_ is a hard-coded complex-to-real FFT of size (base cases -+# of real-output FFT recursion) -+set(R2CB r2cb_2.c r2cb_3.c r2cb_4.c r2cb_5.c r2cb_6.c r2cb_7.c r2cb_8.c -+r2cb_9.c r2cb_10.c r2cb_11.c r2cb_12.c r2cb_13.c r2cb_14.c r2cb_15.c -+r2cb_16.c r2cb_32.c r2cb_64.c r2cb_128.c r2cb_20.c r2cb_25.c -+# r2cb_30.c r2cb_40.c r2cb_50.c -+) -+ -+########################################################################### -+# hb_ is a "twiddle" FFT of size , implementing a radix-r DIF -+# step for a real-output FFT. Every hb codelet must have a -+# corresponding r2cbIII codelet (see below)! -+set(HB hb_2.c hb_3.c hb_4.c hb_5.c hb_6.c hb_7.c hb_8.c hb_9.c -+hb_10.c hb_12.c hb_15.c hb_16.c hb_32.c hb_64.c -+hb_20.c hb_25.c # hb_30.c hb_40.c hb_50.c -+) -+ -+# like hb, but generates part of its trig table on the fly (good for large n) -+set(HB2 hb2_4.c hb2_8.c hb2_16.c hb2_32.c -+hb2_5.c hb2_20.c hb2_25.c -+) -+ -+# an r2cb transform where the output is shifted by half a sample (input -+# is multiplied by a phase). This is needed as part of the DIF recursion; -+# every hb_ or hb2_ codelet should have a corresponding r2cbIII_ -+set(R2CBIII r2cbIII_2.c r2cbIII_3.c r2cbIII_4.c r2cbIII_5.c r2cbIII_6.c -+r2cbIII_7.c r2cbIII_8.c r2cbIII_9.c r2cbIII_10.c r2cbIII_12.c -+r2cbIII_15.c r2cbIII_16.c r2cbIII_32.c r2cbIII_64.c -+r2cbIII_20.c r2cbIII_25.c # r2cbIII_30.c r2cbIII_40.c r2cbIII_50.c -+) -+ -+########################################################################### -+# hc2cb_ is a "twiddle" FFT of size , implementing a radix-r DIF -+# step for a real-input FFT with rdft2-style output. must be even. -+set(HC2CB hc2cb_2.c hc2cb_4.c hc2cb_6.c hc2cb_8.c hc2cb_10.c hc2cb_12.c -+hc2cb_16.c hc2cb_32.c -+hc2cb_20.c # hc2cb_30.c -+) -+ -+set(HC2CBDFT hc2cbdft_2.c hc2cbdft_4.c hc2cbdft_6.c hc2cbdft_8.c -+hc2cbdft_10.c hc2cbdft_12.c hc2cbdft_16.c hc2cbdft_32.c -+hc2cbdft_20.c # hc2cbdft_30.c -+) -+ -+# like hc2cb, but generates part of its trig table on the fly (good -+# for large n) -+set(HC2CB2 hc2cb2_4.c hc2cb2_8.c hc2cb2_16.c hc2cb2_32.c -+hc2cb2_20.c # hc2cb2_30.c -+) -+set(HC2CBDFT2 hc2cbdft2_4.c hc2cbdft2_8.c hc2cbdft2_16.c hc2cbdft2_32.c -+hc2cbdft2_20.c # hc2cbdft2_30.c -+) -+ -+########################################################################### -+set(ALL_CODELETS ${R2CB} ${HB} ${HB2} ${R2CBIII} ${HC2CB} ${HC2CB2} ${HC2CBDFT} ${HC2CBDFT2}) -+ -+include(${CMAKE_SOURCE_DIR}/support/codelets.cmake) -+ -+set(SOLVTAB_NAME "X(solvtab_rdft_r2cb)") -+ -+set(CODLIST ${CMAKE_CURRENT_BINARY_DIR}/codlist.c) -+set(CODELET_NAME codelet_) -+ -+write_codelet_list(${CODLIST} ${CODELET_NAME} ${SOLVTAB_NAME} ${ALL_CODELETS}) -+ -+set(rdft_scalar_r2cb_SRCS ${ALL_CODELETS}) -+ -+if(BUILD_ALL_STATIC) -+ add_library(rdft_scalar_r2cb STATIC ${rdft_scalar_r2cb_SRCS}) -+else(BUILD_ALL_STATIC) -+ prepend_prefix(rdft_scalar_r2cb_SRCS) -+ set(__fftw_SRCS ${__fftw_SRCS} ${rdft_scalar_r2cb_SRCS} ${CODLIST} PARENT_SCOPE) -+endif(BUILD_ALL_STATIC) -+ -+# this is old source code which can be changed later -+# only delete codlist.c in maintainer-mode, since it is included in the dist -+# FIXME: is there a way to delete in 'make clean' only when builddir != srcdir? -+#maintainer-clean-local: -+# rm -f $(CODLIST) -+ -+#if MAINTAINER_MODE -+#FLAGS_R2CB=$(RDFT_FLAGS_COMMON) -sign 1 -+#FLAGS_HB=$(RDFT_FLAGS_COMMON) -sign 1 -+#FLAGS_HB2=$(RDFT_FLAGS_COMMON) -sign 1 -twiddle-log3 -precompute-twiddles -+#FLAGS_HC2CB=$(RDFT_FLAGS_COMMON) -sign 1 -+#FLAGS_HC2CB2=$(RDFT_FLAGS_COMMON) -sign 1 -twiddle-log3 -precompute-twiddles -+#FLAGS_R2CBIII=$(RDFT_FLAGS_COMMON) -sign 1 -+# -+#r2cb_%.c: $(CODELET_DEPS) $(GEN_R2CB) -+# ($(PRELUDE_COMMANDS_RDFT); $(TWOVERS) $(GEN_R2CB) $(FLAGS_R2CB) -n $* -name r2cb_$* -include "r2cb.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#hb_%.c: $(CODELET_DEPS) $(GEN_HC2HC) -+# ($(PRELUDE_COMMANDS_RDFT); $(TWOVERS) $(GEN_HC2HC) $(FLAGS_HB) -n $* -dif -name hb_$* -include "hb.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#hb2_%.c: $(CODELET_DEPS) $(GEN_HC2HC) -+# ($(PRELUDE_COMMANDS_RDFT); $(TWOVERS) $(GEN_HC2HC) $(FLAGS_HB2) -n $* -dif -name hb2_$* -include "hb.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#r2cbIII_%.c: $(CODELET_DEPS) $(GEN_R2CB) -+# ($(PRELUDE_COMMANDS_RDFT); $(TWOVERS) $(GEN_R2CB) $(FLAGS_R2CB) -n $* -name r2cbIII_$* -dft-III -include "r2cbIII.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#hc2cb_%.c: $(CODELET_DEPS) $(GEN_HC2C) -+# ($(PRELUDE_COMMANDS_RDFT); $(TWOVERS) $(GEN_HC2C) $(FLAGS_HC2CB) -n $* -dif -name hc2cb_$* -include "hc2cb.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#hc2cb2_%.c: $(CODELET_DEPS) $(GEN_HC2C) -+# ($(PRELUDE_COMMANDS_RDFT); $(TWOVERS) $(GEN_HC2C) $(FLAGS_HC2CB2) -n $* -dif -name hc2cb2_$* -include "hc2cb.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#hc2cbdft_%.c: $(CODELET_DEPS) $(GEN_HC2CDFT) -+# ($(PRELUDE_COMMANDS_RDFT); $(TWOVERS) $(GEN_HC2CDFT) $(FLAGS_HC2CB) -n $* -dif -name hc2cbdft_$* -include "hc2cb.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#hc2cbdft2_%.c: $(CODELET_DEPS) $(GEN_HC2CDFT) -+# ($(PRELUDE_COMMANDS_RDFT); $(TWOVERS) $(GEN_HC2CDFT) $(FLAGS_HC2CB) -n $* -dif -name hc2cbdft2_$* -include "hc2cb.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#endif # MAINTAINER_MODE -diff -Nru fftw-3.2.2.orig/rdft/scalar/r2cf/CMakeLists.txt fftw-3.2.2/rdft/scalar/r2cf/CMakeLists.txt ---- fftw-3.2.2.orig/rdft/scalar/r2cf/CMakeLists.txt 1970-01-01 01:00:00.000000000 +0100 -+++ fftw-3.2.2/rdft/scalar/r2cf/CMakeLists.txt 2011-12-21 11:36:18.198873000 +0100 -@@ -0,0 +1,128 @@ -+# This Makefile.am specifies a set of codelets, efficient transforms -+# of small sizes, that are used as building blocks (kernels) by FFTW -+# to build up large transforms, as well as the options for generating -+# and compiling them. -+ -+# You can customize FFTW for special needs, e.g. to handle certain -+# sizes more efficiently, by adding new codelets to the lists of those -+# included by default. If you change the list of codelets, any new -+# ones you added will be automatically generated when you run the -+# bootstrap script (see "Generating your own code" in the FFTW -+# manual). -+ -+########################################################################### -+ -+########################################################################### -+# r2cf_ is a hard-coded real-to-complex FFT of size (base cases -+# of real-input FFT recursion) -+set(R2CF r2cf_2.c r2cf_3.c r2cf_4.c r2cf_5.c r2cf_6.c r2cf_7.c r2cf_8.c -+r2cf_9.c r2cf_10.c r2cf_11.c r2cf_12.c r2cf_13.c r2cf_14.c r2cf_15.c -+r2cf_16.c r2cf_32.c r2cf_64.c r2cf_128.c -+r2cf_20.c r2cf_25.c # r2cf_30.c r2cf_40.c r2cf_50.c -+) -+ -+########################################################################### -+# hf_ is a "twiddle" FFT of size , implementing a radix-r DIT -+# step for a real-input FFT. Every hf codelet must have a -+# corresponding r2cfII codelet (see below)! -+set(HF hf_2.c hf_3.c hf_4.c hf_5.c hf_6.c hf_7.c hf_8.c hf_9.c -+hf_10.c hf_12.c hf_15.c hf_16.c hf_32.c hf_64.c -+hf_20.c hf_25.c # hf_30.c hf_40.c hf_50.c -+) -+ -+# like hf, but generates part of its trig table on the fly (good for large n) -+set(HF2 hf2_4.c hf2_8.c hf2_16.c hf2_32.c -+hf2_5.c hf2_20.c hf2_25.c -+) -+ -+# an r2cf transform where the input is shifted by half a sample (output -+# is multiplied by a phase). This is needed as part of the DIT recursion; -+# every hf_ or hf2_ codelet should have a corresponding r2cfII_ -+set(R2CFII r2cfII_2.c r2cfII_3.c r2cfII_4.c r2cfII_5.c r2cfII_6.c -+r2cfII_7.c r2cfII_8.c r2cfII_9.c r2cfII_10.c r2cfII_12.c r2cfII_15.c -+r2cfII_16.c r2cfII_32.c r2cfII_64.c -+r2cfII_20.c r2cfII_25.c # r2cfII_30.c r2cfII_40.c r2cfII_50.c -+) -+ -+########################################################################### -+# hc2cf_ is a "twiddle" FFT of size , implementing a radix-r DIT -+# step for a real-input FFT with rdft2-style output. must be even. -+set(HC2CF hc2cf_2.c hc2cf_4.c hc2cf_6.c hc2cf_8.c hc2cf_10.c hc2cf_12.c -+hc2cf_16.c hc2cf_32.c -+hc2cf_20.c # hc2cf_30.c -+) -+ -+set(HC2CFDFT hc2cfdft_2.c hc2cfdft_4.c hc2cfdft_6.c hc2cfdft_8.c -+hc2cfdft_10.c hc2cfdft_12.c hc2cfdft_16.c hc2cfdft_32.c -+hc2cfdft_20.c # hc2cfdft_30.c -+) -+ -+# like hc2cf, but generates part of its trig table on the fly (good -+# for large n) -+set(HC2CF2 hc2cf2_4.c hc2cf2_8.c hc2cf2_16.c hc2cf2_32.c -+hc2cf2_20.c # hc2cf2_30.c -+) -+set(HC2CFDFT2 hc2cfdft2_4.c hc2cfdft2_8.c hc2cfdft2_16.c hc2cfdft2_32.c -+hc2cfdft2_20.c # hc2cfdft2_30.c -+) -+ -+########################################################################### -+set(ALL_CODELETS ${R2CF} ${HF} ${HF2} ${R2CFII} ${HC2CF} ${HC2CF2} ${HC2CFDFT} ${HC2CFDFT2}) -+ -+include(${CMAKE_SOURCE_DIR}/support/codelets.cmake) -+ -+set(SOLVTAB_NAME "X(solvtab_rdft_r2cf)") -+ -+set(CODLIST ${CMAKE_CURRENT_BINARY_DIR}/codlist.c) -+set(CODELET_NAME codelet_) -+ -+write_codelet_list(${CODLIST} ${CODELET_NAME} ${SOLVTAB_NAME} ${ALL_CODELETS}) -+ -+set(rdft_scalar_r2cf_SRCS ${ALL_CODELETS}) -+ -+if(BUILD_ALL_STATIC) -+ add_library(rdft_scalar_r2cf STATIC ${rdft_scalar_r2cf_SRCS}) -+else(BUILD_ALL_STATIC) -+ prepend_prefix(rdft_scalar_r2cf_SRCS) -+ set(__fftw_SRCS ${__fftw_SRCS} ${rdft_scalar_r2cf_SRCS} ${CODLIST} PARENT_SCOPE) -+endif(BUILD_ALL_STATIC) -+ -+# this is old source code which can be changed later -+# only delete codlist.c in maintainer-mode, since it is included in the dist -+# FIXME: is there a way to delete in 'make clean' only when builddir != srcdir? -+#maintainer-clean-local: -+# rm -f $(CODLIST) -+ -+#if MAINTAINER_MODE -+#FLAGS_R2CF=$(RDFT_FLAGS_COMMON) -+#FLAGS_HF=$(RDFT_FLAGS_COMMON) -+#FLAGS_HF2=$(RDFT_FLAGS_COMMON) -twiddle-log3 -precompute-twiddles -+#FLAGS_HC2CF=$(RDFT_FLAGS_COMMON) -+#FLAGS_HC2CF2=$(RDFT_FLAGS_COMMON) -twiddle-log3 -precompute-twiddles -+#FLAGS_R2CFII=$(RDFT_FLAGS_COMMON) -+# -+#r2cf_%.c: $(CODELET_DEPS) $(GEN_R2CF) -+# ($(PRELUDE_COMMANDS_RDFT); $(TWOVERS) $(GEN_R2CF) $(FLAGS_R2CF) -n $* -name r2cf_$* -include "r2cf.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#hf_%.c: $(CODELET_DEPS) $(GEN_HC2HC) -+# ($(PRELUDE_COMMANDS_RDFT); $(TWOVERS) $(GEN_HC2HC) $(FLAGS_HF) -n $* -dit -name hf_$* -include "hf.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#hf2_%.c: $(CODELET_DEPS) $(GEN_HC2HC) -+# ($(PRELUDE_COMMANDS_RDFT); $(TWOVERS) $(GEN_HC2HC) $(FLAGS_HF2) -n $* -dit -name hf2_$* -include "hf.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#r2cfII_%.c: $(CODELET_DEPS) $(GEN_R2CF) -+# ($(PRELUDE_COMMANDS_RDFT); $(TWOVERS) $(GEN_R2CF) $(FLAGS_R2CF) -n $* -name r2cfII_$* -dft-II -include "r2cfII.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#hc2cf_%.c: $(CODELET_DEPS) $(GEN_HC2C) -+# ($(PRELUDE_COMMANDS_RDFT); $(TWOVERS) $(GEN_HC2C) $(FLAGS_HC2CF) -n $* -dit -name hc2cf_$* -include "hc2cf.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#hc2cf2_%.c: $(CODELET_DEPS) $(GEN_HC2C) -+# ($(PRELUDE_COMMANDS_RDFT); $(TWOVERS) $(GEN_HC2C) $(FLAGS_HC2CF2) -n $* -dit -name hc2cf2_$* -include "hc2cf.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#hc2cfdft_%.c: $(CODELET_DEPS) $(GEN_HC2CDFT) -+# ($(PRELUDE_COMMANDS_RDFT); $(TWOVERS) $(GEN_HC2CDFT) $(FLAGS_HC2CF) -n $* -dit -name hc2cfdft_$* -include "hc2cf.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#hc2cfdft2_%.c: $(CODELET_DEPS) $(GEN_HC2CDFT) -+# ($(PRELUDE_COMMANDS_RDFT); $(TWOVERS) $(GEN_HC2CDFT) $(FLAGS_HC2CF2) -n $* -dit -name hc2cfdft2_$* -include "hc2cf.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#endif # MAINTAINER_MODE -diff -Nru fftw-3.2.2.orig/rdft/scalar/r2r/CMakeLists.txt fftw-3.2.2/rdft/scalar/r2r/CMakeLists.txt ---- fftw-3.2.2.orig/rdft/scalar/r2r/CMakeLists.txt 1970-01-01 01:00:00.000000000 +0100 -+++ fftw-3.2.2/rdft/scalar/r2r/CMakeLists.txt 2011-12-21 11:36:18.200873200 +0100 -@@ -0,0 +1,100 @@ -+# This Makefile.am specifies a set of codelets, efficient transforms -+# of small sizes, that are used as building blocks (kernels) by FFTW -+# to build up large transforms, as well as the options for generating -+# and compiling them. -+ -+# You can customize FFTW for special needs, e.g. to handle certain -+# sizes more efficiently, by adding new codelets to the lists of those -+# included by default. If you change the list of codelets, any new -+# ones you added will be automatically generated when you run the -+# bootstrap script (see "Generating your own code" in the FFTW -+# manual). -+ -+########################################################################### -+ -+########################################################################### -+# The following lines specify the REDFT/RODFT/DHT sizes for which to generate -+# specialized codelets. Currently, only REDFT01/10 of size 8 (used in JPEG). -+ -+# e_ is a hard-coded REDFT FFT (DCT) of size -+set(E00 # e00_2.c e00_3.c e00_4.c e00_5.c e00_6.c e00_7.c e00_8.c -+) -+set(E01 e01_8.c # e01_2.c e01_3.c e01_4.c e01_5.c e01_6.c e01_7.c -+) -+set(E10 e10_8.c # e10_2.c e10_3.c e10_4.c e10_5.c e10_6.c e10_7.c -+) -+set(E11 # e11_2.c e11_3.c e11_4.c e11_5.c e11_6.c e11_7.c e11_8.c -+) -+ -+# o_ is a hard-coded RODFT FFT (DST) of size -+set(O00 # o00_2.c o00_3.c o00_4.c o00_5.c o00_6.c o00_7.c o00_8.c -+) -+set(O01 # o01_2.c o01_3.c o01_4.c o01_5.c o01_6.c o01_7.c o01_8.c -+) -+set(O10 # o10_2.c o10_3.c o10_4.c o10_5.c o10_6.c o10_7.c o10_8.c -+) -+set(O11 # o11_2.c o11_3.c o11_4.c o11_5.c o11_6.c o11_7.c o11_8.c -+) -+ -+# dht_ is a hard-coded DHT of size -+set(DHT # dht_2.c dht_3.c dht_4.c dht_5.c dht_6.c dht_7.c dht_8.c -+) -+ -+########################################################################### -+set(ALL_CODELETS ${E00} ${E01} ${E10} ${E11} ${O00} ${O01} ${O10} ${O11} ${DHT}) -+ -+include(${CMAKE_SOURCE_DIR}/support/codelets.cmake) -+ -+set(SOLVTAB_NAME "X(solvtab_rdft_r2r)") -+ -+set(CODLIST ${CMAKE_CURRENT_BINARY_DIR}/codlist.c) -+set(CODELET_NAME codelet_) -+ -+write_codelet_list(${CODLIST} ${CODELET_NAME} ${SOLVTAB_NAME} ${ALL_CODELETS}) -+ -+set(rdft_scalar_r2r_SRCS ${ALL_CODELETS}) -+ -+if(BUILD_ALL_STATIC) -+ add_library(rdft_scalar_r2r STATIC ${rdft_scalar_r2r_SRCS}) -+else(BUILD_ALL_STATIC) -+ prepend_prefix(rdft_scalar_r2r_SRCS) -+ set(__fftw_SRCS ${__fftw_SRCS} ${rdft_scalar_r2r_SRCS} ${CODLIST} PARENT_SCOPE) -+endif(BUILD_ALL_STATIC) -+ -+# this is old source code which can be changed later -+# only delete codlist.c in maintainer-mode, since it is included in the dist -+# FIXME: is there a way to delete in 'make clean' only when builddir != srcdir? -+#maintainer-clean-local: -+# rm -f $(CODLIST) -+ -+#if MAINTAINER_MODE -+#FLAGS_N1=$(DFT_FLAGS_COMMON) -+#FLAGS_T1=$(DFT_FLAGS_COMMON) -+#FLAGS_T2=$(DFT_FLAGS_COMMON) -twiddle-log3 -precompute-twiddles -+#FLAGS_F1=$(DFT_FLAGS_COMMON) -+#FLAGS_F2=$(DFT_FLAGS_COMMON) -twiddle-log3 -precompute-twiddles -+#FLAGS_Q1=$(DFT_FLAGS_COMMON) -reload-twiddle -+#FLAGS_Q2=$(DFT_FLAGS_COMMON) -twiddle-log3 -precompute-twiddles -+# -+#n1_%.c: $(CODELET_DEPS) $(GEN_NOTW) -+# ($(PRELUDE_COMMANDS_DFT); $(TWOVERS) $(GEN_NOTW) $(FLAGS_N1) -n $* -name n1_$* -include "n.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#t1_%.c: $(CODELET_DEPS) $(GEN_TWIDDLE) -+# ($(PRELUDE_COMMANDS_DFT); $(TWOVERS) $(GEN_TWIDDLE) $(FLAGS_T1) -n $* -name t1_$* -include "t.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#t2_%.c: $(CODELET_DEPS) $(GEN_TWIDDLE) -+# ($(PRELUDE_COMMANDS_DFT); $(TWOVERS) $(GEN_TWIDDLE) $(FLAGS_T2) -n $* -name t2_$* -include "t.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#f1_%.c: $(CODELET_DEPS) $(GEN_TWIDDLE) -+# ($(PRELUDE_COMMANDS_DFT); $(TWOVERS) $(GEN_TWIDDLE) $(FLAGS_F1) -dif -n $* -name f1_$* -include "f.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#f2_%.c: $(CODELET_DEPS) $(GEN_TWIDDLE) -+# ($(PRELUDE_COMMANDS_DFT); $(TWOVERS) $(GEN_TWIDDLE) $(FLAGS_F2) -dif -n $* -name f2_$* -include "f.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#q1_%.c: $(CODELET_DEPS) $(GEN_TWIDSQ) -+# ($(PRELUDE_COMMANDS_DFT); $(TWOVERS) $(GEN_TWIDSQ) $(FLAGS_Q1) -dif -n $* -name q1_$* -include "q.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#q2_%.c: $(CODELET_DEPS) $(GEN_TWIDSQ) -+# ($(PRELUDE_COMMANDS_DFT); $(TWOVERS) $(GEN_TWIDSQ) $(FLAGS_Q2) -dif -n $* -name q2_$* -include "q.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#endif # MAINTAINER_MODE -diff -Nru fftw-3.2.2.orig/rdft/simd/CMakeLists.txt fftw-3.2.2/rdft/simd/CMakeLists.txt ---- fftw-3.2.2.orig/rdft/simd/CMakeLists.txt 1970-01-01 01:00:00.000000000 +0100 -+++ fftw-3.2.2/rdft/simd/CMakeLists.txt 2011-12-21 11:36:18.202873400 +0100 -@@ -0,0 +1,10 @@ -+add_subdirectory(codelets) -+ -+set(rdft_simd_SRCS hc2cbv.c hc2cfv.c) -+ -+if(BUILD_ALL_STATIC) -+ add_library(rdft_simd STATIC ${rdft_simd_SRCS}) -+else(BUILD_ALL_STATIC) -+ prepend_prefix(rdft_simd_SRCS) -+ set(_fftw_SRCS ${_fftw_SRCS} ${rdft_simd_SRCS} ${__fftw_SRCS} PARENT_SCOPE) -+endif(BUILD_ALL_STATIC) -diff -Nru fftw-3.2.2.orig/rdft/simd/codelets/CMakeLists.txt fftw-3.2.2/rdft/simd/codelets/CMakeLists.txt ---- fftw-3.2.2.orig/rdft/simd/codelets/CMakeLists.txt 1970-01-01 01:00:00.000000000 +0100 -+++ fftw-3.2.2/rdft/simd/codelets/CMakeLists.txt 2011-12-21 11:36:18.203873500 +0100 -@@ -0,0 +1,61 @@ -+# This Makefile.am specifies a set of codelets, efficient transforms -+# of small sizes, that are used as building blocks (kernels) by FFTW -+# to build up large transforms, as well as the options for generating -+# and compiling them. -+ -+# You can customize FFTW for special needs, e.g. to handle certain -+# sizes more efficiently, by adding new codelets to the lists of those -+# included by default. If you change the list of codelets, any new -+# ones you added will be automatically generated when you run the -+# bootstrap script (see "Generating your own code" in the FFTW -+# manual). -+ -+########################################################################### -+ -+set(HC2CFDFTV hc2cfdftv_2.c hc2cfdftv_4.c hc2cfdftv_6.c hc2cfdftv_8.c -+hc2cfdftv_10.c hc2cfdftv_12.c hc2cfdftv_16.c hc2cfdftv_32.c -+hc2cfdftv_20.c # hc2cfdftv_30.c -+) -+ -+set(HC2CBDFTV hc2cbdftv_2.c hc2cbdftv_4.c hc2cbdftv_6.c hc2cbdftv_8.c -+hc2cbdftv_10.c hc2cbdftv_12.c hc2cbdftv_16.c hc2cbdftv_32.c -+hc2cbdftv_20.c # hc2cbdftv_30.c -+) -+ -+########################################################################### -+set(SIMD_CODELETS ${HC2CFDFTV} ${HC2CBDFTV}) -+ -+if(HAVE_SIMD) -+ set(ALL_CODELETS ${SIMD_CODELETS}) -+else(HAVE_SIMD) -+ set(ALL_CODELETS) -+endif(HAVE_SIMD) -+ -+set(SOLVTAB_NAME "X(solvtab_rdft_simd)") -+set(CODLIST ${CMAKE_CURRENT_BINARY_DIR}/codlist.c) -+set(CODELET_NAME codelet_) -+ -+set(rdft_simd_codelets_SRCS ${ALL_CODELETS}) -+ -+# special rules for regenerating codelets. -+include(${CMAKE_SOURCE_DIR}/support/codelets.cmake) -+ -+write_codelet_list(${CODLIST} ${CODELET_NAME} ${SOLVTAB_NAME} ${ALL_CODELETS}) -+ -+if(BUILD_ALL_STATIC) -+ add_library(rdft_simd_codelets STATIC ${ALL_CODELETS}) -+else(BUILD_ALL_STATIC) -+ prepend_prefix(rdft_simd_codelets_SRCS) -+ set(__fftw_SRCS ${__fftw_SRCS} ${rdft_simd_codelets_SRCS} ${CODLIST} PARENT_SCOPE) -+endif(BUILD_ALL_STATIC) -+ -+#if MAINTAINER_MODE -+#FLAGS_HC2C=-simd $(FLAGS_COMMON) -pipeline-latency 8 -trivial-stores -variables 32 -no-generate-bytw -+# -+#hc2cfdftv_%.c: $(CODELET_DEPS) $(GEN_HC2CDFT_C) -+# ($(PRELUDE_COMMANDS_RDFT); $(TWOVERS) $(GEN_HC2CDFT_C) $(FLAGS_HC2C) -n $* -dit -name hc2cfdftv_$* -include "hc2cfv.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#hc2cbdftv_%.c: $(CODELET_DEPS) $(GEN_HC2CDFT_C) -+# ($(PRELUDE_COMMANDS_RDFT); $(TWOVERS) $(GEN_HC2CDFT_C) $(FLAGS_HC2C) -n $* -dif -sign 1 -name hc2cbdftv_$* -include "hc2cbv.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#endif # MAINTAINER_MODE -diff -Nru fftw-3.2.2.orig/reodft/CMakeLists.txt fftw-3.2.2/reodft/CMakeLists.txt ---- fftw-3.2.2.orig/reodft/CMakeLists.txt 1970-01-01 01:00:00.000000000 +0100 -+++ fftw-3.2.2/reodft/CMakeLists.txt 2011-12-21 11:36:18.205873700 +0100 -@@ -0,0 +1,12 @@ -+set(reodft_SRCS conf.c reodft010e-r2hc.c -+reodft11e-radix2.c reodft11e-r2hc-odd.c redft00e-r2hc-pad.c -+rodft00e-r2hc-pad.c reodft00e-splitradix.c -+# redft00e-r2hc.c rodft00e-r2hc.c reodft11e-r2hc.c -+) -+ -+if(BUILD_ALL_STATIC) -+ add_library(reodft STATIC ${reodft_SRCS}) -+else(BUILD_ALL_STATIC) -+ prepend_prefix(reodft_SRCS) -+ set(fftw_SRCS ${fftw_SRCS} ${reodft_SRCS} ${_fftw_SRCS} PARENT_SCOPE) -+endif(BUILD_ALL_STATIC) -diff -Nru fftw-3.2.2.orig/simd/CMakeLists.txt fftw-3.2.2/simd/CMakeLists.txt ---- fftw-3.2.2.orig/simd/CMakeLists.txt 1970-01-01 01:00:00.000000000 +0100 -+++ fftw-3.2.2/simd/CMakeLists.txt 2011-12-21 11:42:02.916250500 +0100 -@@ -0,0 +1,10 @@ -+set(simd_SRCS altivec.c sse.c sse2.c taint.c mips_ps.c) -+ -+add_subdirectory(nonportable) -+ -+if(BUILD_ALL_STATIC) -+ add_library(simd STATIC ${simd_SRCS}) -+else(BUILD_ALL_STATIC) -+ prepend_prefix(simd_SRCS) -+ set(fftw_SRCS ${fftw_SRCS} ${simd_SRCS} ${_fftw_SRCS} PARENT_SCOPE) -+endif(BUILD_ALL_STATIC) -diff -Nru fftw-3.2.2.orig/simd/nonportable/CMakeLists.txt fftw-3.2.2/simd/nonportable/CMakeLists.txt ---- fftw-3.2.2.orig/simd/nonportable/CMakeLists.txt 1970-01-01 01:00:00.000000000 +0100 -+++ fftw-3.2.2/simd/nonportable/CMakeLists.txt 2011-12-21 11:36:18.208874000 +0100 -@@ -0,0 +1,8 @@ -+set(simd_nonportable_SRCS sse.c sse2.c) -+ -+if(BUILD_ALL_STATIC) -+ add_library(simd_nonportable STATIC ${simd_nonportable_SRCS}) -+else(BUILD_ALL_STATIC) -+ prepend_prefix(simd_nonportable_SRCS) -+ set(_fftw_SRCS ${_fftw_SRCS} ${simd_nonportable_SRCS} PARENT_SCOPE) -+endif(BUILD_ALL_STATIC) -diff -Nru fftw-3.2.2.orig/support/codelets.cmake fftw-3.2.2/support/codelets.cmake ---- fftw-3.2.2.orig/support/codelets.cmake 1970-01-01 01:00:00.000000000 +0100 -+++ fftw-3.2.2/support/codelets.cmake 2011-12-21 11:36:18.209874100 +0100 -@@ -0,0 +1,18 @@ -+# generate file codlist.c -+ -+macro(write_codelet_list CODLIST CODELET_NAME SOLVTAB_NAME) -+ file(RELATIVE_PATH filename ${CMAKE_BINARY_DIR} ${CODLIST}) -+ message(STATUS "writing codelet list file ${filename}") -+ file(WRITE ${CODLIST} "#include \"ifftw.h\"\n\n") -+ foreach(codelet ${ARGN}) -+ string(REGEX REPLACE "([A-Za-z0-9_]*)\\.c" "\\1" codeletbase "${codelet}") -+ file(APPEND ${CODLIST} "extern void X(${CODELET_NAME}${codeletbase})(planner *);\n") -+ endforeach(codelet ${ARGN}) -+ file(APPEND ${CODLIST} "\n\nextern const solvtab ${SOLVTAB_NAME};\n") -+ file(APPEND ${CODLIST} "const solvtab ${SOLVTAB_NAME} = {\n") -+ foreach(codelet ${ARGN}) -+ string(REGEX REPLACE "([A-Za-z0-9_]*)\\.c" "\\1" codeletbase "${codelet}") -+ file(APPEND ${CODLIST} " SOLVTAB(X(${CODELET_NAME}${codeletbase})),\n") -+ endforeach(codelet ${ARGN}) -+ file(APPEND ${CODLIST} " SOLVTAB_END\n};\n") -+endmacro(write_codelet_list CODLIST CODELET_NAME SOLVTAB_NAME) -\ No newline at end of file -diff -Nru fftw-3.2.2.orig/tests/CMakeLists.txt fftw-3.2.2/tests/CMakeLists.txt ---- fftw-3.2.2.orig/tests/CMakeLists.txt 1970-01-01 01:00:00.000000000 +0100 -+++ fftw-3.2.2/tests/CMakeLists.txt 2011-12-21 11:36:18.211874300 +0100 -@@ -0,0 +1,71 @@ -+#noinst_PROGRAMS = bench -+#EXTRA_DIST = check.pl README -+ -+#if SMP -+#if !COMBINED_THREADS -+#LIBFFTWTHREADS = $(top_builddir)/threads/libfftw3@PREC_SUFFIX@_threads.la -+#endif -+#else -+#LIBFFTWTHREADS = -+#endif -+ -+set(bench_SRCS bench.c hook.c fftw-bench.c) -+add_executable(bench ${bench_SRCS}) -+target_link_libraries(bench fftw bench2 fftw) -+ -+#check-local: bench$(EXEEXT) -+# perl -w $(srcdir)/check.pl -r -c=30 -v `pwd`/bench -+# @echo "--------------------------------------------------------------" -+# @echo " FFTW transforms passed basic tests!" -+# @echo "--------------------------------------------------------------" -+#if SMP -+# perl -w $(srcdir)/check.pl -r -c=30 -v --nthreads=2 `pwd`/bench -+# @echo "--------------------------------------------------------------" -+# @echo " FFTW threaded transforms passed basic tests!" -+# @echo "--------------------------------------------------------------" -+#endif -+# -+#bigcheck: bench$(EXEEXT) -+# perl -w $(srcdir)/check.pl -a -v `pwd`/bench -+# @echo "--------------------------------------------------------------" -+# @echo " FFTW transforms passed big tests!" -+# @echo "--------------------------------------------------------------" -+#if SMP -+# perl -w $(srcdir)/check.pl -a -v --nthreads=2 `pwd`/bench -+# perl -w $(srcdir)/check.pl -a -v --nthreads=3 `pwd`/bench -+# perl -w $(srcdir)/check.pl -a -v --nthreads=10 `pwd`/bench -+# @echo "--------------------------------------------------------------" -+# @echo " FFTW threaded transforms passed big tests!" -+# @echo "--------------------------------------------------------------" -+#endif -+# -+#smallcheck: bench$(EXEEXT) -+# perl -w $(srcdir)/check.pl -r -c=1 -v `pwd`/bench -+# perl -w $(srcdir)/check.pl -r --estimate -c=5 -v `pwd`/bench -+# @echo "--------------------------------------------------------------" -+# @echo " FFTW transforms passed a few tests!" -+# @echo "--------------------------------------------------------------" -+#if SMP -+# perl -w $(srcdir)/check.pl -r --estimate -c=2 -v --nthreads=2 `pwd`/bench -+# @echo "--------------------------------------------------------------" -+# @echo " FFTW threaded transforms passed a few tests!" -+# @echo "--------------------------------------------------------------" -+#endif -+# -+#paranoid-check: bench$(EXEEXT) -+#if SMP -+# perl -w $(srcdir)/check.pl -a --patient --nthreads=10 --paranoid `pwd`/bench -+# perl -w $(srcdir)/check.pl -a --patient --nthreads=7 --paranoid `pwd`/bench -+# perl -w $(srcdir)/check.pl -a --patient --nthreads=3 --paranoid `pwd`/bench -+# perl -w $(srcdir)/check.pl -a --patient --nthreads=2 --paranoid `pwd`/bench -+#endif -+# perl -w $(srcdir)/check.pl -a --patient --paranoid `pwd`/bench -+# -+#exhaustive-check: bench$(EXEEXT) -+#if SMP -+# perl -w $(srcdir)/check.pl -a --exhaustive --nthreads=10 --paranoid `pwd`/bench -+# perl -w $(srcdir)/check.pl -a --exhaustive --nthreads=7 --paranoid `pwd`/bench -+# perl -w $(srcdir)/check.pl -a --exhaustive --nthreads=3 --paranoid `pwd`/bench -+# perl -w $(srcdir)/check.pl -a --exhaustive --nthreads=2 --paranoid `pwd`/bench -+#endif -+# perl -w $(srcdir)/check.pl -a --exhaustive --paranoid `pwd`/bench -diff -Nru fftw-3.2.2.orig/threads/api.c fftw-3.2.2/threads/api.c ---- fftw-3.2.2.orig/threads/api.c 2009-07-12 12:35:49.000000000 +0200 -+++ fftw-3.2.2/threads/api.c 2011-12-21 11:36:18.212874400 +0100 -@@ -56,7 +56,6 @@ - return 1; - } - -- - void X(cleanup_threads)(void) - { - X(cleanup)(); -diff -Nru fftw-3.2.2.orig/threads/CMakeLists.txt fftw-3.2.2/threads/CMakeLists.txt ---- fftw-3.2.2.orig/threads/CMakeLists.txt 1970-01-01 01:00:00.000000000 +0100 -+++ fftw-3.2.2/threads/CMakeLists.txt 2011-12-21 11:36:18.215874700 +0100 -@@ -0,0 +1,9 @@ -+set(threads_SRCS api.c conf.c threads.c openmp.c dft-vrank-geq1.c ct.c rdft-vrank-geq1.c hc2hc.c vrank-geq1-rdft2.c f77api.c -+) -+ -+if(BUILD_ALL_STATIC) -+ add_library(threads STATIC ${threads_SRCS}) -+else(BUILD_ALL_STATIC) -+ prepend_prefix(threads_SRCS) -+ set(fftw_SRCS ${fftw_SRCS} ${threads_SRCS} PARENT_SCOPE) -+endif(BUILD_ALL_STATIC) -\ No newline at end of file diff --git a/3rdparty/ext_fftw3/fftw-3.2.2-20130818.diff b/3rdparty/ext_fftw3/fftw-3.2.2-20130818.diff deleted file mode 100644 index d15309b8a0..0000000000 --- a/3rdparty/ext_fftw3/fftw-3.2.2-20130818.diff +++ /dev/null @@ -1,470 +0,0 @@ -diff -Nru -x '*~' fftw-3.2.2.orig/simd/simd-sse.h fftw-3.2.2/simd/simd-sse.h ---- fftw-3.2.2.orig/simd/simd-sse.h 2009-07-12 07:35:49.000000000 -0300 -+++ fftw-3.2.2/simd/simd-sse.h 2013-08-18 14:36:21.511098200 -0300 -@@ -82,7 +82,7 @@ - # define LOADH(addr, val) _mm_loadh_pi(val, (const __m64 *)(addr)) - # define LOADL0(addr, val) _mm_loadl_pi(val, (const __m64 *)(addr)) - -- static inline V LD(const R *x, INT ivs, const R *aligned_like) -+ static V LD(const R *x, INT ivs, const R *aligned_like) - { - V var; - (void)aligned_like; /* UNUSED */ -@@ -111,14 +111,14 @@ - (((fp3) << 6) | ((fp2) << 4) | ((fp1) << 2) | ((fp0))) - - --static inline V LDA(const R *x, INT ivs, const R *aligned_like) -+static V LDA(const R *x, INT ivs, const R *aligned_like) - { - (void)aligned_like; /* UNUSED */ - (void)ivs; /* UNUSED */ - return *(const V *)x; - } - --static inline void ST(R *x, V v, INT ovs, const R *aligned_like) -+static void ST(R *x, V v, INT ovs, const R *aligned_like) - { - (void)aligned_like; /* UNUSED */ - /* WARNING: the extra_iter hack depends upon STOREL occurring -@@ -127,7 +127,7 @@ - STOREL(x, v); - } - --static inline void STA(R *x, V v, INT ovs, const R *aligned_like) -+static void STA(R *x, V v, INT ovs, const R *aligned_like) - { - (void)aligned_like; /* UNUSED */ - (void)ovs; /* UNUSED */ -@@ -136,7 +136,7 @@ - - #if 0 - /* this should be faster but it isn't. */ --static inline void STN2(R *x, V v0, V v1, INT ovs) -+static void STN2(R *x, V v0, V v1, INT ovs) - { - STA(x, SHUFPS(v0, v1, SHUFVAL(0, 1, 0, 1)), ovs, 0); - STA(x + ovs, SHUFPS(v0, v1, SHUFVAL(2, 3, 2, 3)), ovs, 0); -@@ -148,7 +148,7 @@ - #define STM4(x, v, ovs, aligned_like) /* no-op */ - - #ifdef VISUAL_CXX_DOES_NOT_SUCK --static inline void STN4(R *x, V v0, V v1, V v2, V v3, INT ovs) -+static void STN4(R *x, V v0, V v1, V v2, V v3, INT ovs) - { - V x0, x1, x2, x3; - x0 = UNPCKL(v0, v2); -@@ -198,23 +198,23 @@ - } - #endif - --static inline V FLIP_RI(V x) -+static V FLIP_RI(V x) - { - return SHUFPS(x, x, SHUFVAL(1, 0, 3, 2)); - } - - extern const union uvec X(sse_pmpm); --static inline V VCONJ(V x) -+static V VCONJ(V x) - { - return VXOR(X(sse_pmpm).v, x); - } - --static inline V VBYI(V x) -+static V VBYI(V x) - { - return FLIP_RI(VCONJ(x)); - } - --static inline V VZMUL(V tx, V sr) -+static V VZMUL(V tx, V sr) - { - V tr = SHUFPS(tx, tx, SHUFVAL(0, 0, 2, 2)); - V ti = SHUFPS(tx, tx, SHUFVAL(1, 1, 3, 3)); -@@ -223,7 +223,7 @@ - return VADD(tr, VMUL(ti, sr)); - } - --static inline V VZMULJ(V tx, V sr) -+static V VZMULJ(V tx, V sr) - { - V tr = SHUFPS(tx, tx, SHUFVAL(0, 0, 2, 2)); - V ti = SHUFPS(tx, tx, SHUFVAL(1, 1, 3, 3)); -@@ -232,7 +232,7 @@ - return VSUB(tr, VMUL(ti, sr)); - } - --static inline V VZMULI(V tx, V sr) -+static V VZMULI(V tx, V sr) - { - V tr = SHUFPS(tx, tx, SHUFVAL(0, 0, 2, 2)); - V ti = SHUFPS(tx, tx, SHUFVAL(1, 1, 3, 3)); -@@ -241,7 +241,7 @@ - return VSUB(VMUL(tr, sr), ti); - } - --static inline V VZMULIJ(V tx, V sr) -+static V VZMULIJ(V tx, V sr) - { - V tr = SHUFPS(tx, tx, SHUFVAL(0, 0, 2, 2)); - V ti = SHUFPS(tx, tx, SHUFVAL(1, 1, 3, 3)); -@@ -258,7 +258,7 @@ - {TW_COS, v, x}, {TW_COS, v+1, x}, {TW_SIN, v, x}, {TW_SIN, v+1, x} - #define TWVL1 (VL) - --static inline V BYTW1(const R *t, V sr) -+static V BYTW1(const R *t, V sr) - { - const V *twp = (const V *)t; - V tx = twp[0]; -@@ -269,7 +269,7 @@ - return VADD(tr, VMUL(ti, sr)); - } - --static inline V BYTWJ1(const R *t, V sr) -+static V BYTWJ1(const R *t, V sr) - { - const V *twp = (const V *)t; - V tx = twp[0]; -@@ -286,7 +286,7 @@ - {TW_SIN, v, -x}, {TW_SIN, v, x}, {TW_SIN, v+1, -x}, {TW_SIN, v+1, x} - #define TWVL2 (2 * VL) - --static inline V BYTW2(const R *t, V sr) -+static V BYTW2(const R *t, V sr) - { - const V *twp = (const V *)t; - V si = FLIP_RI(sr); -@@ -294,7 +294,7 @@ - return VADD(VMUL(tr, sr), VMUL(ti, si)); - } - --static inline V BYTWJ2(const R *t, V sr) -+static V BYTWJ2(const R *t, V sr) - { - const V *twp = (const V *)t; - V si = FLIP_RI(sr); -diff -Nru -x '*~' fftw-3.2.2.orig/simd/simd-sse.h.bak fftw-3.2.2/simd/simd-sse.h.bak ---- fftw-3.2.2.orig/simd/simd-sse.h.bak 1969-12-31 22:00:00.000000000 -0200 -+++ fftw-3.2.2/simd/simd-sse.h.bak 2013-08-18 14:32:55.234299800 -0300 -@@ -0,0 +1,319 @@ -+/* -+ * Copyright (c) 2003, 2007-8 Matteo Frigo -+ * Copyright (c) 2003, 2007-8 Massachusetts Institute of Technology -+ * -+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -+ * -+ */ -+ -+#ifndef FFTW_SINGLE -+#error "SSE only works in single precision" -+#endif -+ -+#define VL 2 /* SIMD complex vector length */ -+#define ALIGNMENT 8 /* alignment for LD/ST */ -+#define ALIGNMENTA 16 /* alignment for LDA/STA */ -+#define SIMD_VSTRIDE_OKA(x) ((x) == 2) -+#define SIMD_STRIDE_OKPAIR SIMD_STRIDE_OK -+ -+#define RIGHT_CPU X(have_sse) -+extern int RIGHT_CPU(void); -+ -+/* gcc compiles the following code only when __SSE__ is defined */ -+#if defined(__SSE__) || !defined(__GNUC__) -+ -+/* some versions of glibc's sys/cdefs.h define __inline to be empty, -+ which is wrong because xmmintrin.h defines several inline -+ procedures */ -+#undef __inline -+ -+#include -+ -+typedef __m128 V; -+#define VADD _mm_add_ps -+#define VSUB _mm_sub_ps -+#define VMUL _mm_mul_ps -+#define VXOR _mm_xor_ps -+#define SHUFPS _mm_shuffle_ps -+#define STOREH(addr, val) _mm_storeh_pi((__m64 *)(addr), val) -+#define STOREL(addr, val) _mm_storel_pi((__m64 *)(addr), val) -+#define UNPCKH _mm_unpackhi_ps -+#define UNPCKL _mm_unpacklo_ps -+ -+#ifdef __GNUC__ -+# define DVK(var, val) const V var = __extension__ ({ \ -+ static const union fvec _var = { {val, val, val, val} }; \ -+ _var.v; \ -+ }) -+# define LDK(x) x -+ -+ /* we use inline asm because gcc generates slow code for -+ _mm_loadh_pi(). gcc insists upon having an existing variable for -+ VAL, which is however never used. Thus, it generates code to move -+ values in and out the variable. Worse still, gcc-4.0 stores VAL on -+ the stack, causing valgrind to complain about uninitialized reads. -+ */ -+ -+ static inline V LD(const R *x, INT ivs, const R *aligned_like) -+ { -+ V var; -+ (void)aligned_like; /* UNUSED */ -+ __asm__("movlps %1, %0\n\tmovhps %2, %0" -+ : "=x"(var) : "m"(x[0]), "m"(x[ivs])); -+ return var; -+ } -+ -+#else -+ -+# define DVK(var, val) const R var = K(val) -+# define LDK(x) _mm_set_ps1(x) -+# define LOADH(addr, val) _mm_loadh_pi(val, (const __m64 *)(addr)) -+# define LOADL0(addr, val) _mm_loadl_pi(val, (const __m64 *)(addr)) -+ -+#ifndef __INTEL_COMPILER -+ static inline V LD(const R *x, INT ivs, const R *aligned_like) -+#else -+ static V LD(const R *x, INT ivs, const R *aligned_like) -+#endif -+ { -+ V var; -+ (void)aligned_like; /* UNUSED */ -+ var = LOADL0(x, var); -+ var = LOADH(x + ivs, var); -+ return var; -+ } -+ -+#endif -+ -+union fvec { -+ R f[4]; -+ V v; -+}; -+ -+union uvec { -+ unsigned u[4]; -+ V v; -+}; -+ -+#define VFMA(a, b, c) VADD(c, VMUL(a, b)) -+#define VFNMS(a, b, c) VSUB(c, VMUL(a, b)) -+#define VFMS(a, b, c) VSUB(VMUL(a, b), c) -+ -+#define SHUFVAL(fp0,fp1,fp2,fp3) \ -+ (((fp3) << 6) | ((fp2) << 4) | ((fp1) << 2) | ((fp0))) -+ -+ -+static inline V LDA(const R *x, INT ivs, const R *aligned_like) -+{ -+ (void)aligned_like; /* UNUSED */ -+ (void)ivs; /* UNUSED */ -+ return *(const V *)x; -+} -+ -+static inline void ST(R *x, V v, INT ovs, const R *aligned_like) -+{ -+ (void)aligned_like; /* UNUSED */ -+ /* WARNING: the extra_iter hack depends upon STOREL occurring -+ after STOREH */ -+ STOREH(x + ovs, v); -+ STOREL(x, v); -+} -+ -+static inline void STA(R *x, V v, INT ovs, const R *aligned_like) -+{ -+ (void)aligned_like; /* UNUSED */ -+ (void)ovs; /* UNUSED */ -+ *(V *)x = v; -+} -+ -+#if 0 -+/* this should be faster but it isn't. */ -+static inline void STN2(R *x, V v0, V v1, INT ovs) -+{ -+ STA(x, SHUFPS(v0, v1, SHUFVAL(0, 1, 0, 1)), ovs, 0); -+ STA(x + ovs, SHUFPS(v0, v1, SHUFVAL(2, 3, 2, 3)), ovs, 0); -+} -+#endif -+#define STM2 ST -+#define STN2(x, v0, v1, ovs) /* nop */ -+ -+#define STM4(x, v, ovs, aligned_like) /* no-op */ -+ -+#ifdef VISUAL_CXX_DOES_NOT_SUCK -+static inline void STN4(R *x, V v0, V v1, V v2, V v3, INT ovs) -+{ -+ V x0, x1, x2, x3; -+ x0 = UNPCKL(v0, v2); -+ x1 = UNPCKH(v0, v2); -+ x2 = UNPCKL(v1, v3); -+ x3 = UNPCKH(v1, v3); -+ STA(x, UNPCKL(x0, x2), 0, 0); -+ STA(x + ovs, UNPCKH(x0, x2), 0, 0); -+ STA(x + 2 * ovs, UNPCKL(x1, x3), 0, 0); -+ STA(x + 3 * ovs, UNPCKH(x1, x3), 0, 0); -+} -+#else /* Visual C++ sucks */ -+ -+/* -+ Straight from the mouth of the horse: -+ -+ We "reserved" the possibility of aligning arguments with -+ __declspec(align(X)) passed by value by issuing this error. -+ -+ The first 3 parameters of type __m64 (or other MMX types) are -+ passed in registers. The rest would be passed on the stack. We -+ decided aligning the stack was wasteful, especially for __m128 -+ parameters. Also, we thought it would be infrequent that people -+ would want to pass more than 3 by value. -+ -+ If we didn't issue an error, we would have to binary compatibility -+ in the future if we decided to align the arguments. -+ -+ -+ Hope that explains it. -+ -- -+ Jason Shirk, Visual C++ Compiler Team -+ This posting is provided AS IS with no warranties, and confers no rights -+*/ -+ -+#define STN4(x, v0, v1, v2, v3, ovs) \ -+{ \ -+ V xxx0, xxx1, xxx2, xxx3; \ -+ xxx0 = UNPCKL(v0, v2); \ -+ xxx1 = UNPCKH(v0, v2); \ -+ xxx2 = UNPCKL(v1, v3); \ -+ xxx3 = UNPCKH(v1, v3); \ -+ STA(x, UNPCKL(xxx0, xxx2), 0, 0); \ -+ STA(x + ovs, UNPCKH(xxx0, xxx2), 0, 0); \ -+ STA(x + 2 * ovs, UNPCKL(xxx1, xxx3), 0, 0); \ -+ STA(x + 3 * ovs, UNPCKH(xxx1, xxx3), 0, 0); \ -+} -+#endif -+ -+static inline V FLIP_RI(V x) -+{ -+ return SHUFPS(x, x, SHUFVAL(1, 0, 3, 2)); -+} -+ -+extern const union uvec X(sse_pmpm); -+static inline V VCONJ(V x) -+{ -+ return VXOR(X(sse_pmpm).v, x); -+} -+ -+static inline V VBYI(V x) -+{ -+ return FLIP_RI(VCONJ(x)); -+} -+ -+static inline V VZMUL(V tx, V sr) -+{ -+ V tr = SHUFPS(tx, tx, SHUFVAL(0, 0, 2, 2)); -+ V ti = SHUFPS(tx, tx, SHUFVAL(1, 1, 3, 3)); -+ tr = VMUL(tr, sr); -+ sr = VBYI(sr); -+ return VADD(tr, VMUL(ti, sr)); -+} -+ -+static inline V VZMULJ(V tx, V sr) -+{ -+ V tr = SHUFPS(tx, tx, SHUFVAL(0, 0, 2, 2)); -+ V ti = SHUFPS(tx, tx, SHUFVAL(1, 1, 3, 3)); -+ tr = VMUL(tr, sr); -+ sr = VBYI(sr); -+ return VSUB(tr, VMUL(ti, sr)); -+} -+ -+static inline V VZMULI(V tx, V sr) -+{ -+ V tr = SHUFPS(tx, tx, SHUFVAL(0, 0, 2, 2)); -+ V ti = SHUFPS(tx, tx, SHUFVAL(1, 1, 3, 3)); -+ ti = VMUL(ti, sr); -+ sr = VBYI(sr); -+ return VSUB(VMUL(tr, sr), ti); -+} -+ -+static inline V VZMULIJ(V tx, V sr) -+{ -+ V tr = SHUFPS(tx, tx, SHUFVAL(0, 0, 2, 2)); -+ V ti = SHUFPS(tx, tx, SHUFVAL(1, 1, 3, 3)); -+ ti = VMUL(ti, sr); -+ sr = VBYI(sr); -+ return VADD(VMUL(tr, sr), ti); -+} -+ -+#define VFMAI(b, c) VADD(c, VBYI(b)) -+#define VFNMSI(b, c) VSUB(c, VBYI(b)) -+ -+/* twiddle storage #1: compact, slower */ -+#define VTW1(v,x) \ -+ {TW_COS, v, x}, {TW_COS, v+1, x}, {TW_SIN, v, x}, {TW_SIN, v+1, x} -+#define TWVL1 (VL) -+ -+static inline V BYTW1(const R *t, V sr) -+{ -+ const V *twp = (const V *)t; -+ V tx = twp[0]; -+ V tr = UNPCKL(tx, tx); -+ V ti = UNPCKH(tx, tx); -+ tr = VMUL(tr, sr); -+ sr = VBYI(sr); -+ return VADD(tr, VMUL(ti, sr)); -+} -+ -+static inline V BYTWJ1(const R *t, V sr) -+{ -+ const V *twp = (const V *)t; -+ V tx = twp[0]; -+ V tr = UNPCKL(tx, tx); -+ V ti = UNPCKH(tx, tx); -+ tr = VMUL(tr, sr); -+ sr = VBYI(sr); -+ return VSUB(tr, VMUL(ti, sr)); -+} -+ -+/* twiddle storage #2: twice the space, faster (when in cache) */ -+#define VTW2(v,x) \ -+ {TW_COS, v, x}, {TW_COS, v, x}, {TW_COS, v+1, x}, {TW_COS, v+1, x}, \ -+ {TW_SIN, v, -x}, {TW_SIN, v, x}, {TW_SIN, v+1, -x}, {TW_SIN, v+1, x} -+#define TWVL2 (2 * VL) -+ -+static inline V BYTW2(const R *t, V sr) -+{ -+ const V *twp = (const V *)t; -+ V si = FLIP_RI(sr); -+ V tr = twp[0], ti = twp[1]; -+ return VADD(VMUL(tr, sr), VMUL(ti, si)); -+} -+ -+static inline V BYTWJ2(const R *t, V sr) -+{ -+ const V *twp = (const V *)t; -+ V si = FLIP_RI(sr); -+ V tr = twp[0], ti = twp[1]; -+ return VSUB(VMUL(tr, sr), VMUL(ti, si)); -+} -+ -+/* twiddle storage #3 */ -+#define VTW3(v,x) {TW_CEXP, v, x}, {TW_CEXP, v+1, x} -+#define TWVL3 (VL) -+ -+/* twiddle storage for split arrays */ -+#define VTWS(v,x) \ -+ {TW_COS, v, x}, {TW_COS, v+1, x}, {TW_COS, v+2, x}, {TW_COS, v+3, x}, \ -+ {TW_SIN, v, x}, {TW_SIN, v+1, x}, {TW_SIN, v+2, x}, {TW_SIN, v+3, x} -+#define TWVLS (2 * VL) -+ -+#endif /* __SSE__ */ diff --git a/3rdparty/ext_fftw3/fftw-3.2.2.diff b/3rdparty/ext_fftw3/fftw-3.2.2.diff deleted file mode 100644 index ced98e685e..0000000000 --- a/3rdparty/ext_fftw3/fftw-3.2.2.diff +++ /dev/null @@ -1,3892 +0,0 @@ -diff --git a/CMakeLists.txt b/CMakeLists.txt -new file mode 100644 -index 0000000..3e6534b ---- /dev/null -+++ b/CMakeLists.txt -@@ -0,0 +1,123 @@ -+project(libfftw) -+ -+cmake_minimum_required(VERSION 2.6) -+ -+include(${CMAKE_SOURCE_DIR}/CheckMingwVersion.cmake) -+ -+option(DISABLE_FORTRAN "disable fortran wrappers" ON) -+option(FFTW_SINGLE "compile in single precision" OFF) -+option(FFTW_DOUBLE "compile in double precision" ON) -+option(FFTW_LDOUBLE "compile in long double precision" OFF) -+option(ENABLE_OPENMP "compile in openmp extensions" OFF) -+option(BUILD_ALL_STATIC "build each and every library as a small static library" OFF) -+option(BUILD_STATIC "build one static library" OFF) -+option(BUILD_BENCHMARKS "build benchmarks" ON) -+ -+add_definitions(-DHAVE_CONFIG_H) -+if(MSVC) -+ add_definitions(-D_CRT_SECURE_NO_WARNINGS -wd4700) -+endif(MSVC) -+ -+if(FFTW_SINGLE) -+ set(FFTW_DOUBLE OFF) -+ set(FFTW_LDOUBLE OFF) -+elseif(FFTW_DOUBLE) -+ set(FFTW_LDOUBLE OFF) -+endif(FFTW_SINGLE) -+ -+if(MINGW32) -+ add_definitions(-march=pentium4) -+endif(MINGW32) -+ -+if(ENABLE_OPENMP) -+ add_definitions(-openmp) -+endif(ENABLE_OPENMP) -+ -+set(PACKAGE "\"fftw\"") -+set(VERSION "\"3.2.2\"") -+set(PACKAGE_VERSION "${VERSION}") -+set(FFTW_CC "\"${CMAKE_C_COMPILER}\"") -+set(CODELET_OPTIM "\"\"") -+ -+set(fftw_SRCS) -+ -+include(ConfigureChecks.cmake) -+ -+macro(prepend_prefix outList) -+ FILE(RELATIVE_PATH prefix ${CMAKE_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}) -+ foreach(element ${${outList}}) -+ set(_outList ${_outList} ${prefix}/${element}) -+ endforeach(element ${${outList}}) -+ set(${outList} ${_outList}) -+endmacro(prepend_prefix outList) -+ -+include_directories( -+ ${CMAKE_CURRENT_SOURCE_DIR} -+ ${CMAKE_CURRENT_BINARY_DIR} -+ ${CMAKE_CURRENT_SOURCE_DIR}/kernel -+ ${CMAKE_CURRENT_SOURCE_DIR}/simd -+ ${CMAKE_CURRENT_SOURCE_DIR}/dft -+ ${CMAKE_CURRENT_SOURCE_DIR}/dft/scalar -+ ${CMAKE_CURRENT_SOURCE_DIR}/dft/scalar/codelets -+ ${CMAKE_CURRENT_BINARY_DIR}/dft/scalar/codelets -+ ${CMAKE_CURRENT_SOURCE_DIR}/dft/simd -+ ${CMAKE_CURRENT_SOURCE_DIR}/dft/simd/codelets -+ ${CMAKE_CURRENT_BINARY_DIR}/dft/simd/codelets -+ ${CMAKE_CURRENT_SOURCE_DIR}/rdft -+ ${CMAKE_CURRENT_SOURCE_DIR}/rdft/scalar -+ ${CMAKE_CURRENT_SOURCE_DIR}/rdft/scalar/r2cb -+ ${CMAKE_CURRENT_BINARY_DIR}/rdft/scalar/r2cb -+ ${CMAKE_CURRENT_SOURCE_DIR}/rdft/scalar/r2cf -+ ${CMAKE_CURRENT_BINARY_DIR}/rdft/scalar/r2cf -+ ${CMAKE_CURRENT_SOURCE_DIR}/rdft/scalar/r2r -+ ${CMAKE_CURRENT_BINARY_DIR}/rdft/scalar/r2r -+ ${CMAKE_CURRENT_SOURCE_DIR}/rdft/simd -+ ${CMAKE_CURRENT_SOURCE_DIR}/rdft/simd/codelets -+ ${CMAKE_CURRENT_BINARY_DIR}/rdft/simd/codelets -+ ${CMAKE_CURRENT_SOURCE_DIR}/reodft -+ ${CMAKE_CURRENT_SOURCE_DIR}/api -+ ${CMAKE_CURRENT_SOURCE_DIR}/threads -+ ${CMAKE_CURRENT_SOURCE_DIR}/cell -+ ${CMAKE_CURRENT_SOURCE_DIR}/libbench2 -+ ${CMAKE_CURRENT_SOURCE_DIR}/tests -+ ${CMAKE_CURRENT_SOURCE_DIR}/tools -+) -+add_subdirectory(kernel) -+add_subdirectory(simd) -+add_subdirectory(dft) -+add_subdirectory(rdft) -+add_subdirectory(reodft) -+add_subdirectory(api) -+add_subdirectory(threads) -+ -+if(NOT BUILD_ALL_STATIC) -+ if(NOT BUILD_STATIC) -+ set(_shared SHARED) -+ set(fftw_SRCS ${fftw_SRCS} fftw.def) -+ else(NOT BUILD_STATIC) -+ set(_shared STATIC) -+ endif(NOT BUILD_STATIC) -+ -+ if(FFTW_SINGLE) -+ set(FFTW_OUTPUT_NAME fftw3f-3) -+ elseif(FFTW_DOUBLE) -+ set(FFTW_OUTPUT_NAME fftw3-3) -+ elseif(FFTW_LDOUBLE) -+ set(FFTW_OUTPUT_NAME fftw3l-3) -+ endif(FFTW_SINGLE) -+ -+ add_library(fftw ${_shared} ${fftw_SRCS}) -+ set_target_properties(fftw PROPERTIES DEFINE_SYMBOL DLL_EXPORT -+ OUTPUT_NAME ${FFTW_OUTPUT_NAME}) -+ install(TARGETS fftw RUNTIME DESTINATION bin -+ LIBRARY DESTINATION lib -+ ARCHIVE DESTINATION lib) -+ -+ install(FILES api/fftw3.h api/fftw3.f DESTINATION include) -+endif(NOT BUILD_ALL_STATIC) -+ -+if(BUILD_BENCHMARKS) -+ add_subdirectory(libbench2) -+ add_subdirectory(tests) -+endif(BUILD_BENCHMARKS) -+#add_subdirectory(tools) -diff --git a/CheckMingwVersion.cmake b/CheckMingwVersion.cmake -new file mode 100644 -index 0000000..8a2e805 ---- /dev/null -+++ b/CheckMingwVersion.cmake -@@ -0,0 +1,26 @@ -+# -+# check mingw compiler version -+# -+# Copyright (c) 2010, Ralf Habacker -+# -+# Redistribution and use is allowed according to the terms of the BSD license. -+# -+if (NOT MINGW32 AND NOT MINGW64) -+ exec_program( -+ ${CMAKE_C_COMPILER} -+ ARGS ${CMAKE_C_COMPILER_ARG1} -dumpmachine -+ OUTPUT_VARIABLE _machine -+ ) -+ if (_machine STREQUAL mingw32) -+ set (MINGW32 1) -+ message(STATUS "found mingw 32 bit compiler") -+ elseif (_machine STREQUAL i686-w64-mingw32) -+ set (MINGW32 1) -+ set (MINGW_W32 1) -+ message(STATUS "found mingw 64 bit compiler") -+ elseif (_machine STREQUAL x86_64-w64-mingw32) -+ set (MINGW64 1) -+ set (MINGW_W64 1) -+ message(STATUS "found mingw 64 bit compiler") -+ endif (_machine STREQUAL mingw32) -+endif (NOT MINGW32 AND NOT MINGW64) -diff --git a/ConfigureChecks.cmake b/ConfigureChecks.cmake -new file mode 100644 -index 0000000..d04a045 ---- /dev/null -+++ b/ConfigureChecks.cmake -@@ -0,0 +1,273 @@ -+if(MSVC) -+ set(inline __inline) -+endif(MSVC) -+set(HAVE_THREADS 1) -+set(WITH_OUR_MALLOC16 1) -+set(STDC_HEADERS 1) -+set(HAVE_UINTPTR_T 1) -+set(HAVE_UINTPTR_T 1) -+set(HAVE_SIMD 1) -+set(HAVE_LONG_DOUBLE 1) -+set(HAVE_OPENMP ${ENABLE_OPENMP}) -+ -+if(FFTW_SINGLE) -+ set(HAVE_SSE 1) -+elseif(FFTW_DOUBLE) -+ set(HAVE_SSE2 1) # only works in double precision -+endif(FFTW_SINGLE) -+ -+if(FFTW_SINGLE) -+ set(FFTW_SYMBOL_PREFIX "fftwf_") -+elseif(FFTW_DOUBLE) -+ set(FFTW_SYMBOL_PREFIX "fftw_") -+elseif(FFTW_LDOUBLE) -+ set(FFTW_SYMBOL_PREFIX "fftwl_") -+endif(FFTW_SINGLE) -+ -+if(BUILD_BENCHMARKS) -+ set(BENCHFFT_SINGLE ${FFTW_SINGLE}) -+ set(BENCHFFT_DOUBLE ${FFTW_DOUBLE}) -+ set(BENCHFFT_LDOUBLE ${FFTW_LDOUBLE}) -+endif(BUILD_BENCHMARKS) -+#/* extra CFLAGS for codelets */ -+#cmakedefine CODELET_OPTIM @CODELET_OPTIM@ -+ -+include(CheckIncludeFiles) -+include(CheckSymbolExists) -+include(CheckTypeSize) -+ -+check_include_files(alloca.h HAVE_ALLOCA_H) -+check_include_files(stdlib.h HAVE_STDLIB_H) -+check_include_files(stdint.h HAVE_STDINT_H) -+check_include_files(stddef.h HAVE_STDDEF_H) -+check_include_files(altivec.h HAVE_ALTIVEC_H) -+check_include_files(c_asm.h HAVE_C_ASM_H) -+check_include_files(dlfcn.h HAVE_DLFCN_H) -+check_include_files(intrinsics.h HAVE_INTRINSICS_H) -+check_include_files(inttypes.h HAVE_INTTYPES_H) -+check_include_files(libintl.h HAVE_LIBINTL_H) -+check_include_files(limits.h HAVE_LIMITS_H) -+check_include_files(mach/mach_time.h HAVE_MACH_MACH_TIME_H) -+check_include_files(malloc.h HAVE_MALLOC_H) -+check_include_files(memory.h HAVE_MEMORY_H) -+check_include_files(stddef.h HAVE_STDDEF_H) -+check_include_files(stdint.h HAVE_STDINT_H) -+check_include_files(stdlib.h HAVE_STDLIB_H) -+check_include_files(strings.h HAVE_STRINGS_H) -+check_include_files(string.h HAVE_STRING_H) -+check_include_files(sys/stat.h HAVE_SYS_STAT_H) -+check_include_files(sys/sysctl.h HAVE_SYS_SYSCTL_H) -+check_include_files(sys/time.h HAVE_SYS_TIME_H) -+check_include_files(sys/types.h HAVE_SYS_TYPES_H) -+check_include_files(unistd.h HAVE_UNISTD_H) -+check_include_files(xmmintrin.h HAVE_XMMINTRIN_H) -+ -+check_type_size("double" SIZEOF_DOUBLE) -+check_type_size("float" SIZEOF_FLOAT) -+check_type_size("int" SIZEOF_INT) -+check_type_size("long" SIZEOF_LONG) -+check_type_size("long long" SIZEOF_LONG_LONG) -+check_type_size("ptrdiff_t" SIZEOF_PTRDIFF_T) -+check_type_size("size_t" SIZEOF_SIZE_T) -+check_type_size("unsigned int" SIZEOF_UNSIGNED_INT) -+check_type_size("unsigned long" SIZEOF_UNSIGNED_LONG) -+check_type_size("unsigned long long" SIZEOF_UNSIGNED_LONG_LONG) -+check_type_size("void*" SIZEOF_VOID_P) -+ -+###################################################################################### -+ -+#/* Define to enable extra FFTW debugging code. */ -+#cmakedefine FFTW_DEBUG -+ -+#/* Define to enable alignment debugging hacks. */ -+#cmakedefine FFTW_DEBUG_ALIGNMENT -+ -+#/* Define to enable debugging malloc. */ -+#cmakedefine FFTW_DEBUG_MALLOC -+ -+ -+#/* Define to 1 if using `alloca.c'. */ -+#cmakedefine C_ALLOCA -+ -+#/* Define to enable the use of alloca(). */ -+#cmakedefine FFTW_ENABLE_ALLOCA -+ -+#/* Define to 1 if you have `alloca', as a function or macro. */ -+#cmakedefine HAVE_ALLOCA -+ -+#/* Define to 1 if you have the `abort' function. */ -+#cmakedefine HAVE_ABORT -+ -+#/* Define to enable Altivec optimizations. */ -+#cmakedefine HAVE_ALTIVEC -+ -+#/* Define to 1 if you have the `BSDgettimeofday' function. */ -+#cmakedefine HAVE_BSDGETTIMEOFDAY -+ -+#/* Define to enable optimizations for the Cell Broadband Engine */ -+#cmakedefine HAVE_CELL -+ -+#/* Define to 1 if you have the `clock_gettime' function. */ -+#cmakedefine HAVE_CLOCK_GETTIME -+ -+#/* Define to 1 if you have the `cosl' function. */ -+#cmakedefine HAVE_COSL -+ -+#/* Define to 1 if you have the declaration of `cosl', and to 0 if you don't. -+# */ -+#cmakedefine HAVE_DECL_COSL -+ -+#/* Define to 1 if you have the declaration of `drand48', and to 0 if you -+# don't. */ -+#cmakedefine HAVE_DECL_DRAND48 -+ -+#/* Define to 1 if you have the declaration of `memalign', and to 0 if you -+# don't. */ -+#cmakedefine HAVE_DECL_MEMALIGN -+ -+#/* Define to 1 if you have the declaration of `posix_memalign', and to 0 if -+# you don't. */ -+#cmakedefine HAVE_DECL_POSIX_MEMALIGN -+ -+#/* Define to 1 if you have the declaration of `sinl', and to 0 if you don't. -+# */ -+#cmakedefine HAVE_DECL_SINL -+ -+#/* Define to 1 if you don't have `vprintf' but do have `_doprnt.' */ -+#cmakedefine HAVE_DOPRNT -+ -+#/* Define to 1 if you have the `drand48' function. */ -+#cmakedefine HAVE_DRAND48 -+ -+#/* Define if you have a machine with fused multiply-add */ -+#cmakedefine HAVE_FMA -+ -+#/* Define to 1 if you have the `gethrtime' function. */ -+#cmakedefine HAVE_GETHRTIME -+ -+#/* Define to 1 if you have the `gettimeofday' function. */ -+#cmakedefine HAVE_GETTIMEOFDAY -+ -+#/* Define to 1 if hrtime_t is defined in */ -+#cmakedefine HAVE_HRTIME_T -+ -+#/* Define if the isnan() function/macro is available. */ -+#cmakedefine HAVE_ISNAN -+ -+#/* Define to 1 if you have the `m' library (-lm). */ -+#cmakedefine HAVE_LIBM -+ -+#/* Define to 1 if you have the `spe' library (-lspe). */ -+#cmakedefine HAVE_LIBSPE -+ -+#/* Define to 1 if you have the `spe2' library (-lspe2). */ -+#cmakedefine HAVE_LIBSPE2 -+ -+#/* Define to 1 if you have the `mach_absolute_time' function. */ -+#cmakedefine HAVE_MACH_ABSOLUTE_TIME -+ -+#/* Define to 1 if you have the `memalign' function. */ -+#cmakedefine HAVE_MEMALIGN -+ -+#/* Define to 1 if you have the `memset' function. */ -+#cmakedefine HAVE_MEMSET -+ -+#/* Define to enable MIPS paired-single optimizations. */ -+#cmakedefine HAVE_MIPS_PS -+ -+#/* Define to enable use of MIPS ZBus cycle-counter. */ -+#cmakedefine HAVE_MIPS_ZBUS_TIMER -+ -+#/* Define if you have the MPI library. */ -+#cmakedefine HAVE_MPI -+ -+#/* Define if we have and are using OpenMP multithreading directives */ -+#cmakedefine USING_OPENMP_THREADS -+ -+#/* Define to 1 if you have the `posix_memalign' function. */ -+#cmakedefine HAVE_POSIX_MEMALIGN -+ -+#/* Define if you have POSIX threads libraries and header files. */ -+#cmakedefine HAVE_PTHREAD -+ -+#/* Define to 1 if you have the `read_real_time' function. */ -+#cmakedefine HAVE_READ_REAL_TIME -+ -+#/* Define to 1 if you have the `sinl' function. */ -+#cmakedefine HAVE_SINL -+ -+#/* Define to 1 if you have the `snprintf' function. */ -+#cmakedefine HAVE_SNPRINTF -+ -+#/* Define to 1 if you have the `sqrt' function. */ -+#cmakedefine HAVE_SQRT -+ -+#/* Define to enable SSE optimizations. */ -+#cmakedefine HAVE_SSE @HAVE_SSE@ -+ -+#/* Define to enable SSE2 optimizations. */ -+#cmakedefine HAVE_SSE2 @HAVE_SSE2@ -+ -+#/* Define to 1 if you have the `sysctl' function. */ -+#cmakedefine HAVE_SYSCTL -+ -+#/* Define to 1 if you have the `tanl' function. */ -+#cmakedefine HAVE_TANL -+ -+#/* Define if we have a threads library. */ -+#cmakedefine HAVE_THREADS -+ -+#/* Define to 1 if you have the `time_base_to_time' function. */ -+#cmakedefine HAVE_TIME_BASE_TO_TIME -+ -+#/* Define to 1 if the system has the type `uintptr_t'. */ -+#cmakedefine HAVE_UINTPTR_T -+ -+#/* Define to 1 if you have the `vprintf' function. */ -+#cmakedefine HAVE_VPRINTF -+ -+#/* Define to 1 if you have the `_mm_free' function. */ -+#cmakedefine HAVE__MM_FREE -+ -+#/* Define to 1 if you have the `_mm_malloc' function. */ -+#cmakedefine HAVE__MM_MALLOC -+ -+#/* Define if you have the UNICOS _rtc() intrinsic. */ -+#cmakedefine HAVE__RTC -+ -+#/* Define to necessary symbol if this constant uses a non-standard name on -+# your system. */ -+#cmakedefine PTHREAD_CREATE_JOINABLE -+ -+#/* If using the C implementation of alloca, define if you know the -+# direction of stack growth for your system; otherwise it will be -+# automatically deduced at runtime. -+# STACK_DIRECTION > 0 => grows toward higher addresses -+# STACK_DIRECTION < 0 => grows toward lower addresses -+# STACK_DIRECTION = 0 => direction of growth unknown */ -+#cmakedefine STACK_DIRECTION -+ -+#/* Define to 1 if you can safely include both and . */ -+#cmakedefine TIME_WITH_SYS_TIME -+ -+########################################################################## -+# stuff I don't know what to do with: -+#/* Use low-precision timers, making planner very slow */ -+#cmakedefine WITH_SLOW_TIMER -+#/* Define if we have and are using POSIX threads. */ -+#cmakedefine USING_POSIX_THREADS -+ -+# Fortran stuff: not implemented -+#cmakedefine DISABLE_FORTRAN -+#cmakedefine F77_DUMMY_MAIN -+#cmakedefine F77_FUNC -+#cmakedefine F77_FUNC_ -+#cmakedefine F77_FUNC_EQUIV -+#cmakedefine FC_DUMMY_MAIN_EQ_F77 -+#cmakedefine WINDOWS_F77_MANGLING -+#cmakedefine WITH_G77_WRAPPERS -+ -+ -+ -+configure_file(config.h.cmake config.h) -+configure_file(fftw.def.cmake fftw.def) -diff --git a/api/CMakeLists.txt b/api/CMakeLists.txt -new file mode 100644 -index 0000000..46aaf1f ---- /dev/null -+++ b/api/CMakeLists.txt -@@ -0,0 +1,41 @@ -+set(api_SRCS apiplan.c configure.c execute-dft-c2r.c -+execute-dft-r2c.c execute-dft.c execute-r2r.c execute-split-dft-c2r.c -+execute-split-dft-r2c.c execute-split-dft.c execute.c -+export-wisdom-to-file.c export-wisdom-to-string.c export-wisdom.c -+f77api.c flops.c forget-wisdom.c import-system-wisdom.c -+import-wisdom-from-file.c import-wisdom-from-string.c import-wisdom.c -+malloc.c map-r2r-kind.c mapflags.c mkprinter-file.c mktensor-iodims.c -+mktensor-rowmajor.c plan-dft-1d.c plan-dft-2d.c plan-dft-3d.c -+plan-dft-c2r-1d.c plan-dft-c2r-2d.c plan-dft-c2r-3d.c plan-dft-c2r.c -+plan-dft-r2c-1d.c plan-dft-r2c-2d.c plan-dft-r2c-3d.c plan-dft-r2c.c -+plan-dft.c plan-guru-dft-c2r.c plan-guru-dft-r2c.c plan-guru-dft.c -+plan-guru-r2r.c plan-guru-split-dft-c2r.c plan-guru-split-dft-r2c.c -+plan-guru-split-dft.c plan-many-dft-c2r.c plan-many-dft-r2c.c -+plan-many-dft.c plan-many-r2r.c plan-r2r-1d.c plan-r2r-2d.c -+plan-r2r-3d.c plan-r2r.c print-plan.c rdft2-pad.c the-planner.c -+version.c plan-guru64-dft-c2r.c -+plan-guru64-dft-r2c.c plan-guru64-dft.c plan-guru64-r2r.c -+plan-guru64-split-dft-c2r.c plan-guru64-split-dft-r2c.c -+plan-guru64-split-dft.c mktensor-iodims64.c -+) -+ -+if(BUILD_ALL_STATIC) -+ add_library(api STATIC ${api_SRCS}) -+else(BUILD_ALL_STATIC) -+ prepend_prefix(api_SRCS) -+ set(fftw_SRCS ${fftw_SRCS} ${api_SRCS} PARENT_SCOPE) -+endif(BUILD_ALL_STATIC) -+ -+ -+#BUILT_SOURCES = fftw3.f -+ -+#if MAINTAINER_MODE -+# -+# convert constants to F77 PARAMETER statements -+#fftw3.f: -+# rm -f $@ -+# perl -pe 's/([A-Z0-9_]+)=([0-9]+)/\n INTEGER \1\n PARAMETER (\1=\2)\n/g' $< |egrep 'PARAMETER|INTEGER' > $@ -+# perl -pe 's/#define +([A-Z0-9_]+) +\(([+-]?[0-9]+)U?\)/\n INTEGER \1\n PARAMETER (\1=\2)\n/g' $< |egrep 'PARAMETER|INTEGER' >> $@ -+# perl -pe 'if (/#define +([A-Z0-9_]+) +\(([0-9]+)U? *<< *([0-9]+)\)/) { print "\n INTEGER $$1\n PARAMETER ($$1=",$$2 << $$3,")\n"; }' $< |egrep 'PARAMETER|INTEGER' >> $@ -+# -+#endif # MAINTAINER_MODE -diff --git a/config.h.cmake b/config.h.cmake -new file mode 100644 -index 0000000..d8ef741 ---- /dev/null -+++ b/config.h.cmake -@@ -0,0 +1,380 @@ -+/* config.h.in. Generated from configure.ac by autoheader. */ -+ -+/* Define to compile in long-double precision. */ -+#cmakedefine BENCHFFT_LDOUBLE -+ -+/* Define to compile in single precision. */ -+#cmakedefine BENCHFFT_SINGLE -+ -+/* extra CFLAGS for codelets */ -+#cmakedefine CODELET_OPTIM @CODELET_OPTIM@ -+ -+/* Define to one of `_getb67', `GETB67', `getb67' for Cray-2 and Cray-YMP -+ systems. This function is required for `alloca.c' support on those systems. -+ */ -+#cmakedefine CRAY_STACKSEG_END -+ -+/* Define to 1 if using `alloca.c'. */ -+#cmakedefine C_ALLOCA -+ -+/* Define to disable Fortran wrappers. */ -+#cmakedefine DISABLE_FORTRAN -+ -+/* Define to dummy `main' function (if any) required to link to the Fortran -+ libraries. */ -+#cmakedefine F77_DUMMY_MAIN -+ -+/* Define to a macro mangling the given C identifier (in lower and upper -+ case), which must not contain underscores, for linking with Fortran. */ -+#cmakedefine F77_FUNC -+ -+/* As F77_FUNC, but for C identifiers containing underscores. */ -+#cmakedefine F77_FUNC_ -+ -+/* Define if F77_FUNC and F77_FUNC_ are equivalent. */ -+#cmakedefine F77_FUNC_EQUIV -+ -+/* Define if F77 and FC dummy `main' functions are identical. */ -+#cmakedefine FC_DUMMY_MAIN_EQ_F77 -+ -+/* C compiler name and flags */ -+#cmakedefine FFTW_CC @FFTW_CC@ -+ -+/* Define to enable extra FFTW debugging code. */ -+#cmakedefine FFTW_DEBUG -+ -+/* Define to enable alignment debugging hacks. */ -+#cmakedefine FFTW_DEBUG_ALIGNMENT -+ -+/* Define to enable debugging malloc. */ -+#cmakedefine FFTW_DEBUG_MALLOC -+ -+/* Define to enable the use of alloca(). */ -+#cmakedefine FFTW_ENABLE_ALLOCA -+ -+/* Define to compile in long-double precision. */ -+#cmakedefine FFTW_LDOUBLE -+ -+/* Define to compile in single precision. */ -+#cmakedefine FFTW_SINGLE -+ -+/* Define to 1 if you have the `abort' function. */ -+#cmakedefine HAVE_ABORT @HAVE_ABORT@ -+ -+/* Define to 1 if you have `alloca', as a function or macro. */ -+#cmakedefine HAVE_ALLOCA @HAVE_ALLOCA@ -+ -+/* Define to 1 if you have and it should be used (not on Ultrix). -+ */ -+#cmakedefine HAVE_ALLOCA_H @HAVE_ALLOCA_H@ -+ -+/* Define to enable Altivec optimizations. */ -+#cmakedefine HAVE_ALTIVEC @HAVE_ALTIVEC@ -+ -+/* Define to 1 if you have the header file. */ -+#cmakedefine HAVE_ALTIVEC_H @HAVE_ALTIVEC_H@ -+ -+/* Define to 1 if you have the `BSDgettimeofday' function. */ -+#cmakedefine HAVE_BSDGETTIMEOFDAY @HAVE_BSDGETTIMEOFDAY@ -+ -+/* Define to enable optimizations for the Cell Broadband Engine */ -+#cmakedefine HAVE_CELL @HAVE_CELL@ -+ -+/* Define to 1 if you have the `clock_gettime' function. */ -+#cmakedefine HAVE_CLOCK_GETTIME @HAVE_CLOCK_GETTIME@ -+ -+/* Define to 1 if you have the `cosl' function. */ -+#cmakedefine HAVE_COSL @HAVE_COSL@ -+ -+/* Define to 1 if you have the header file. */ -+#cmakedefine HAVE_C_ASM_H @HAVE_C_ASM_H@ -+ -+/* Define to 1 if you have the declaration of `cosl', and to 0 if you don't. -+ */ -+#cmakedefine HAVE_DECL_COSL @HAVE_DECL_COSL@ -+ -+/* Define to 1 if you have the declaration of `drand48', and to 0 if you -+ don't. */ -+#cmakedefine HAVE_DECL_DRAND48 @HAVE_DECL_DRAND48@ -+ -+/* Define to 1 if you have the declaration of `memalign', and to 0 if you -+ don't. */ -+#cmakedefine HAVE_DECL_MEMALIGN @HAVE_DECL_MEMALIGN@ -+ -+/* Define to 1 if you have the declaration of `posix_memalign', and to 0 if -+ you don't. */ -+#cmakedefine HAVE_DECL_POSIX_MEMALIGN @HAVE_DECL_POSIX_MEMALIGN@ -+ -+/* Define to 1 if you have the declaration of `sinl', and to 0 if you don't. -+ */ -+#cmakedefine HAVE_DECL_SINL @HAVE_DECL_SINL@ -+ -+/* Define to 1 if you have the header file. */ -+#cmakedefine HAVE_DLFCN_H @HAVE_DLFCN_H@ -+ -+/* Define to 1 if you don't have `vprintf' but do have `_doprnt.' */ -+#cmakedefine HAVE_DOPRNT @HAVE_DOPRNT@ -+ -+/* Define to 1 if you have the `drand48' function. */ -+#cmakedefine HAVE_DRAND48 @HAVE_DRAND48@ -+ -+/* Define if you have a machine with fused multiply-add */ -+#cmakedefine HAVE_FMA @HAVE_FMA@ -+ -+/* Define to 1 if you have the `gethrtime' function. */ -+#cmakedefine HAVE_GETHRTIME @HAVE_GETHRTIME@ -+ -+/* Define to 1 if you have the `gettimeofday' function. */ -+#cmakedefine HAVE_GETTIMEOFDAY @HAVE_GETTIMEOFDAY@ -+ -+/* Define to 1 if hrtime_t is defined in */ -+#cmakedefine HAVE_HRTIME_T @HAVE_HRTIME_T@ -+ -+/* Define to 1 if you have the header file. */ -+#cmakedefine HAVE_INTRINSICS_H @HAVE_INTRINSICS_H@ -+ -+/* Define to 1 if you have the header file. */ -+#cmakedefine HAVE_INTTYPES_H @HAVE_INTTYPES_H@ -+ -+/* Define if the isnan() function/macro is available. */ -+#cmakedefine HAVE_ISNAN @HAVE_ISNAN@ -+ -+/* Define to 1 if you have the header file. */ -+#cmakedefine HAVE_LIBINTL_H @HAVE_LIBINTL_H@ -+ -+/* Define to 1 if you have the `m' library (-lm). */ -+#cmakedefine HAVE_LIBM @HAVE_LIBM@ -+ -+/* Define to 1 if you have the `spe' library (-lspe). */ -+#cmakedefine HAVE_LIBSPE @HAVE_LIBSPE@ -+ -+/* Define to 1 if you have the `spe2' library (-lspe2). */ -+#cmakedefine HAVE_LIBSPE2 @HAVE_LIBSPE2@ -+ -+/* Define to 1 if you have the header file. */ -+#cmakedefine HAVE_LIMITS_H @HAVE_LIMITS_H@ -+ -+/* Define to 1 if the compiler supports `long double' */ -+#cmakedefine HAVE_LONG_DOUBLE @HAVE_LONG_DOUBLE@ -+ -+/* Define to 1 if you have the `mach_absolute_time' function. */ -+#cmakedefine HAVE_MACH_ABSOLUTE_TIME @HAVE_MACH_ABSOLUTE_TIME@ -+ -+/* Define to 1 if you have the header file. */ -+#cmakedefine HAVE_MACH_MACH_TIME_H @HAVE_MACH_MACH_TIME_H@ -+ -+/* Define to 1 if you have the header file. */ -+#cmakedefine HAVE_MALLOC_H @HAVE_MALLOC_H@ -+ -+/* Define to 1 if you have the `memalign' function. */ -+#cmakedefine HAVE_MEMALIGN @HAVE_MEMALIGN@ -+ -+/* Define to 1 if you have the header file. */ -+#cmakedefine HAVE_MEMORY_H @HAVE_MEMORY_H@ -+ -+/* Define to 1 if you have the `memset' function. */ -+#cmakedefine HAVE_MEMSET @HAVE_MEMSET@ -+ -+/* Define to enable MIPS paired-single optimizations. */ -+#cmakedefine HAVE_MIPS_PS @HAVE_MIPS_PS@ -+ -+/* Define to enable use of MIPS ZBus cycle-counter. */ -+#cmakedefine HAVE_MIPS_ZBUS_TIMER @HAVE_MIPS_ZBUS_TIMER@ -+ -+/* Define if you have the MPI library. */ -+#cmakedefine HAVE_MPI @HAVE_MPI@ -+ -+/* Define to enable OpenMP */ -+#cmakedefine HAVE_OPENMP @HAVE_OPENMP@ -+ -+/* Define to 1 if you have the `posix_memalign' function. */ -+#cmakedefine HAVE_POSIX_MEMALIGN @HAVE_POSIX_MEMALIGN@ -+ -+/* Define if you have POSIX threads libraries and header files. */ -+#cmakedefine HAVE_PTHREAD @HAVE_PTHREAD@ -+ -+/* Define to 1 if you have the `read_real_time' function. */ -+#cmakedefine HAVE_READ_REAL_TIME @HAVE_READ_REAL_TIME@ -+ -+/* Define to 1 if you have the `sinl' function. */ -+#cmakedefine HAVE_SINL @HAVE_SINL@ -+ -+/* Define to 1 if you have the `snprintf' function. */ -+#cmakedefine HAVE_SNPRINTF @HAVE_SNPRINTF@ -+ -+/* Define to 1 if you have the `sqrt' function. */ -+#cmakedefine HAVE_SQRT @HAVE_SQRT@ -+ -+/* Define to enable SSE optimizations. */ -+#cmakedefine HAVE_SSE @HAVE_SSE@ -+ -+/* Define to enable SSE2 optimizations. */ -+#cmakedefine HAVE_SSE2 @HAVE_SSE2@ -+ -+/* Define to 1 if you have the header file. */ -+#cmakedefine HAVE_STDDEF_H @HAVE_STDDEF_H@ -+ -+/* Define to 1 if you have the header file. */ -+#cmakedefine HAVE_STDINT_H @HAVE_STDINT_H@ -+ -+/* Define to 1 if you have the header file. */ -+#cmakedefine HAVE_STDLIB_H @HAVE_STDLIB_H@ -+ -+/* Define to 1 if you have the header file. */ -+#cmakedefine HAVE_STRINGS_H @HAVE_STRINGS_H@ -+ -+/* Define to 1 if you have the header file. */ -+#cmakedefine HAVE_STRING_H @HAVE_STRING_H@ -+ -+/* Define to 1 if you have the `sysctl' function. */ -+#cmakedefine HAVE_SYSCTL @HAVE_SYSCTL@ -+ -+/* Define to 1 if you have the header file. */ -+#cmakedefine HAVE_SYS_STAT_H @HAVE_SYS_STAT_H@ -+ -+/* Define to 1 if you have the header file. */ -+#cmakedefine HAVE_SYS_SYSCTL_H @HAVE_SYS_SYSCTL_H@ -+ -+/* Define to 1 if you have the header file. */ -+#cmakedefine HAVE_SYS_TIME_H @HAVE_SYS_TIME_H@ -+ -+/* Define to 1 if you have the header file. */ -+#cmakedefine HAVE_SYS_TYPES_H @HAVE_SYS_TYPES_H@ -+ -+/* Define to 1 if you have the `tanl' function. */ -+#cmakedefine HAVE_TANL @HAVE_TANL@ -+ -+/* Define if we have a threads library. */ -+#cmakedefine HAVE_THREADS @HAVE_THREADS@ -+ -+/* Define to 1 if you have the `time_base_to_time' function. */ -+#cmakedefine HAVE_TIME_BASE_TO_TIME @HAVE_TIME_BASE_TO_TIME@ -+ -+/* Define to 1 if the system has the type `uintptr_t'. */ -+#cmakedefine HAVE_UINTPTR_T @HAVE_UINTPTR_T@ -+ -+/* Define to 1 if you have the header file. */ -+#cmakedefine HAVE_UNISTD_H @HAVE_UNISTD_H@ -+ -+/* Define to 1 if you have the `vprintf' function. */ -+#cmakedefine HAVE_VPRINTF @HAVE_VPRINTF@ -+ -+/* Define to 1 if you have the header file. */ -+#cmakedefine HAVE_XMMINTRIN_H @HAVE_XMMINTRIN_H@ -+ -+/* Define to 1 if you have the `_mm_free' function. */ -+#cmakedefine HAVE__MM_FREE @HAVE__MM_FREE@ -+ -+/* Define to 1 if you have the `_mm_malloc' function. */ -+#cmakedefine HAVE__MM_MALLOC @HAVE__MM_MALLOC@ -+ -+/* Define if you have the UNICOS _rtc() intrinsic. */ -+#cmakedefine HAVE__RTC @HAVE__RTC@ -+ -+/* Define to the sub-directory in which libtool stores uninstalled libraries. -+ */ -+#cmakedefine LT_OBJDIR -+ -+/* Name of package */ -+#cmakedefine PACKAGE @PACKAGE@ -+ -+/* Define to the address where bug reports for this package should be sent. */ -+#cmakedefine PACKAGE_BUGREPORT -+ -+/* Define to the full name of this package. */ -+#cmakedefine PACKAGE_NAME -+ -+/* Define to the full name and version of this package. */ -+#cmakedefine PACKAGE_STRING -+ -+/* Define to the one symbol short name of this package. */ -+#cmakedefine PACKAGE_TARNAME -+ -+/* Define to the version of this package. */ -+#cmakedefine PACKAGE_VERSION @PACKAGE_VERSION@ -+ -+/* Define to necessary symbol if this constant uses a non-standard name on -+ your system. */ -+#cmakedefine PTHREAD_CREATE_JOINABLE -+ -+/* The size of `double', as computed by sizeof. */ -+#cmakedefine SIZEOF_DOUBLE @SIZEOF_DOUBLE@ -+ -+/* The size of `float', as computed by sizeof. */ -+#cmakedefine SIZEOF_FLOAT @SIZEOF_FLOAT@ -+ -+/* The size of `int', as computed by sizeof. */ -+#cmakedefine SIZEOF_INT @SIZEOF_INT@ -+ -+/* The size of `long', as computed by sizeof. */ -+#cmakedefine SIZEOF_LONG @SIZEOF_LONG@ -+ -+/* The size of `long long', as computed by sizeof. */ -+#cmakedefine SIZEOF_LONG_LONG @SIZEOF_LONG_LONG@ -+ -+/* The size of `ptrdiff_t', as computed by sizeof. */ -+#cmakedefine SIZEOF_PTRDIFF_T @SIZEOF_PTRDIFF_T@ -+ -+/* The size of `size_t', as computed by sizeof. */ -+#cmakedefine SIZEOF_SIZE_T @SIZEOF_SIZE_T@ -+ -+/* The size of `unsigned int', as computed by sizeof. */ -+#cmakedefine SIZEOF_UNSIGNED_INT @SIZEOF_UNSIGNED_INT@ -+ -+/* The size of `unsigned long', as computed by sizeof. */ -+#cmakedefine SIZEOF_UNSIGNED_LONG @SIZEOF_UNSIGNED_LONG@ -+ -+/* The size of `unsigned long long', as computed by sizeof. */ -+#cmakedefine SIZEOF_UNSIGNED_LONG_LONG @SIZEOF_UNSIGNED_LONG_LONG@ -+ -+/* The size of `void *', as computed by sizeof. */ -+#cmakedefine SIZEOF_VOID_P @SIZEOF_VOID_P@ -+ -+/* If using the C implementation of alloca, define if you know the -+ direction of stack growth for your system; otherwise it will be -+ automatically deduced at runtime. -+ STACK_DIRECTION > 0 => grows toward higher addresses -+ STACK_DIRECTION < 0 => grows toward lower addresses -+ STACK_DIRECTION = 0 => direction of growth unknown */ -+#cmakedefine STACK_DIRECTION -+ -+/* Define to 1 if you have the ANSI C header files. */ -+#cmakedefine STDC_HEADERS -+ -+/* Define to 1 if you can safely include both and . */ -+#cmakedefine TIME_WITH_SYS_TIME -+ -+/* Define if we have and are using OpenMP multithreading directives */ -+#cmakedefine USING_OPENMP_THREADS -+ -+/* Define if we have and are using POSIX threads. */ -+#cmakedefine USING_POSIX_THREADS -+ -+/* Version number of package */ -+#cmakedefine VERSION @VERSION@ -+ -+/* Use common Windows Fortran mangling styles for the Fortran interfaces. */ -+#cmakedefine WINDOWS_F77_MANGLING -+ -+/* Include g77-compatible wrappers in addition to any other Fortran wrappers. -+ */ -+#cmakedefine WITH_G77_WRAPPERS -+ -+/* Use our own 16-byte aligned malloc routine; mainly helpful for Windows -+ systems lacking aligned allocation system-library routines. */ -+#cmakedefine WITH_OUR_MALLOC16 -+ -+/* Use low-precision timers, making planner very slow */ -+#cmakedefine WITH_SLOW_TIMER -+ -+/* Define to empty if `const' does not conform to ANSI C. */ -+#cmakedefine const -+ -+/* Define to `__inline__' or `__inline' if that's what the C compiler -+ calls it, or to nothing if 'inline' is not supported under any name. */ -+#ifndef __cplusplus -+#cmakedefine inline @inline@ -+#endif -+ -+/* Define to `unsigned int' if does not define. */ -+#cmakedefine size_t -diff --git a/dft/CMakeLists.txt b/dft/CMakeLists.txt -new file mode 100644 -index 0000000..24cc3b6 ---- /dev/null -+++ b/dft/CMakeLists.txt -@@ -0,0 +1,19 @@ -+#SUBDIRS = scalar simd -+add_subdirectory(scalar) -+add_subdirectory(simd) -+ -+# pkgincludedir = $(includedir)/fftw3@PREC_SUFFIX@ -+# pkginclude_HEADERS = codelet-dft.h dft.h -+ -+set(dft_SRCS bluestein.c buffered.c conf.c ct.c dftw-direct.c -+dftw-directsq.c dftw-generic.c dftw-genericbuf.c direct.c generic.c -+indirect.c indirect-transpose.c kdft-dif.c kdft-difsq.c kdft-dit.c -+kdft.c nop.c plan.c problem.c rader.c rank-geq2.c solve.c vrank-geq1.c -+zero.c) -+ -+if(BUILD_ALL_STATIC) -+ add_library(dft STATIC ${dft_SRCS}) -+else(BUILD_ALL_STATIC) -+ prepend_prefix(dft_SRCS) -+ set(fftw_SRCS ${fftw_SRCS} ${dft_SRCS} ${_fftw_SRCS} PARENT_SCOPE) -+endif(BUILD_ALL_STATIC) -diff --git a/dft/scalar/CMakeLists.txt b/dft/scalar/CMakeLists.txt -new file mode 100644 -index 0000000..2981db9 ---- /dev/null -+++ b/dft/scalar/CMakeLists.txt -@@ -0,0 +1,10 @@ -+add_subdirectory(codelets) -+ -+set(dft_scalar_SRCS ${dft_scalar_SRCS} n.c t.c) -+ -+if(BUILD_ALL_STATIC) -+ add_library(dft_scalar STATIC ${dft_scalar_SRCS}) -+else(BUILD_ALL_STATIC) -+ prepend_prefix(dft_scalar_SRCS) -+ set(_fftw_SRCS ${_fftw_SRCS} ${dft_scalar_SRCS} ${__fftw_SRCS} PARENT_SCOPE) -+endif(BUILD_ALL_STATIC) -diff --git a/dft/scalar/codelets/CMakeLists.txt b/dft/scalar/codelets/CMakeLists.txt -new file mode 100644 -index 0000000..174eced ---- /dev/null -+++ b/dft/scalar/codelets/CMakeLists.txt -@@ -0,0 +1,113 @@ -+# This Makefile.am specifies a set of codelets, efficient transforms -+# of small sizes, that are used as building blocks (kernels) by FFTW -+# to build up large transforms, as well as the options for generating -+# and compiling them. -+ -+# You can customize FFTW for special needs, e.g. to handle certain -+# sizes more efficiently, by adding new codelets to the lists of those -+# included by default. If you change the list of codelets, any new -+# ones you added will be automatically generated when you run the -+# bootstrap script (see "Generating your own code" in the FFTW -+# manual). -+ -+########################################################################### -+ -+########################################################################### -+# n1_ is a hard-coded FFT of size (base cases of FFT recursion) -+set(N1 n1_2.c n1_3.c n1_4.c n1_5.c n1_6.c n1_7.c n1_8.c n1_9.c n1_10.c -+n1_11.c n1_12.c n1_13.c n1_14.c n1_15.c n1_16.c n1_32.c n1_64.c -+n1_20.c n1_25.c # n1_30.c n1_40.c n1_50.c -+) -+ -+########################################################################### -+# t1_ is a "twiddle" FFT of size , implementing a radix-r DIT step -+set(T1 t1_2.c t1_3.c t1_4.c t1_5.c t1_6.c t1_7.c t1_8.c t1_9.c -+t1_10.c t1_12.c t1_15.c t1_16.c t1_32.c t1_64.c -+t1_20.c t1_25.c # t1_30.c t1_40.c t1_50.c -+) -+ -+# t2_ is also a twiddle FFT, but instead of using a complete lookup table -+# of trig. functions, it partially generates the trig. values on the fly -+# (this is faster for large sizes). -+set(T2 t2_4.c t2_8.c t2_16.c t2_32.c t2_64.c -+ t2_5.c t2_10.c t2_20.c t2_25.c -+) -+ -+########################################################################### -+# The F (DIF) codelets are used for a kind of in-place transform algorithm, -+# but the planner seems to never (or hardly ever) use them on the machines -+# we have access to, preferring the Q codelets and the use of buffers -+# for sub-transforms. So, we comment them out, at least for now. -+ -+# f1_ is a "twiddle" FFT of size , implementing a radix-r DIF step -+#F1 = # f1_2.c f1_3.c f1_4.c f1_5.c f1_6.c f1_7.c f1_8.c f1_9.c f1_10.c f1_12.c f1_15.c f1_16.c f1_32.c f1_64.c -+ -+# like f1, but partially generates its trig. table on the fly -+#F2 = # f2_4.c f2_8.c f2_16.c f2_32.c f2_64.c -+ -+########################################################################### -+# q1_ is twiddle FFTs of size (DIF step), where the output is -+# transposed. This is used for in-place transposes in sizes that are -+# divisible by ^2. These codelets have size ~ ^2, so you should -+# probably not use bigger than 8 or so. -+set(Q1 q1_2.c q1_4.c q1_8.c q1_3.c q1_5.c q1_6.c) -+ -+########################################################################### -+set(ALL_CODELETS ${N1} ${T1} ${T2} ${F1} ${F2} ${Q1}) -+ -+include(${CMAKE_SOURCE_DIR}/support/codelets.cmake) -+ -+set(SOLVTAB_NAME "X(solvtab_dft_standard)") -+ -+set(CODLIST ${CMAKE_CURRENT_BINARY_DIR}/codlist.c) -+set(CODELET_NAME codelet_) -+ -+write_codelet_list(${CODLIST} ${CODELET_NAME} ${SOLVTAB_NAME} ${ALL_CODELETS}) -+ -+set(dft_scalar_codelets_SRCS ${ALL_CODELETS}) -+ -+if(BUILD_ALL_STATIC) -+ add_library(dft_scalar_codelets STATIC ${dft_scalar_codelets_SRCS}) -+else(BUILD_ALL_STATIC) -+ prepend_prefix(dft_scalar_codelets_SRCS) -+ set(__fftw_SRCS ${__fftw_SRCS} ${dft_scalar_codelets_SRCS} ${CODLIST} PARENT_SCOPE) -+endif(BUILD_ALL_STATIC) -+ -+# this is old source code which can be changed later -+# only delete codlist.c in maintainer-mode, since it is included in the dist -+# FIXME: is there a way to delete in 'make clean' only when builddir != srcdir? -+#maintainer-clean-local: -+# rm -f $(CODLIST) -+ -+#if MAINTAINER_MODE -+#FLAGS_N1=$(DFT_FLAGS_COMMON) -+#FLAGS_T1=$(DFT_FLAGS_COMMON) -+#FLAGS_T2=$(DFT_FLAGS_COMMON) -twiddle-log3 -precompute-twiddles -+#FLAGS_F1=$(DFT_FLAGS_COMMON) -+#FLAGS_F2=$(DFT_FLAGS_COMMON) -twiddle-log3 -precompute-twiddles -+#FLAGS_Q1=$(DFT_FLAGS_COMMON) -reload-twiddle -+#FLAGS_Q2=$(DFT_FLAGS_COMMON) -twiddle-log3 -precompute-twiddles -+# -+#n1_%.c: $(CODELET_DEPS) $(GEN_NOTW) -+# ($(PRELUDE_COMMANDS_DFT); $(TWOVERS) $(GEN_NOTW) $(FLAGS_N1) -n $* -name n1_$* -include "n.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#t1_%.c: $(CODELET_DEPS) $(GEN_TWIDDLE) -+# ($(PRELUDE_COMMANDS_DFT); $(TWOVERS) $(GEN_TWIDDLE) $(FLAGS_T1) -n $* -name t1_$* -include "t.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#t2_%.c: $(CODELET_DEPS) $(GEN_TWIDDLE) -+# ($(PRELUDE_COMMANDS_DFT); $(TWOVERS) $(GEN_TWIDDLE) $(FLAGS_T2) -n $* -name t2_$* -include "t.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#f1_%.c: $(CODELET_DEPS) $(GEN_TWIDDLE) -+# ($(PRELUDE_COMMANDS_DFT); $(TWOVERS) $(GEN_TWIDDLE) $(FLAGS_F1) -dif -n $* -name f1_$* -include "f.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#f2_%.c: $(CODELET_DEPS) $(GEN_TWIDDLE) -+# ($(PRELUDE_COMMANDS_DFT); $(TWOVERS) $(GEN_TWIDDLE) $(FLAGS_F2) -dif -n $* -name f2_$* -include "f.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#q1_%.c: $(CODELET_DEPS) $(GEN_TWIDSQ) -+# ($(PRELUDE_COMMANDS_DFT); $(TWOVERS) $(GEN_TWIDSQ) $(FLAGS_Q1) -dif -n $* -name q1_$* -include "q.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#q2_%.c: $(CODELET_DEPS) $(GEN_TWIDSQ) -+# ($(PRELUDE_COMMANDS_DFT); $(TWOVERS) $(GEN_TWIDSQ) $(FLAGS_Q2) -dif -n $* -name q2_$* -include "q.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#endif # MAINTAINER_MODE -+ -diff --git a/dft/simd/CMakeLists.txt b/dft/simd/CMakeLists.txt -new file mode 100644 -index 0000000..a36a198 ---- /dev/null -+++ b/dft/simd/CMakeLists.txt -@@ -0,0 +1,10 @@ -+add_subdirectory(codelets) -+ -+set(dft_simd_SRCS n1b.c n1f.c n2b.c n2f.c n2s.c q1b.c q1f.c t.c ts.c) -+ -+if(BUILD_ALL_STATIC) -+ add_library(dft_simd STATIC ${dft_simd_SRCS}) -+else(BUILD_ALL_STATIC) -+ prepend_prefix(dft_simd_SRCS) -+ set(_fftw_SRCS ${_fftw_SRCS} ${dft_simd_SRCS} ${__fftw_SRCS} PARENT_SCOPE) -+endif(BUILD_ALL_STATIC) -diff --git a/dft/simd/codelets/CMakeLists.txt b/dft/simd/codelets/CMakeLists.txt -new file mode 100644 -index 0000000..74766d2 ---- /dev/null -+++ b/dft/simd/codelets/CMakeLists.txt -@@ -0,0 +1,189 @@ -+# This Makefile.am specifies a set of codelets, efficient transforms -+# of small sizes, that are used as building blocks (kernels) by FFTW -+# to build up large transforms, as well as the options for generating -+# and compiling them. -+ -+# You can customize FFTW for special needs, e.g. to handle certain -+# sizes more efficiently, by adding new codelets to the lists of those -+# included by default. If you change the list of codelets, any new -+# ones you added will be automatically generated when you run the -+# bootstrap script (see "Generating your own code" in the FFTW -+# manual). -+ -+########################################################################### -+#AM_CPPFLAGS = -I$(top_srcdir)/kernel -I$(top_srcdir)/dft \ -+#-I$(top_srcdir)/dft/simd -I$(top_srcdir)/simd -+#AM_CFLAGS = $(SIMD_CFLAGS) -+#noinst_LTLIBRARIES = libdft_simd_codelets.la -+ -+########################################################################### -+# n1fv_ is a hard-coded FFTW_FORWARD FFT of size , using SIMD -+set(N1F n1fv_2.c n1fv_3.c n1fv_4.c n1fv_5.c n1fv_6.c n1fv_7.c n1fv_8.c -+n1fv_9.c n1fv_10.c n1fv_11.c n1fv_12.c n1fv_13.c n1fv_14.c n1fv_15.c -+n1fv_16.c n1fv_32.c n1fv_64.c n1fv_128.c -+n1fv_20.c n1fv_25.c # n1fv_30.c n1fv_40.c n1fv_50.c -+) -+ -+# as above, with restricted input vector stride -+set(N2F n2fv_2.c n2fv_4.c n2fv_6.c n2fv_8.c n2fv_10.c n2fv_12.c -+n2fv_14.c n2fv_16.c n2fv_32.c n2fv_64.c -+n2fv_20.c # n2fv_30.c n2fv_40.c n2fv_50.c -+) -+ -+# as above, but FFTW_BACKWARD -+set(N1B n1bv_2.c n1bv_3.c n1bv_4.c n1bv_5.c n1bv_6.c n1bv_7.c n1bv_8.c -+n1bv_9.c n1bv_10.c n1bv_11.c n1bv_12.c n1bv_13.c n1bv_14.c n1bv_15.c -+n1bv_16.c n1bv_32.c n1bv_64.c n1bv_128.c -+n1bv_20.c n1bv_25.c # n1bv_30.c n1bv_40.c n1bv_50.c -+) -+ -+set(N2B n2bv_2.c n2bv_4.c n2bv_6.c n2bv_8.c n2bv_10.c n2bv_12.c -+n2bv_14.c n2bv_16.c n2bv_32.c n2bv_64.c -+n2bv_20.c # n2bv_30.c n2bv_40.c n2bv_50.c -+) -+ -+# split-complex codelets -+set(N2S n2sv_4.c n2sv_8.c n2sv_16.c n2sv_32.c n2sv_64.c -+) -+ -+########################################################################### -+# t1fv_ is a "twiddle" FFT of size , implementing a radix-r DIT step -+# for an FFTW_FORWARD transform, using SIMD -+set(T1F t1fv_2.c t1fv_3.c t1fv_4.c t1fv_5.c t1fv_6.c t1fv_7.c t1fv_8.c -+t1fv_9.c t1fv_10.c t1fv_12.c t1fv_15.c t1fv_16.c t1fv_32.c t1fv_64.c -+t1fv_20.c t1fv_25.c # t1fv_30.c t1fv_40.c t1fv_50.c -+) -+ -+# same as t1fv_*, but with different twiddle storage scheme -+set(T2F t2fv_2.c t2fv_4.c t2fv_8.c t2fv_16.c t2fv_32.c t2fv_64.c -+t2fv_5.c t2fv_10.c t2fv_20.c t2fv_25.c -+) -+set(T3F t3fv_4.c t3fv_8.c t3fv_16.c t3fv_32.c -+t3fv_5.c t3fv_10.c t3fv_20.c t3fv_25.c -+) -+set(T1FU t1fuv_2.c t1fuv_3.c t1fuv_4.c t1fuv_5.c t1fuv_6.c t1fuv_7.c -+t1fuv_8.c t1fuv_9.c t1fuv_10.c -+) -+ -+# as above, but FFTW_BACKWARD -+set(T1B t1bv_2.c t1bv_3.c t1bv_4.c t1bv_5.c t1bv_6.c t1bv_7.c t1bv_8.c -+t1bv_9.c t1bv_10.c t1bv_12.c t1bv_15.c t1bv_16.c t1bv_32.c t1bv_64.c -+t1bv_20.c t1bv_25.c # t1bv_30.c t1bv_40.c t1bv_50.c -+) -+ -+# same as t1bv_*, but with different twiddle storage scheme -+set(T2B t2bv_2.c t2bv_4.c t2bv_8.c t2bv_16.c t2bv_32.c t2bv_64.c -+t2bv_5.c t2bv_10.c t2bv_20.c t2bv_25.c -+) -+set(T3B t3bv_4.c t3bv_8.c t3bv_16.c t3bv_32.c -+t3bv_5.c t3bv_10.c t3bv_20.c t3bv_25.c -+) -+set(T1BU t1buv_2.c t1buv_3.c t1buv_4.c t1buv_5.c t1buv_6.c t1buv_7.c -+t1buv_8.c t1buv_9.c t1buv_10.c -+) -+ -+# split-complex codelets -+set(T1S t1sv_2.c t1sv_4.c t1sv_8.c t1sv_16.c t1sv_32.c #t1sv_64.c -+) -+set(T2S t2sv_4.c t2sv_8.c t2sv_16.c t2sv_32.c #t2sv_64.c -+) -+ -+########################################################################### -+# q1fv_ is twiddle FFTW_FORWARD FFTs of size (DIF step), -+# where the output is transposed, using SIMD. This is used for -+# in-place transposes in sizes that are divisible by ^2. These -+# codelets have size ~ ^2, so you should probably not use -+# bigger than 8 or so. -+set(Q1F q1fv_2.c q1fv_4.c q1fv_5.c q1fv_8.c ) -+ -+# as above, but FFTW_BACKWARD -+set(Q1B q1bv_2.c q1bv_4.c q1bv_5.c q1bv_8.c) -+ -+########################################################################### -+set(SIMD_CODELETS ${N1F} ${N1B} ${N2F} ${N2B} ${N2S} ${T1FU} ${T1F} ${T2F} ${T3F} ${T1BU} ${T1B} ${T2B} ${T3B} ${T1S} ${T2S} ${Q1F} ${Q1B}) -+ -+if(HAVE_SIMD) -+ set(ALL_CODELETS ${SIMD_CODELETS}) -+else(HAVE_SIMD) -+ set(ALL_CODELETS) -+endif(HAVE_SIMD) -+ -+set(SOLVTAB_NAME "X(solvtab_dft_simd)") -+set(CODLIST ${CMAKE_CURRENT_BINARY_DIR}/codlist.c) -+set(CODELET_NAME codelet_) -+ -+set(dft_simd_codelets_SRCS ${ALL_CODELETS}) -+ -+# special rules for regenerating codelets. -+include(${CMAKE_SOURCE_DIR}/support/codelets.cmake) -+ -+write_codelet_list(${CODLIST} ${CODELET_NAME} ${SOLVTAB_NAME} ${ALL_CODELETS}) -+ -+if(BUILD_ALL_STATIC) -+ add_library(dft_simd_codelets STATIC ${ALL_CODELETS}) -+else(BUILD_ALL_STATIC) -+ prepend_prefix(dft_simd_codelets_SRCS) -+ set(__fftw_SRCS ${__fftw_SRCS} ${dft_simd_codelets_SRCS} ${CODLIST} PARENT_SCOPE) -+endif(BUILD_ALL_STATIC) -+ -+#include $(top_srcdir)/support/Makefile.codelets -+ -+#if MAINTAINER_MODE -+#GFLAGS = -simd $(FLAGS_COMMON) -pipeline-latency 8 -+#FLAGS_T2S=-twiddle-log3 -precompute-twiddles -+#FLAGS_T3=-twiddle-log3 -precompute-twiddles -no-generate-bytw -+# -+#n1fv_%.c: $(CODELET_DEPS) $(GEN_NOTW_C) -+# ($(PRELUDE_COMMANDS_DFT); $(TWOVERS) $(GEN_NOTW_C) $(GFLAGS) -n $* -name n1fv_$* -include "n1f.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#n2fv_%.c: $(CODELET_DEPS) $(GEN_NOTW_C) -+# ($(PRELUDE_COMMANDS_DFT); $(TWOVERS) $(GEN_NOTW_C) $(GFLAGS) -n $* -name n2fv_$* -with-ostride 2 -include "n2f.h" -store-multiple 2) | $(ADD_DATE) | $(INDENT) >$@ -+# -+#n1bv_%.c: $(CODELET_DEPS) $(GEN_NOTW_C) -+# ($(PRELUDE_COMMANDS_DFT); $(TWOVERS) $(GEN_NOTW_C) $(GFLAGS) -sign 1 -n $* -name n1bv_$* -include "n1b.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#n2bv_%.c: $(CODELET_DEPS) $(GEN_NOTW_C) -+# ($(PRELUDE_COMMANDS_DFT); $(TWOVERS) $(GEN_NOTW_C) $(GFLAGS) -sign 1 -n $* -name n2bv_$* -with-ostride 2 -include "n2b.h" -store-multiple 2) | $(ADD_DATE) | $(INDENT) >$@ -+# -+#n2sv_%.c: $(CODELET_DEPS) $(GEN_NOTW) -+# ($(PRELUDE_COMMANDS_DFT); $(TWOVERS) $(GEN_NOTW) $(GFLAGS) -n $* -name n2sv_$* -with-ostride 1 -include "n2s.h" -store-multiple 4) | $(ADD_DATE) | $(INDENT) >$@ -+# -+#t1fv_%.c: $(CODELET_DEPS) $(GEN_TWIDDLE_C) -+# ($(PRELUDE_COMMANDS_DFT); $(TWOVERS) $(GEN_TWIDDLE_C) $(GFLAGS) -n $* -name t1fv_$* -include "t1f.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#t1fuv_%.c: $(CODELET_DEPS) $(GEN_TWIDDLE_C) -+# ($(PRELUDE_COMMANDS_DFT); $(TWOVERS) $(GEN_TWIDDLE_C) $(GFLAGS) -n $* -name t1fuv_$* -include "t1fu.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#t2fv_%.c: $(CODELET_DEPS) $(GEN_TWIDDLE_C) -+# ($(PRELUDE_COMMANDS_DFT); $(TWOVERS) $(GEN_TWIDDLE_C) $(GFLAGS) -n $* -name t2fv_$* -include "t2f.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#t3fv_%.c: $(CODELET_DEPS) $(GEN_TWIDDLE_C) -+# ($(PRELUDE_COMMANDS_DFT); $(TWOVERS) $(GEN_TWIDDLE_C) $(GFLAGS) $(FLAGS_T3) -n $* -name t3fv_$* -include "t3f.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#t1bv_%.c: $(CODELET_DEPS) $(GEN_TWIDDLE_C) -+# ($(PRELUDE_COMMANDS_DFT); $(TWOVERS) $(GEN_TWIDDLE_C) $(GFLAGS) -n $* -name t1bv_$* -include "t1b.h" -sign 1) | $(ADD_DATE) | $(INDENT) >$@ -+# -+#t1buv_%.c: $(CODELET_DEPS) $(GEN_TWIDDLE_C) -+# ($(PRELUDE_COMMANDS_DFT); $(TWOVERS) $(GEN_TWIDDLE_C) $(GFLAGS) -n $* -name t1buv_$* -include "t1bu.h" -sign 1) | $(ADD_DATE) | $(INDENT) >$@ -+# -+#t2bv_%.c: $(CODELET_DEPS) $(GEN_TWIDDLE_C) -+# ($(PRELUDE_COMMANDS_DFT); $(TWOVERS) $(GEN_TWIDDLE_C) $(GFLAGS) -n $* -name t2bv_$* -include "t2b.h" -sign 1) | $(ADD_DATE) | $(INDENT) >$@ -+# -+#t3bv_%.c: $(CODELET_DEPS) $(GEN_TWIDDLE_C) -+# ($(PRELUDE_COMMANDS_DFT); $(TWOVERS) $(GEN_TWIDDLE_C) $(GFLAGS) $(FLAGS_T3) -n $* -name t3bv_$* -include "t3b.h" -sign 1) | $(ADD_DATE) | $(INDENT) >$@ -+# -+#t1sv_%.c: $(CODELET_DEPS) $(GEN_TWIDDLE) -+# ($(PRELUDE_COMMANDS_DFT); $(TWOVERS) $(GEN_TWIDDLE) $(GFLAGS) -n $* -name t1sv_$* -include "ts.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#t2sv_%.c: $(CODELET_DEPS) $(GEN_TWIDDLE) -+# ($(PRELUDE_COMMANDS_DFT); $(TWOVERS) $(GEN_TWIDDLE) $(GFLAGS) $(FLAGS_T2S) -n $* -name t2sv_$* -include "ts.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#q1fv_%.c: $(CODELET_DEPS) $(GEN_TWIDSQ_C) -+# ($(PRELUDE_COMMANDS_DFT); $(TWOVERS) $(GEN_TWIDSQ_C) $(GFLAGS) -n $* -dif -name q1fv_$* -include "q1f.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#q1bv_%.c: $(CODELET_DEPS) $(GEN_TWIDSQ_C) -+# ($(PRELUDE_COMMANDS_DFT); $(TWOVERS) $(GEN_TWIDSQ_C) $(GFLAGS) -n $* -dif -name q1bv_$* -include "q1b.h" -sign 1) | $(ADD_DATE) | $(INDENT) >$@ -+# -+# -+#endif # MAINTAINER_MODE -+# -diff --git a/fftw.def.cmake b/fftw.def.cmake -new file mode 100644 -index 0000000..66e0cbe ---- /dev/null -+++ b/fftw.def.cmake -@@ -0,0 +1,808 @@ -+EXPORTS -+@FFTW_SYMBOL_PREFIX@alignment_of -+@FFTW_SYMBOL_PREFIX@assertion_failed -+@FFTW_SYMBOL_PREFIX@bufdist -+@FFTW_SYMBOL_PREFIX@check_alignment_of_sse_pmpm -+@FFTW_SYMBOL_PREFIX@choose_radix -+@FFTW_SYMBOL_PREFIX@cleanup -+@FFTW_SYMBOL_PREFIX@cleanup_threads -+@FFTW_SYMBOL_PREFIX@codelet_e01_8 -+@FFTW_SYMBOL_PREFIX@codelet_e10_8 -+@FFTW_SYMBOL_PREFIX@codelet_hb2_16 -+@FFTW_SYMBOL_PREFIX@codelet_hb2_20 -+@FFTW_SYMBOL_PREFIX@codelet_hb2_25 -+@FFTW_SYMBOL_PREFIX@codelet_hb2_32 -+@FFTW_SYMBOL_PREFIX@codelet_hb2_4 -+@FFTW_SYMBOL_PREFIX@codelet_hb2_5 -+@FFTW_SYMBOL_PREFIX@codelet_hb2_8 -+@FFTW_SYMBOL_PREFIX@codelet_hb_10 -+@FFTW_SYMBOL_PREFIX@codelet_hb_12 -+@FFTW_SYMBOL_PREFIX@codelet_hb_15 -+@FFTW_SYMBOL_PREFIX@codelet_hb_16 -+@FFTW_SYMBOL_PREFIX@codelet_hb_2 -+@FFTW_SYMBOL_PREFIX@codelet_hb_20 -+@FFTW_SYMBOL_PREFIX@codelet_hb_25 -+@FFTW_SYMBOL_PREFIX@codelet_hb_3 -+@FFTW_SYMBOL_PREFIX@codelet_hb_32 -+@FFTW_SYMBOL_PREFIX@codelet_hb_4 -+@FFTW_SYMBOL_PREFIX@codelet_hb_5 -+@FFTW_SYMBOL_PREFIX@codelet_hb_6 -+@FFTW_SYMBOL_PREFIX@codelet_hb_64 -+@FFTW_SYMBOL_PREFIX@codelet_hb_7 -+@FFTW_SYMBOL_PREFIX@codelet_hb_8 -+@FFTW_SYMBOL_PREFIX@codelet_hb_9 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cb2_16 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cb2_20 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cb2_32 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cb2_4 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cb2_8 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cb_10 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cb_12 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cb_16 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cb_2 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cb_20 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cb_32 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cb_4 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cb_6 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cb_8 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cbdft2_16 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cbdft2_20 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cbdft2_32 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cbdft2_4 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cbdft2_8 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cbdft_10 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cbdft_12 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cbdft_16 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cbdft_2 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cbdft_20 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cbdft_32 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cbdft_4 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cbdft_6 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cbdft_8 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cbdftv_10 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cbdftv_12 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cbdftv_16 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cbdftv_2 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cbdftv_20 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cbdftv_32 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cbdftv_4 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cbdftv_6 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cbdftv_8 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cf2_16 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cf2_20 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cf2_32 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cf2_4 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cf2_8 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cf_10 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cf_12 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cf_16 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cf_2 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cf_20 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cf_32 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cf_4 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cf_6 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cf_8 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cfdft2_16 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cfdft2_20 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cfdft2_32 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cfdft2_4 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cfdft2_8 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cfdft_10 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cfdft_12 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cfdft_16 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cfdft_2 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cfdft_20 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cfdft_32 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cfdft_4 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cfdft_6 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cfdft_8 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cfdftv_10 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cfdftv_12 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cfdftv_16 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cfdftv_2 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cfdftv_20 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cfdftv_32 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cfdftv_4 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cfdftv_6 -+@FFTW_SYMBOL_PREFIX@codelet_hc2cfdftv_8 -+@FFTW_SYMBOL_PREFIX@codelet_hf2_16 -+@FFTW_SYMBOL_PREFIX@codelet_hf2_20 -+@FFTW_SYMBOL_PREFIX@codelet_hf2_25 -+@FFTW_SYMBOL_PREFIX@codelet_hf2_32 -+@FFTW_SYMBOL_PREFIX@codelet_hf2_4 -+@FFTW_SYMBOL_PREFIX@codelet_hf2_5 -+@FFTW_SYMBOL_PREFIX@codelet_hf2_8 -+@FFTW_SYMBOL_PREFIX@codelet_hf_10 -+@FFTW_SYMBOL_PREFIX@codelet_hf_12 -+@FFTW_SYMBOL_PREFIX@codelet_hf_15 -+@FFTW_SYMBOL_PREFIX@codelet_hf_16 -+@FFTW_SYMBOL_PREFIX@codelet_hf_2 -+@FFTW_SYMBOL_PREFIX@codelet_hf_20 -+@FFTW_SYMBOL_PREFIX@codelet_hf_25 -+@FFTW_SYMBOL_PREFIX@codelet_hf_3 -+@FFTW_SYMBOL_PREFIX@codelet_hf_32 -+@FFTW_SYMBOL_PREFIX@codelet_hf_4 -+@FFTW_SYMBOL_PREFIX@codelet_hf_5 -+@FFTW_SYMBOL_PREFIX@codelet_hf_6 -+@FFTW_SYMBOL_PREFIX@codelet_hf_64 -+@FFTW_SYMBOL_PREFIX@codelet_hf_7 -+@FFTW_SYMBOL_PREFIX@codelet_hf_8 -+@FFTW_SYMBOL_PREFIX@codelet_hf_9 -+@FFTW_SYMBOL_PREFIX@codelet_n1_10 -+@FFTW_SYMBOL_PREFIX@codelet_n1_11 -+@FFTW_SYMBOL_PREFIX@codelet_n1_12 -+@FFTW_SYMBOL_PREFIX@codelet_n1_13 -+@FFTW_SYMBOL_PREFIX@codelet_n1_14 -+@FFTW_SYMBOL_PREFIX@codelet_n1_15 -+@FFTW_SYMBOL_PREFIX@codelet_n1_16 -+@FFTW_SYMBOL_PREFIX@codelet_n1_2 -+@FFTW_SYMBOL_PREFIX@codelet_n1_20 -+@FFTW_SYMBOL_PREFIX@codelet_n1_25 -+@FFTW_SYMBOL_PREFIX@codelet_n1_3 -+@FFTW_SYMBOL_PREFIX@codelet_n1_32 -+@FFTW_SYMBOL_PREFIX@codelet_n1_4 -+@FFTW_SYMBOL_PREFIX@codelet_n1_5 -+@FFTW_SYMBOL_PREFIX@codelet_n1_6 -+@FFTW_SYMBOL_PREFIX@codelet_n1_64 -+@FFTW_SYMBOL_PREFIX@codelet_n1_7 -+@FFTW_SYMBOL_PREFIX@codelet_n1_8 -+@FFTW_SYMBOL_PREFIX@codelet_n1_9 -+@FFTW_SYMBOL_PREFIX@codelet_n1bv_10 -+@FFTW_SYMBOL_PREFIX@codelet_n1bv_11 -+@FFTW_SYMBOL_PREFIX@codelet_n1bv_12 -+@FFTW_SYMBOL_PREFIX@codelet_n1bv_128 -+@FFTW_SYMBOL_PREFIX@codelet_n1bv_13 -+@FFTW_SYMBOL_PREFIX@codelet_n1bv_14 -+@FFTW_SYMBOL_PREFIX@codelet_n1bv_15 -+@FFTW_SYMBOL_PREFIX@codelet_n1bv_16 -+@FFTW_SYMBOL_PREFIX@codelet_n1bv_2 -+@FFTW_SYMBOL_PREFIX@codelet_n1bv_20 -+@FFTW_SYMBOL_PREFIX@codelet_n1bv_25 -+@FFTW_SYMBOL_PREFIX@codelet_n1bv_3 -+@FFTW_SYMBOL_PREFIX@codelet_n1bv_32 -+@FFTW_SYMBOL_PREFIX@codelet_n1bv_4 -+@FFTW_SYMBOL_PREFIX@codelet_n1bv_5 -+@FFTW_SYMBOL_PREFIX@codelet_n1bv_6 -+@FFTW_SYMBOL_PREFIX@codelet_n1bv_64 -+@FFTW_SYMBOL_PREFIX@codelet_n1bv_7 -+@FFTW_SYMBOL_PREFIX@codelet_n1bv_8 -+@FFTW_SYMBOL_PREFIX@codelet_n1bv_9 -+@FFTW_SYMBOL_PREFIX@codelet_n1fv_10 -+@FFTW_SYMBOL_PREFIX@codelet_n1fv_11 -+@FFTW_SYMBOL_PREFIX@codelet_n1fv_12 -+@FFTW_SYMBOL_PREFIX@codelet_n1fv_128 -+@FFTW_SYMBOL_PREFIX@codelet_n1fv_13 -+@FFTW_SYMBOL_PREFIX@codelet_n1fv_14 -+@FFTW_SYMBOL_PREFIX@codelet_n1fv_15 -+@FFTW_SYMBOL_PREFIX@codelet_n1fv_16 -+@FFTW_SYMBOL_PREFIX@codelet_n1fv_2 -+@FFTW_SYMBOL_PREFIX@codelet_n1fv_20 -+@FFTW_SYMBOL_PREFIX@codelet_n1fv_25 -+@FFTW_SYMBOL_PREFIX@codelet_n1fv_3 -+@FFTW_SYMBOL_PREFIX@codelet_n1fv_32 -+@FFTW_SYMBOL_PREFIX@codelet_n1fv_4 -+@FFTW_SYMBOL_PREFIX@codelet_n1fv_5 -+@FFTW_SYMBOL_PREFIX@codelet_n1fv_6 -+@FFTW_SYMBOL_PREFIX@codelet_n1fv_64 -+@FFTW_SYMBOL_PREFIX@codelet_n1fv_7 -+@FFTW_SYMBOL_PREFIX@codelet_n1fv_8 -+@FFTW_SYMBOL_PREFIX@codelet_n1fv_9 -+@FFTW_SYMBOL_PREFIX@codelet_n2bv_10 -+@FFTW_SYMBOL_PREFIX@codelet_n2bv_12 -+@FFTW_SYMBOL_PREFIX@codelet_n2bv_14 -+@FFTW_SYMBOL_PREFIX@codelet_n2bv_16 -+@FFTW_SYMBOL_PREFIX@codelet_n2bv_2 -+@FFTW_SYMBOL_PREFIX@codelet_n2bv_20 -+@FFTW_SYMBOL_PREFIX@codelet_n2bv_32 -+@FFTW_SYMBOL_PREFIX@codelet_n2bv_4 -+@FFTW_SYMBOL_PREFIX@codelet_n2bv_6 -+@FFTW_SYMBOL_PREFIX@codelet_n2bv_64 -+@FFTW_SYMBOL_PREFIX@codelet_n2bv_8 -+@FFTW_SYMBOL_PREFIX@codelet_n2fv_10 -+@FFTW_SYMBOL_PREFIX@codelet_n2fv_12 -+@FFTW_SYMBOL_PREFIX@codelet_n2fv_14 -+@FFTW_SYMBOL_PREFIX@codelet_n2fv_16 -+@FFTW_SYMBOL_PREFIX@codelet_n2fv_2 -+@FFTW_SYMBOL_PREFIX@codelet_n2fv_20 -+@FFTW_SYMBOL_PREFIX@codelet_n2fv_32 -+@FFTW_SYMBOL_PREFIX@codelet_n2fv_4 -+@FFTW_SYMBOL_PREFIX@codelet_n2fv_6 -+@FFTW_SYMBOL_PREFIX@codelet_n2fv_64 -+@FFTW_SYMBOL_PREFIX@codelet_n2fv_8 -+@FFTW_SYMBOL_PREFIX@codelet_n2sv_16 -+@FFTW_SYMBOL_PREFIX@codelet_n2sv_32 -+@FFTW_SYMBOL_PREFIX@codelet_n2sv_4 -+@FFTW_SYMBOL_PREFIX@codelet_n2sv_64 -+@FFTW_SYMBOL_PREFIX@codelet_n2sv_8 -+@FFTW_SYMBOL_PREFIX@codelet_q1_2 -+@FFTW_SYMBOL_PREFIX@codelet_q1_3 -+@FFTW_SYMBOL_PREFIX@codelet_q1_4 -+@FFTW_SYMBOL_PREFIX@codelet_q1_5 -+@FFTW_SYMBOL_PREFIX@codelet_q1_6 -+@FFTW_SYMBOL_PREFIX@codelet_q1_8 -+@FFTW_SYMBOL_PREFIX@codelet_q1bv_2 -+@FFTW_SYMBOL_PREFIX@codelet_q1bv_4 -+@FFTW_SYMBOL_PREFIX@codelet_q1bv_5 -+@FFTW_SYMBOL_PREFIX@codelet_q1bv_8 -+@FFTW_SYMBOL_PREFIX@codelet_q1fv_2 -+@FFTW_SYMBOL_PREFIX@codelet_q1fv_4 -+@FFTW_SYMBOL_PREFIX@codelet_q1fv_5 -+@FFTW_SYMBOL_PREFIX@codelet_q1fv_8 -+@FFTW_SYMBOL_PREFIX@codelet_r2cbIII_10 -+@FFTW_SYMBOL_PREFIX@codelet_r2cbIII_12 -+@FFTW_SYMBOL_PREFIX@codelet_r2cbIII_15 -+@FFTW_SYMBOL_PREFIX@codelet_r2cbIII_16 -+@FFTW_SYMBOL_PREFIX@codelet_r2cbIII_2 -+@FFTW_SYMBOL_PREFIX@codelet_r2cbIII_20 -+@FFTW_SYMBOL_PREFIX@codelet_r2cbIII_25 -+@FFTW_SYMBOL_PREFIX@codelet_r2cbIII_3 -+@FFTW_SYMBOL_PREFIX@codelet_r2cbIII_32 -+@FFTW_SYMBOL_PREFIX@codelet_r2cbIII_4 -+@FFTW_SYMBOL_PREFIX@codelet_r2cbIII_5 -+@FFTW_SYMBOL_PREFIX@codelet_r2cbIII_6 -+@FFTW_SYMBOL_PREFIX@codelet_r2cbIII_64 -+@FFTW_SYMBOL_PREFIX@codelet_r2cbIII_7 -+@FFTW_SYMBOL_PREFIX@codelet_r2cbIII_8 -+@FFTW_SYMBOL_PREFIX@codelet_r2cbIII_9 -+@FFTW_SYMBOL_PREFIX@codelet_r2cb_10 -+@FFTW_SYMBOL_PREFIX@codelet_r2cb_11 -+@FFTW_SYMBOL_PREFIX@codelet_r2cb_12 -+@FFTW_SYMBOL_PREFIX@codelet_r2cb_128 -+@FFTW_SYMBOL_PREFIX@codelet_r2cb_13 -+@FFTW_SYMBOL_PREFIX@codelet_r2cb_14 -+@FFTW_SYMBOL_PREFIX@codelet_r2cb_15 -+@FFTW_SYMBOL_PREFIX@codelet_r2cb_16 -+@FFTW_SYMBOL_PREFIX@codelet_r2cb_2 -+@FFTW_SYMBOL_PREFIX@codelet_r2cb_20 -+@FFTW_SYMBOL_PREFIX@codelet_r2cb_25 -+@FFTW_SYMBOL_PREFIX@codelet_r2cb_3 -+@FFTW_SYMBOL_PREFIX@codelet_r2cb_32 -+@FFTW_SYMBOL_PREFIX@codelet_r2cb_4 -+@FFTW_SYMBOL_PREFIX@codelet_r2cb_5 -+@FFTW_SYMBOL_PREFIX@codelet_r2cb_6 -+@FFTW_SYMBOL_PREFIX@codelet_r2cb_64 -+@FFTW_SYMBOL_PREFIX@codelet_r2cb_7 -+@FFTW_SYMBOL_PREFIX@codelet_r2cb_8 -+@FFTW_SYMBOL_PREFIX@codelet_r2cb_9 -+@FFTW_SYMBOL_PREFIX@codelet_r2cfII_10 -+@FFTW_SYMBOL_PREFIX@codelet_r2cfII_12 -+@FFTW_SYMBOL_PREFIX@codelet_r2cfII_15 -+@FFTW_SYMBOL_PREFIX@codelet_r2cfII_16 -+@FFTW_SYMBOL_PREFIX@codelet_r2cfII_2 -+@FFTW_SYMBOL_PREFIX@codelet_r2cfII_20 -+@FFTW_SYMBOL_PREFIX@codelet_r2cfII_25 -+@FFTW_SYMBOL_PREFIX@codelet_r2cfII_3 -+@FFTW_SYMBOL_PREFIX@codelet_r2cfII_32 -+@FFTW_SYMBOL_PREFIX@codelet_r2cfII_4 -+@FFTW_SYMBOL_PREFIX@codelet_r2cfII_5 -+@FFTW_SYMBOL_PREFIX@codelet_r2cfII_6 -+@FFTW_SYMBOL_PREFIX@codelet_r2cfII_64 -+@FFTW_SYMBOL_PREFIX@codelet_r2cfII_7 -+@FFTW_SYMBOL_PREFIX@codelet_r2cfII_8 -+@FFTW_SYMBOL_PREFIX@codelet_r2cfII_9 -+@FFTW_SYMBOL_PREFIX@codelet_r2cf_10 -+@FFTW_SYMBOL_PREFIX@codelet_r2cf_11 -+@FFTW_SYMBOL_PREFIX@codelet_r2cf_12 -+@FFTW_SYMBOL_PREFIX@codelet_r2cf_128 -+@FFTW_SYMBOL_PREFIX@codelet_r2cf_13 -+@FFTW_SYMBOL_PREFIX@codelet_r2cf_14 -+@FFTW_SYMBOL_PREFIX@codelet_r2cf_15 -+@FFTW_SYMBOL_PREFIX@codelet_r2cf_16 -+@FFTW_SYMBOL_PREFIX@codelet_r2cf_2 -+@FFTW_SYMBOL_PREFIX@codelet_r2cf_20 -+@FFTW_SYMBOL_PREFIX@codelet_r2cf_25 -+@FFTW_SYMBOL_PREFIX@codelet_r2cf_3 -+@FFTW_SYMBOL_PREFIX@codelet_r2cf_32 -+@FFTW_SYMBOL_PREFIX@codelet_r2cf_4 -+@FFTW_SYMBOL_PREFIX@codelet_r2cf_5 -+@FFTW_SYMBOL_PREFIX@codelet_r2cf_6 -+@FFTW_SYMBOL_PREFIX@codelet_r2cf_64 -+@FFTW_SYMBOL_PREFIX@codelet_r2cf_7 -+@FFTW_SYMBOL_PREFIX@codelet_r2cf_8 -+@FFTW_SYMBOL_PREFIX@codelet_r2cf_9 -+@FFTW_SYMBOL_PREFIX@codelet_t1_10 -+@FFTW_SYMBOL_PREFIX@codelet_t1_12 -+@FFTW_SYMBOL_PREFIX@codelet_t1_15 -+@FFTW_SYMBOL_PREFIX@codelet_t1_16 -+@FFTW_SYMBOL_PREFIX@codelet_t1_2 -+@FFTW_SYMBOL_PREFIX@codelet_t1_20 -+@FFTW_SYMBOL_PREFIX@codelet_t1_25 -+@FFTW_SYMBOL_PREFIX@codelet_t1_3 -+@FFTW_SYMBOL_PREFIX@codelet_t1_32 -+@FFTW_SYMBOL_PREFIX@codelet_t1_4 -+@FFTW_SYMBOL_PREFIX@codelet_t1_5 -+@FFTW_SYMBOL_PREFIX@codelet_t1_6 -+@FFTW_SYMBOL_PREFIX@codelet_t1_64 -+@FFTW_SYMBOL_PREFIX@codelet_t1_7 -+@FFTW_SYMBOL_PREFIX@codelet_t1_8 -+@FFTW_SYMBOL_PREFIX@codelet_t1_9 -+@FFTW_SYMBOL_PREFIX@codelet_t1buv_10 -+@FFTW_SYMBOL_PREFIX@codelet_t1buv_2 -+@FFTW_SYMBOL_PREFIX@codelet_t1buv_3 -+@FFTW_SYMBOL_PREFIX@codelet_t1buv_4 -+@FFTW_SYMBOL_PREFIX@codelet_t1buv_5 -+@FFTW_SYMBOL_PREFIX@codelet_t1buv_6 -+@FFTW_SYMBOL_PREFIX@codelet_t1buv_7 -+@FFTW_SYMBOL_PREFIX@codelet_t1buv_8 -+@FFTW_SYMBOL_PREFIX@codelet_t1buv_9 -+@FFTW_SYMBOL_PREFIX@codelet_t1bv_10 -+@FFTW_SYMBOL_PREFIX@codelet_t1bv_12 -+@FFTW_SYMBOL_PREFIX@codelet_t1bv_15 -+@FFTW_SYMBOL_PREFIX@codelet_t1bv_16 -+@FFTW_SYMBOL_PREFIX@codelet_t1bv_2 -+@FFTW_SYMBOL_PREFIX@codelet_t1bv_20 -+@FFTW_SYMBOL_PREFIX@codelet_t1bv_25 -+@FFTW_SYMBOL_PREFIX@codelet_t1bv_3 -+@FFTW_SYMBOL_PREFIX@codelet_t1bv_32 -+@FFTW_SYMBOL_PREFIX@codelet_t1bv_4 -+@FFTW_SYMBOL_PREFIX@codelet_t1bv_5 -+@FFTW_SYMBOL_PREFIX@codelet_t1bv_6 -+@FFTW_SYMBOL_PREFIX@codelet_t1bv_64 -+@FFTW_SYMBOL_PREFIX@codelet_t1bv_7 -+@FFTW_SYMBOL_PREFIX@codelet_t1bv_8 -+@FFTW_SYMBOL_PREFIX@codelet_t1bv_9 -+@FFTW_SYMBOL_PREFIX@codelet_t1fuv_10 -+@FFTW_SYMBOL_PREFIX@codelet_t1fuv_2 -+@FFTW_SYMBOL_PREFIX@codelet_t1fuv_3 -+@FFTW_SYMBOL_PREFIX@codelet_t1fuv_4 -+@FFTW_SYMBOL_PREFIX@codelet_t1fuv_5 -+@FFTW_SYMBOL_PREFIX@codelet_t1fuv_6 -+@FFTW_SYMBOL_PREFIX@codelet_t1fuv_7 -+@FFTW_SYMBOL_PREFIX@codelet_t1fuv_8 -+@FFTW_SYMBOL_PREFIX@codelet_t1fuv_9 -+@FFTW_SYMBOL_PREFIX@codelet_t1fv_10 -+@FFTW_SYMBOL_PREFIX@codelet_t1fv_12 -+@FFTW_SYMBOL_PREFIX@codelet_t1fv_15 -+@FFTW_SYMBOL_PREFIX@codelet_t1fv_16 -+@FFTW_SYMBOL_PREFIX@codelet_t1fv_2 -+@FFTW_SYMBOL_PREFIX@codelet_t1fv_20 -+@FFTW_SYMBOL_PREFIX@codelet_t1fv_25 -+@FFTW_SYMBOL_PREFIX@codelet_t1fv_3 -+@FFTW_SYMBOL_PREFIX@codelet_t1fv_32 -+@FFTW_SYMBOL_PREFIX@codelet_t1fv_4 -+@FFTW_SYMBOL_PREFIX@codelet_t1fv_5 -+@FFTW_SYMBOL_PREFIX@codelet_t1fv_6 -+@FFTW_SYMBOL_PREFIX@codelet_t1fv_64 -+@FFTW_SYMBOL_PREFIX@codelet_t1fv_7 -+@FFTW_SYMBOL_PREFIX@codelet_t1fv_8 -+@FFTW_SYMBOL_PREFIX@codelet_t1fv_9 -+@FFTW_SYMBOL_PREFIX@codelet_t1sv_16 -+@FFTW_SYMBOL_PREFIX@codelet_t1sv_2 -+@FFTW_SYMBOL_PREFIX@codelet_t1sv_32 -+@FFTW_SYMBOL_PREFIX@codelet_t1sv_4 -+@FFTW_SYMBOL_PREFIX@codelet_t1sv_8 -+@FFTW_SYMBOL_PREFIX@codelet_t2_10 -+@FFTW_SYMBOL_PREFIX@codelet_t2_16 -+@FFTW_SYMBOL_PREFIX@codelet_t2_20 -+@FFTW_SYMBOL_PREFIX@codelet_t2_25 -+@FFTW_SYMBOL_PREFIX@codelet_t2_32 -+@FFTW_SYMBOL_PREFIX@codelet_t2_4 -+@FFTW_SYMBOL_PREFIX@codelet_t2_5 -+@FFTW_SYMBOL_PREFIX@codelet_t2_64 -+@FFTW_SYMBOL_PREFIX@codelet_t2_8 -+@FFTW_SYMBOL_PREFIX@codelet_t2bv_10 -+@FFTW_SYMBOL_PREFIX@codelet_t2bv_16 -+@FFTW_SYMBOL_PREFIX@codelet_t2bv_2 -+@FFTW_SYMBOL_PREFIX@codelet_t2bv_20 -+@FFTW_SYMBOL_PREFIX@codelet_t2bv_25 -+@FFTW_SYMBOL_PREFIX@codelet_t2bv_32 -+@FFTW_SYMBOL_PREFIX@codelet_t2bv_4 -+@FFTW_SYMBOL_PREFIX@codelet_t2bv_5 -+@FFTW_SYMBOL_PREFIX@codelet_t2bv_64 -+@FFTW_SYMBOL_PREFIX@codelet_t2bv_8 -+@FFTW_SYMBOL_PREFIX@codelet_t2fv_10 -+@FFTW_SYMBOL_PREFIX@codelet_t2fv_16 -+@FFTW_SYMBOL_PREFIX@codelet_t2fv_2 -+@FFTW_SYMBOL_PREFIX@codelet_t2fv_20 -+@FFTW_SYMBOL_PREFIX@codelet_t2fv_25 -+@FFTW_SYMBOL_PREFIX@codelet_t2fv_32 -+@FFTW_SYMBOL_PREFIX@codelet_t2fv_4 -+@FFTW_SYMBOL_PREFIX@codelet_t2fv_5 -+@FFTW_SYMBOL_PREFIX@codelet_t2fv_64 -+@FFTW_SYMBOL_PREFIX@codelet_t2fv_8 -+@FFTW_SYMBOL_PREFIX@codelet_t2sv_16 -+@FFTW_SYMBOL_PREFIX@codelet_t2sv_32 -+@FFTW_SYMBOL_PREFIX@codelet_t2sv_4 -+@FFTW_SYMBOL_PREFIX@codelet_t2sv_8 -+@FFTW_SYMBOL_PREFIX@codelet_t3bv_10 -+@FFTW_SYMBOL_PREFIX@codelet_t3bv_16 -+@FFTW_SYMBOL_PREFIX@codelet_t3bv_20 -+@FFTW_SYMBOL_PREFIX@codelet_t3bv_25 -+@FFTW_SYMBOL_PREFIX@codelet_t3bv_32 -+@FFTW_SYMBOL_PREFIX@codelet_t3bv_4 -+@FFTW_SYMBOL_PREFIX@codelet_t3bv_5 -+@FFTW_SYMBOL_PREFIX@codelet_t3bv_8 -+@FFTW_SYMBOL_PREFIX@codelet_t3fv_10 -+@FFTW_SYMBOL_PREFIX@codelet_t3fv_16 -+@FFTW_SYMBOL_PREFIX@codelet_t3fv_20 -+@FFTW_SYMBOL_PREFIX@codelet_t3fv_25 -+@FFTW_SYMBOL_PREFIX@codelet_t3fv_32 -+@FFTW_SYMBOL_PREFIX@codelet_t3fv_4 -+@FFTW_SYMBOL_PREFIX@codelet_t3fv_5 -+@FFTW_SYMBOL_PREFIX@codelet_t3fv_8 -+@FFTW_SYMBOL_PREFIX@compute_tilesz -+@FFTW_SYMBOL_PREFIX@configure_planner -+@FFTW_SYMBOL_PREFIX@cpy1d -+@FFTW_SYMBOL_PREFIX@cpy2d -+@FFTW_SYMBOL_PREFIX@cpy2d_ci -+@FFTW_SYMBOL_PREFIX@cpy2d_co -+@FFTW_SYMBOL_PREFIX@cpy2d_pair -+@FFTW_SYMBOL_PREFIX@cpy2d_pair_ci -+@FFTW_SYMBOL_PREFIX@cpy2d_pair_co -+@FFTW_SYMBOL_PREFIX@cpy2d_tiled -+@FFTW_SYMBOL_PREFIX@cpy2d_tiledbuf -+@FFTW_SYMBOL_PREFIX@ct_applicable -+@FFTW_SYMBOL_PREFIX@ct_generic_register -+@FFTW_SYMBOL_PREFIX@ct_genericbuf_register -+@FFTW_SYMBOL_PREFIX@ct_uglyp -+@FFTW_SYMBOL_PREFIX@destroy_plan -+@FFTW_SYMBOL_PREFIX@dft_bluestein_register -+@FFTW_SYMBOL_PREFIX@dft_buffered_register -+@FFTW_SYMBOL_PREFIX@dft_conf_standard -+@FFTW_SYMBOL_PREFIX@dft_generic_register -+@FFTW_SYMBOL_PREFIX@dft_indirect_register -+@FFTW_SYMBOL_PREFIX@dft_indirect_transpose_register -+@FFTW_SYMBOL_PREFIX@dft_nop_register -+@FFTW_SYMBOL_PREFIX@dft_r2hc_register -+@FFTW_SYMBOL_PREFIX@dft_rader_register -+@FFTW_SYMBOL_PREFIX@dft_rank_geq2_register -+@FFTW_SYMBOL_PREFIX@dft_solve -+@FFTW_SYMBOL_PREFIX@dft_thr_vrank_geq1_register -+@FFTW_SYMBOL_PREFIX@dft_vrank_geq1_register -+@FFTW_SYMBOL_PREFIX@dft_zerotens -+@FFTW_SYMBOL_PREFIX@dht_r2hc_register -+@FFTW_SYMBOL_PREFIX@dht_rader_register -+@FFTW_SYMBOL_PREFIX@dimcmp -+@FFTW_SYMBOL_PREFIX@elapsed_since -+@FFTW_SYMBOL_PREFIX@estimate_cost -+@FFTW_SYMBOL_PREFIX@execute -+@FFTW_SYMBOL_PREFIX@execute_dft -+@FFTW_SYMBOL_PREFIX@execute_dft_c2r -+@FFTW_SYMBOL_PREFIX@execute_dft_r2c -+@FFTW_SYMBOL_PREFIX@execute_r2r -+@FFTW_SYMBOL_PREFIX@execute_split_dft -+@FFTW_SYMBOL_PREFIX@execute_split_dft_c2r -+@FFTW_SYMBOL_PREFIX@execute_split_dft_r2c -+@FFTW_SYMBOL_PREFIX@export_wisdom -+@FFTW_SYMBOL_PREFIX@export_wisdom_to_file -+@FFTW_SYMBOL_PREFIX@export_wisdom_to_string -+@FFTW_SYMBOL_PREFIX@extract_reim -+@FFTW_SYMBOL_PREFIX@factors_into -+@FFTW_SYMBOL_PREFIX@find_generator -+@FFTW_SYMBOL_PREFIX@first_divisor -+@FFTW_SYMBOL_PREFIX@flops -+@FFTW_SYMBOL_PREFIX@forget_wisdom -+@FFTW_SYMBOL_PREFIX@fprint_plan -+@FFTW_SYMBOL_PREFIX@free -+@FFTW_SYMBOL_PREFIX@get_crude_time -+@FFTW_SYMBOL_PREFIX@guru64_kosherp -+@FFTW_SYMBOL_PREFIX@guru_kosherp -+@FFTW_SYMBOL_PREFIX@hash -+@FFTW_SYMBOL_PREFIX@have_sse -+@FFTW_SYMBOL_PREFIX@hc2c_applicable -+@FFTW_SYMBOL_PREFIX@hc2hc_applicable -+@FFTW_SYMBOL_PREFIX@hc2hc_generic_register -+@FFTW_SYMBOL_PREFIX@iabs -+@FFTW_SYMBOL_PREFIX@iestimate_cost -+@FFTW_SYMBOL_PREFIX@ifree -+@FFTW_SYMBOL_PREFIX@ifree0 -+@FFTW_SYMBOL_PREFIX@imax -+@FFTW_SYMBOL_PREFIX@imin -+@FFTW_SYMBOL_PREFIX@import_system_wisdom -+@FFTW_SYMBOL_PREFIX@import_wisdom -+@FFTW_SYMBOL_PREFIX@import_wisdom_from_file -+@FFTW_SYMBOL_PREFIX@import_wisdom_from_string -+@FFTW_SYMBOL_PREFIX@init_threads -+@FFTW_SYMBOL_PREFIX@is_prime -+@FFTW_SYMBOL_PREFIX@isqrt -+@FFTW_SYMBOL_PREFIX@ithreads_init -+@FFTW_SYMBOL_PREFIX@join_taint -+@FFTW_SYMBOL_PREFIX@kdft_dif_register -+@FFTW_SYMBOL_PREFIX@kdft_difsq_register -+@FFTW_SYMBOL_PREFIX@kdft_dit_register -+@FFTW_SYMBOL_PREFIX@kdft_register -+@FFTW_SYMBOL_PREFIX@kernel_free -+@FFTW_SYMBOL_PREFIX@kernel_malloc -+@FFTW_SYMBOL_PREFIX@khc2c_register -+@FFTW_SYMBOL_PREFIX@khc2hc_register -+@FFTW_SYMBOL_PREFIX@kr2c_register -+@FFTW_SYMBOL_PREFIX@kr2r_register -+@FFTW_SYMBOL_PREFIX@malloc -+@FFTW_SYMBOL_PREFIX@malloc_plain -+@FFTW_SYMBOL_PREFIX@many_kosherp -+@FFTW_SYMBOL_PREFIX@map_r2r_kind -+@FFTW_SYMBOL_PREFIX@mapflags -+@FFTW_SYMBOL_PREFIX@md5INT -+@FFTW_SYMBOL_PREFIX@md5begin -+@FFTW_SYMBOL_PREFIX@md5end -+@FFTW_SYMBOL_PREFIX@md5int -+@FFTW_SYMBOL_PREFIX@md5putb -+@FFTW_SYMBOL_PREFIX@md5putc -+@FFTW_SYMBOL_PREFIX@md5puts -+@FFTW_SYMBOL_PREFIX@md5unsigned -+@FFTW_SYMBOL_PREFIX@measure_execution_time -+@FFTW_SYMBOL_PREFIX@mkapiplan -+@FFTW_SYMBOL_PREFIX@mkplan -+@FFTW_SYMBOL_PREFIX@mkplan_d -+@FFTW_SYMBOL_PREFIX@mkplan_dft -+@FFTW_SYMBOL_PREFIX@mkplan_dftw -+@FFTW_SYMBOL_PREFIX@mkplan_f_d -+@FFTW_SYMBOL_PREFIX@mkplan_hc2c -+@FFTW_SYMBOL_PREFIX@mkplan_hc2hc -+@FFTW_SYMBOL_PREFIX@mkplan_rdft -+@FFTW_SYMBOL_PREFIX@mkplan_rdft2 -+@FFTW_SYMBOL_PREFIX@mkplanner -+@FFTW_SYMBOL_PREFIX@mkprinter -+@FFTW_SYMBOL_PREFIX@mkprinter_file -+@FFTW_SYMBOL_PREFIX@mkproblem -+@FFTW_SYMBOL_PREFIX@mkproblem_dft -+@FFTW_SYMBOL_PREFIX@mkproblem_dft_d -+@FFTW_SYMBOL_PREFIX@mkproblem_rdft -+@FFTW_SYMBOL_PREFIX@mkproblem_rdft2 -+@FFTW_SYMBOL_PREFIX@mkproblem_rdft2_d -+@FFTW_SYMBOL_PREFIX@mkproblem_rdft2_d_3pointers -+@FFTW_SYMBOL_PREFIX@mkproblem_rdft_0_d -+@FFTW_SYMBOL_PREFIX@mkproblem_rdft_1 -+@FFTW_SYMBOL_PREFIX@mkproblem_rdft_1_d -+@FFTW_SYMBOL_PREFIX@mkproblem_rdft_d -+@FFTW_SYMBOL_PREFIX@mkproblem_unsolvable -+@FFTW_SYMBOL_PREFIX@mkscanner -+@FFTW_SYMBOL_PREFIX@mksolver -+@FFTW_SYMBOL_PREFIX@mksolver_ct -+@FFTW_SYMBOL_PREFIX@mksolver_ct_threads -+@FFTW_SYMBOL_PREFIX@mksolver_dft_direct -+@FFTW_SYMBOL_PREFIX@mksolver_dft_directbuf -+@FFTW_SYMBOL_PREFIX@mksolver_hc2c -+@FFTW_SYMBOL_PREFIX@mksolver_hc2hc -+@FFTW_SYMBOL_PREFIX@mksolver_hc2hc_threads -+@FFTW_SYMBOL_PREFIX@mksolver_rdft2_direct -+@FFTW_SYMBOL_PREFIX@mksolver_rdft_r2c_direct -+@FFTW_SYMBOL_PREFIX@mksolver_rdft_r2c_directbuf -+@FFTW_SYMBOL_PREFIX@mksolver_rdft_r2r_direct -+@FFTW_SYMBOL_PREFIX@mkstride -+@FFTW_SYMBOL_PREFIX@mktensor -+@FFTW_SYMBOL_PREFIX@mktensor_0d -+@FFTW_SYMBOL_PREFIX@mktensor_1d -+@FFTW_SYMBOL_PREFIX@mktensor_2d -+@FFTW_SYMBOL_PREFIX@mktensor_3d -+@FFTW_SYMBOL_PREFIX@mktensor_4d -+@FFTW_SYMBOL_PREFIX@mktensor_5d -+@FFTW_SYMBOL_PREFIX@mktensor_iodims -+@FFTW_SYMBOL_PREFIX@mktensor_iodims64 -+@FFTW_SYMBOL_PREFIX@mktensor_rowmajor -+@FFTW_SYMBOL_PREFIX@mktriggen -+@FFTW_SYMBOL_PREFIX@modulo -+@FFTW_SYMBOL_PREFIX@nbuf -+@FFTW_SYMBOL_PREFIX@nbuf_redundant -+@FFTW_SYMBOL_PREFIX@next_prime -+@FFTW_SYMBOL_PREFIX@null_awake -+@FFTW_SYMBOL_PREFIX@ops_add -+@FFTW_SYMBOL_PREFIX@ops_add2 -+@FFTW_SYMBOL_PREFIX@ops_cpy -+@FFTW_SYMBOL_PREFIX@ops_madd -+@FFTW_SYMBOL_PREFIX@ops_madd2 -+@FFTW_SYMBOL_PREFIX@ops_other -+@FFTW_SYMBOL_PREFIX@ops_zero -+@FFTW_SYMBOL_PREFIX@pickdim -+@FFTW_SYMBOL_PREFIX@plan_awake -+@FFTW_SYMBOL_PREFIX@plan_destroy_internal -+@FFTW_SYMBOL_PREFIX@plan_dft -+@FFTW_SYMBOL_PREFIX@plan_dft_1d -+@FFTW_SYMBOL_PREFIX@plan_dft_2d -+@FFTW_SYMBOL_PREFIX@plan_dft_3d -+@FFTW_SYMBOL_PREFIX@plan_dft_c2r -+@FFTW_SYMBOL_PREFIX@plan_dft_c2r_1d -+@FFTW_SYMBOL_PREFIX@plan_dft_c2r_2d -+@FFTW_SYMBOL_PREFIX@plan_dft_c2r_3d -+@FFTW_SYMBOL_PREFIX@plan_dft_r2c -+@FFTW_SYMBOL_PREFIX@plan_dft_r2c_1d -+@FFTW_SYMBOL_PREFIX@plan_dft_r2c_2d -+@FFTW_SYMBOL_PREFIX@plan_dft_r2c_3d -+@FFTW_SYMBOL_PREFIX@plan_guru64_dft -+@FFTW_SYMBOL_PREFIX@plan_guru64_dft_c2r -+@FFTW_SYMBOL_PREFIX@plan_guru64_dft_r2c -+@FFTW_SYMBOL_PREFIX@plan_guru64_r2r -+@FFTW_SYMBOL_PREFIX@plan_guru64_split_dft -+@FFTW_SYMBOL_PREFIX@plan_guru64_split_dft_c2r -+@FFTW_SYMBOL_PREFIX@plan_guru64_split_dft_r2c -+@FFTW_SYMBOL_PREFIX@plan_guru_dft -+@FFTW_SYMBOL_PREFIX@plan_guru_dft_c2r -+@FFTW_SYMBOL_PREFIX@plan_guru_dft_r2c -+@FFTW_SYMBOL_PREFIX@plan_guru_r2r -+@FFTW_SYMBOL_PREFIX@plan_guru_split_dft -+@FFTW_SYMBOL_PREFIX@plan_guru_split_dft_c2r -+@FFTW_SYMBOL_PREFIX@plan_guru_split_dft_r2c -+@FFTW_SYMBOL_PREFIX@plan_many_dft -+@FFTW_SYMBOL_PREFIX@plan_many_dft_c2r -+@FFTW_SYMBOL_PREFIX@plan_many_dft_r2c -+@FFTW_SYMBOL_PREFIX@plan_many_r2r -+@FFTW_SYMBOL_PREFIX@plan_null_destroy -+@FFTW_SYMBOL_PREFIX@plan_r2r -+@FFTW_SYMBOL_PREFIX@plan_r2r_1d -+@FFTW_SYMBOL_PREFIX@plan_r2r_2d -+@FFTW_SYMBOL_PREFIX@plan_r2r_3d -+@FFTW_SYMBOL_PREFIX@plan_with_nthreads -+@FFTW_SYMBOL_PREFIX@planner_destroy -+@FFTW_SYMBOL_PREFIX@power_mod -+@FFTW_SYMBOL_PREFIX@print_plan -+@FFTW_SYMBOL_PREFIX@printer_destroy -+@FFTW_SYMBOL_PREFIX@problem_destroy -+@FFTW_SYMBOL_PREFIX@rader_tl_delete -+@FFTW_SYMBOL_PREFIX@rader_tl_find -+@FFTW_SYMBOL_PREFIX@rader_tl_insert -+@FFTW_SYMBOL_PREFIX@rdft2_buffered_register -+@FFTW_SYMBOL_PREFIX@rdft2_complex_n -+@FFTW_SYMBOL_PREFIX@rdft2_inplace_strides -+@FFTW_SYMBOL_PREFIX@rdft2_nop_register -+@FFTW_SYMBOL_PREFIX@rdft2_pad -+@FFTW_SYMBOL_PREFIX@rdft2_rank0_register -+@FFTW_SYMBOL_PREFIX@rdft2_rank_geq2_register -+@FFTW_SYMBOL_PREFIX@rdft2_rdft_register -+@FFTW_SYMBOL_PREFIX@rdft2_solve -+@FFTW_SYMBOL_PREFIX@rdft2_strides -+@FFTW_SYMBOL_PREFIX@rdft2_tensor_max_index -+@FFTW_SYMBOL_PREFIX@rdft2_thr_vrank_geq1_register -+@FFTW_SYMBOL_PREFIX@rdft2_vrank_geq1_register -+@FFTW_SYMBOL_PREFIX@rdft_buffered_register -+@FFTW_SYMBOL_PREFIX@rdft_conf_standard -+@FFTW_SYMBOL_PREFIX@rdft_dht_register -+@FFTW_SYMBOL_PREFIX@rdft_generic_register -+@FFTW_SYMBOL_PREFIX@rdft_indirect_register -+@FFTW_SYMBOL_PREFIX@rdft_kind_str -+@FFTW_SYMBOL_PREFIX@rdft_nop_register -+@FFTW_SYMBOL_PREFIX@rdft_rank0_register -+@FFTW_SYMBOL_PREFIX@rdft_rank_geq2_register -+@FFTW_SYMBOL_PREFIX@rdft_solve -+@FFTW_SYMBOL_PREFIX@rdft_thr_vrank_geq1_register -+@FFTW_SYMBOL_PREFIX@rdft_vrank3_transpose_register -+@FFTW_SYMBOL_PREFIX@rdft_vrank_geq1_register -+@FFTW_SYMBOL_PREFIX@rdft_zerotens -+@FFTW_SYMBOL_PREFIX@redft00e_r2hc_pad_register -+@FFTW_SYMBOL_PREFIX@regsolver_ct_directw -+@FFTW_SYMBOL_PREFIX@regsolver_ct_directwsq -+@FFTW_SYMBOL_PREFIX@regsolver_hc2c_direct -+@FFTW_SYMBOL_PREFIX@regsolver_hc2hc_direct -+@FFTW_SYMBOL_PREFIX@reodft00e_splitradix_register -+@FFTW_SYMBOL_PREFIX@reodft010e_r2hc_register -+@FFTW_SYMBOL_PREFIX@reodft11e_r2hc_odd_register -+@FFTW_SYMBOL_PREFIX@reodft11e_radix2_r2hc_register -+@FFTW_SYMBOL_PREFIX@reodft_conf_standard -+@FFTW_SYMBOL_PREFIX@rodft00e_r2hc_pad_register -+@FFTW_SYMBOL_PREFIX@safe_mulmod -+@FFTW_SYMBOL_PREFIX@scanner_destroy -+@FFTW_SYMBOL_PREFIX@set_timelimit -+@FFTW_SYMBOL_PREFIX@solver_destroy -+@FFTW_SYMBOL_PREFIX@solver_register -+@FFTW_SYMBOL_PREFIX@solver_use -+@FFTW_SYMBOL_PREFIX@solvtab_exec -+@FFTW_SYMBOL_PREFIX@spawn_loop -+@FFTW_SYMBOL_PREFIX@stride_destroy -+@FFTW_SYMBOL_PREFIX@taint -+@FFTW_SYMBOL_PREFIX@tensor_append -+@FFTW_SYMBOL_PREFIX@tensor_compress -+@FFTW_SYMBOL_PREFIX@tensor_compress_contiguous -+@FFTW_SYMBOL_PREFIX@tensor_copy -+@FFTW_SYMBOL_PREFIX@tensor_copy_except -+@FFTW_SYMBOL_PREFIX@tensor_copy_inplace -+@FFTW_SYMBOL_PREFIX@tensor_copy_sub -+@FFTW_SYMBOL_PREFIX@tensor_destroy -+@FFTW_SYMBOL_PREFIX@tensor_destroy2 -+@FFTW_SYMBOL_PREFIX@tensor_destroy4 -+@FFTW_SYMBOL_PREFIX@tensor_equal -+@FFTW_SYMBOL_PREFIX@tensor_inplace_locations -+@FFTW_SYMBOL_PREFIX@tensor_inplace_strides -+@FFTW_SYMBOL_PREFIX@tensor_inplace_strides2 -+@FFTW_SYMBOL_PREFIX@tensor_kosherp -+@FFTW_SYMBOL_PREFIX@tensor_max_index -+@FFTW_SYMBOL_PREFIX@tensor_md5 -+@FFTW_SYMBOL_PREFIX@tensor_min_istride -+@FFTW_SYMBOL_PREFIX@tensor_min_ostride -+@FFTW_SYMBOL_PREFIX@tensor_min_stride -+@FFTW_SYMBOL_PREFIX@tensor_print -+@FFTW_SYMBOL_PREFIX@tensor_split -+@FFTW_SYMBOL_PREFIX@tensor_strides_decrease -+@FFTW_SYMBOL_PREFIX@tensor_sz -+@FFTW_SYMBOL_PREFIX@tensor_tornk1 -+@FFTW_SYMBOL_PREFIX@the_planner -+@FFTW_SYMBOL_PREFIX@threads_cleanup -+@FFTW_SYMBOL_PREFIX@threads_conf_standard -+@FFTW_SYMBOL_PREFIX@tile2d -+@FFTW_SYMBOL_PREFIX@toobig -+@FFTW_SYMBOL_PREFIX@transpose -+@FFTW_SYMBOL_PREFIX@transpose_tiled -+@FFTW_SYMBOL_PREFIX@transpose_tiledbuf -+@FFTW_SYMBOL_PREFIX@triggen_destroy -+@FFTW_SYMBOL_PREFIX@twiddle_awake -+@FFTW_SYMBOL_PREFIX@twiddle_length -+;sfftw_cleanup_ -+;sfftw_cleanup__ -+;sfftw_cleanup_threads_ -+;sfftw_cleanup_threads__ -+;sfftw_destroy_plan_ -+;sfftw_destroy_plan__ -+;sfftw_execute_ -+;sfftw_execute__ -+;sfftw_execute_dft_ -+;sfftw_execute_dft__ -+;sfftw_execute_dft_c2r_ -+;sfftw_execute_dft_c2r__ -+;sfftw_execute_dft_r2c_ -+;sfftw_execute_dft_r2c__ -+;sfftw_execute_r2r_ -+;sfftw_execute_r2r__ -+;sfftw_execute_split_dft_ -+;sfftw_execute_split_dft__ -+;sfftw_execute_split_dft_c2r_ -+;sfftw_execute_split_dft_c2r__ -+;sfftw_execute_split_dft_r2c_ -+;sfftw_execute_split_dft_r2c__ -+;sfftw_export_wisdom_ -+;sfftw_export_wisdom__ -+;sfftw_flops_ -+;sfftw_flops__ -+;sfftw_forget_wisdom_ -+;sfftw_forget_wisdom__ -+;sfftw_import_system_wisdom_ -+;sfftw_import_system_wisdom__ -+;sfftw_import_wisdom_ -+;sfftw_import_wisdom__ -+;sfftw_init_threads_ -+;sfftw_init_threads__ -+;sfftw_plan_dft_ -+;sfftw_plan_dft_1d_ -+;sfftw_plan_dft_1d__ -+;sfftw_plan_dft_2d_ -+;sfftw_plan_dft_2d__ -+;sfftw_plan_dft_3d_ -+;sfftw_plan_dft_3d__ -+;sfftw_plan_dft__ -+;sfftw_plan_dft_c2r_ -+;sfftw_plan_dft_c2r_1d_ -+;sfftw_plan_dft_c2r_1d__ -+;sfftw_plan_dft_c2r_2d_ -+;sfftw_plan_dft_c2r_2d__ -+;sfftw_plan_dft_c2r_3d_ -+;sfftw_plan_dft_c2r_3d__ -+;sfftw_plan_dft_c2r__ -+;sfftw_plan_dft_r2c_ -+;sfftw_plan_dft_r2c_1d_ -+;sfftw_plan_dft_r2c_1d__ -+;sfftw_plan_dft_r2c_2d_ -+;sfftw_plan_dft_r2c_2d__ -+;sfftw_plan_dft_r2c_3d_ -+;sfftw_plan_dft_r2c_3d__ -+;sfftw_plan_dft_r2c__ -+;sfftw_plan_guru_dft_ -+;sfftw_plan_guru_dft__ -+;sfftw_plan_guru_dft_c2r_ -+;sfftw_plan_guru_dft_c2r__ -+;sfftw_plan_guru_dft_r2c_ -+;sfftw_plan_guru_dft_r2c__ -+;sfftw_plan_guru_r2r_ -+;sfftw_plan_guru_r2r__ -+;sfftw_plan_guru_split_dft_ -+;sfftw_plan_guru_split_dft__ -+;sfftw_plan_guru_split_dft_c2r_ -+;sfftw_plan_guru_split_dft_c2r__ -+;sfftw_plan_guru_split_dft_r2c_ -+;sfftw_plan_guru_split_dft_r2c__ -+;sfftw_plan_many_dft_ -+;sfftw_plan_many_dft__ -+;sfftw_plan_many_dft_c2r_ -+;sfftw_plan_many_dft_c2r__ -+;sfftw_plan_many_dft_r2c_ -+;sfftw_plan_many_dft_r2c__ -+;sfftw_plan_many_r2r_ -+;sfftw_plan_many_r2r__ -+;sfftw_plan_r2r_ -+;sfftw_plan_r2r_1d_ -+;sfftw_plan_r2r_1d__ -+;sfftw_plan_r2r_2d_ -+;sfftw_plan_r2r_2d__ -+;sfftw_plan_r2r_3d_ -+;sfftw_plan_r2r_3d__ -+;sfftw_plan_r2r__ -+;sfftw_plan_with_nthreads_ -+;sfftw_plan_with_nthreads__ -+;sfftw_print_plan_ -+;sfftw_print_plan__ -diff --git a/kernel/CMakeLists.txt b/kernel/CMakeLists.txt -new file mode 100644 -index 0000000..1552988 ---- /dev/null -+++ b/kernel/CMakeLists.txt -@@ -0,0 +1,17 @@ -+# pkgincludedir = $(includedir)/fftw3@PREC_SUFFIX@ -+# pkginclude_HEADERS = ifftw.h cycle.h -+ -+set(kernel_SRCS align.c alloc.c assert.c awake.c buffered.c -+cpy1d.c cpy2d-pair.c cpy2d.c ct.c debug.c extract-reim.c hash.c iabs.c -+kalloc.c md5-1.c md5.c minmax.c ops.c pickdim.c plan.c planner.c -+primes.c print.c problem.c rader.c scan.c solver.c solvtab.c stride.c -+tensor.c tensor1.c tensor2.c tensor3.c tensor4.c tensor5.c tensor7.c -+tensor8.c tensor9.c tile2d.c timer.c transpose.c trig.c twiddle.c -+) -+ -+if(BUILD_ALL_STATIC) -+ add_library(kernel STATIC ${kernel_SRCS}) -+else(BUILD_ALL_STATIC) -+ prepend_prefix(kernel_SRCS) -+ set(fftw_SRCS ${fftw_SRCS} ${kernel_SRCS} PARENT_SCOPE) -+endif(BUILD_ALL_STATIC) -diff --git a/libbench2/CMakeLists.txt b/libbench2/CMakeLists.txt -new file mode 100644 -index 0000000..a8c06b7 ---- /dev/null -+++ b/libbench2/CMakeLists.txt -@@ -0,0 +1,11 @@ -+set(bench2_SRCS after-ccopy-from.c after-ccopy-to.c -+after-hccopy-from.c after-hccopy-to.c after-rcopy-from.c -+after-rcopy-to.c aligned-main.c allocate.c aset.c -+bench-cost-postprocess.c bench-exit.c bench-main.c can-do.c caset.c -+dotens2.c info.c main.c mflops.c mp.c ovtpvt.c pow2.c problem.c -+report.c speed.c tensor.c timer.c useropt.c util.c verify-dft.c -+verify-lib.c verify-r2r.c verify-rdft2.c verify.c zero.c -+my-getopt.c -+) -+ -+add_library(bench2 STATIC ${bench2_SRCS}) -diff --git a/rdft/CMakeLists.txt b/rdft/CMakeLists.txt -new file mode 100644 -index 0000000..4a9f057 ---- /dev/null -+++ b/rdft/CMakeLists.txt -@@ -0,0 +1,23 @@ -+add_subdirectory(scalar) -+add_subdirectory(simd) -+ -+ -+set(RDFT2_SRCS buffered2.c direct2.c nop2.c rank0-rdft2.c rank-geq2-rdft2.c -+plan2.c problem2.c solve2.c vrank-geq1-rdft2.c rdft2-rdft.c -+rdft2-tensor-max-index.c rdft2-inplace-strides.c rdft2-strides.c -+khc2c.c ct-hc2c.c ct-hc2c-direct.c -+) -+ -+set(rdft_SRCS hc2hc.c dft-r2hc.c dht-r2hc.c dht-rader.c -+buffered.c conf.c direct-r2r.c direct-r2c.c generic.c -+hc2hc-direct.c hc2hc-generic.c khc2hc.c kr2c.c kr2r.c indirect.c nop.c -+plan.c problem.c rank0.c rank-geq2.c rdft-dht.c solve.c -+vrank-geq1.c vrank3-transpose.c ${RDFT2_SRCS} -+) -+ -+if(BUILD_ALL_STATIC) -+ add_library(rdft STATIC ${rdft_SRCS}) -+else(BUILD_ALL_STATIC) -+ prepend_prefix(rdft_SRCS) -+ set(fftw_SRCS ${fftw_SRCS} ${rdft_SRCS} ${_fftw_SRCS} PARENT_SCOPE) -+endif(BUILD_ALL_STATIC) -diff --git a/rdft/scalar/CMakeLists.txt b/rdft/scalar/CMakeLists.txt -new file mode 100644 -index 0000000..ea61666 ---- /dev/null -+++ b/rdft/scalar/CMakeLists.txt -@@ -0,0 +1,12 @@ -+add_subdirectory(r2cb) -+add_subdirectory(r2cf) -+add_subdirectory(r2r) -+ -+set(rdft_scalar_SRCS ${rdft_scalar_SRCS} hfb.c r2c.c r2r.c hc2c.c) -+ -+if(BUILD_ALL_STATIC) -+ add_library(rdft_scalar STATIC ${rdft_scalar_SRCS}) -+else(BUILD_ALL_STATIC) -+ prepend_prefix(rdft_scalar_SRCS) -+ set(_fftw_SRCS ${_fftw_SRCS} ${rdft_scalar_SRCS} ${__fftw_SRCS} PARENT_SCOPE) -+endif(BUILD_ALL_STATIC) -diff --git a/rdft/scalar/r2cb/CMakeLists.txt b/rdft/scalar/r2cb/CMakeLists.txt -new file mode 100644 -index 0000000..09ac2e8 ---- /dev/null -+++ b/rdft/scalar/r2cb/CMakeLists.txt -@@ -0,0 +1,126 @@ -+# This Makefile.am specifies a set of codelets, efficient transforms -+# of small sizes, that are used as building blocks (kernels) by FFTW -+# to build up large transforms, as well as the options for generating -+# and compiling them. -+ -+# You can customize FFTW for special needs, e.g. to handle certain -+# sizes more efficiently, by adding new codelets to the lists of those -+# included by default. If you change the list of codelets, any new -+# ones you added will be automatically generated when you run the -+# bootstrap script (see "Generating your own code" in the FFTW -+# manual). -+ -+########################################################################### -+# r2cb_ is a hard-coded complex-to-real FFT of size (base cases -+# of real-output FFT recursion) -+set(R2CB r2cb_2.c r2cb_3.c r2cb_4.c r2cb_5.c r2cb_6.c r2cb_7.c r2cb_8.c -+r2cb_9.c r2cb_10.c r2cb_11.c r2cb_12.c r2cb_13.c r2cb_14.c r2cb_15.c -+r2cb_16.c r2cb_32.c r2cb_64.c r2cb_128.c r2cb_20.c r2cb_25.c -+# r2cb_30.c r2cb_40.c r2cb_50.c -+) -+ -+########################################################################### -+# hb_ is a "twiddle" FFT of size , implementing a radix-r DIF -+# step for a real-output FFT. Every hb codelet must have a -+# corresponding r2cbIII codelet (see below)! -+set(HB hb_2.c hb_3.c hb_4.c hb_5.c hb_6.c hb_7.c hb_8.c hb_9.c -+hb_10.c hb_12.c hb_15.c hb_16.c hb_32.c hb_64.c -+hb_20.c hb_25.c # hb_30.c hb_40.c hb_50.c -+) -+ -+# like hb, but generates part of its trig table on the fly (good for large n) -+set(HB2 hb2_4.c hb2_8.c hb2_16.c hb2_32.c -+hb2_5.c hb2_20.c hb2_25.c -+) -+ -+# an r2cb transform where the output is shifted by half a sample (input -+# is multiplied by a phase). This is needed as part of the DIF recursion; -+# every hb_ or hb2_ codelet should have a corresponding r2cbIII_ -+set(R2CBIII r2cbIII_2.c r2cbIII_3.c r2cbIII_4.c r2cbIII_5.c r2cbIII_6.c -+r2cbIII_7.c r2cbIII_8.c r2cbIII_9.c r2cbIII_10.c r2cbIII_12.c -+r2cbIII_15.c r2cbIII_16.c r2cbIII_32.c r2cbIII_64.c -+r2cbIII_20.c r2cbIII_25.c # r2cbIII_30.c r2cbIII_40.c r2cbIII_50.c -+) -+ -+########################################################################### -+# hc2cb_ is a "twiddle" FFT of size , implementing a radix-r DIF -+# step for a real-input FFT with rdft2-style output. must be even. -+set(HC2CB hc2cb_2.c hc2cb_4.c hc2cb_6.c hc2cb_8.c hc2cb_10.c hc2cb_12.c -+hc2cb_16.c hc2cb_32.c -+hc2cb_20.c # hc2cb_30.c -+) -+ -+set(HC2CBDFT hc2cbdft_2.c hc2cbdft_4.c hc2cbdft_6.c hc2cbdft_8.c -+hc2cbdft_10.c hc2cbdft_12.c hc2cbdft_16.c hc2cbdft_32.c -+hc2cbdft_20.c # hc2cbdft_30.c -+) -+ -+# like hc2cb, but generates part of its trig table on the fly (good -+# for large n) -+set(HC2CB2 hc2cb2_4.c hc2cb2_8.c hc2cb2_16.c hc2cb2_32.c -+hc2cb2_20.c # hc2cb2_30.c -+) -+set(HC2CBDFT2 hc2cbdft2_4.c hc2cbdft2_8.c hc2cbdft2_16.c hc2cbdft2_32.c -+hc2cbdft2_20.c # hc2cbdft2_30.c -+) -+ -+########################################################################### -+set(ALL_CODELETS ${R2CB} ${HB} ${HB2} ${R2CBIII} ${HC2CB} ${HC2CB2} ${HC2CBDFT} ${HC2CBDFT2}) -+ -+include(${CMAKE_SOURCE_DIR}/support/codelets.cmake) -+ -+set(SOLVTAB_NAME "X(solvtab_rdft_r2cb)") -+ -+set(CODLIST ${CMAKE_CURRENT_BINARY_DIR}/codlist.c) -+set(CODELET_NAME codelet_) -+ -+write_codelet_list(${CODLIST} ${CODELET_NAME} ${SOLVTAB_NAME} ${ALL_CODELETS}) -+ -+set(rdft_scalar_r2cb_SRCS ${ALL_CODELETS}) -+ -+if(BUILD_ALL_STATIC) -+ add_library(rdft_scalar_r2cb STATIC ${rdft_scalar_r2cb_SRCS}) -+else(BUILD_ALL_STATIC) -+ prepend_prefix(rdft_scalar_r2cb_SRCS) -+ set(__fftw_SRCS ${__fftw_SRCS} ${rdft_scalar_r2cb_SRCS} ${CODLIST} PARENT_SCOPE) -+endif(BUILD_ALL_STATIC) -+ -+# this is old source code which can be changed later -+# only delete codlist.c in maintainer-mode, since it is included in the dist -+# FIXME: is there a way to delete in 'make clean' only when builddir != srcdir? -+#maintainer-clean-local: -+# rm -f $(CODLIST) -+ -+#if MAINTAINER_MODE -+#FLAGS_R2CB=$(RDFT_FLAGS_COMMON) -sign 1 -+#FLAGS_HB=$(RDFT_FLAGS_COMMON) -sign 1 -+#FLAGS_HB2=$(RDFT_FLAGS_COMMON) -sign 1 -twiddle-log3 -precompute-twiddles -+#FLAGS_HC2CB=$(RDFT_FLAGS_COMMON) -sign 1 -+#FLAGS_HC2CB2=$(RDFT_FLAGS_COMMON) -sign 1 -twiddle-log3 -precompute-twiddles -+#FLAGS_R2CBIII=$(RDFT_FLAGS_COMMON) -sign 1 -+# -+#r2cb_%.c: $(CODELET_DEPS) $(GEN_R2CB) -+# ($(PRELUDE_COMMANDS_RDFT); $(TWOVERS) $(GEN_R2CB) $(FLAGS_R2CB) -n $* -name r2cb_$* -include "r2cb.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#hb_%.c: $(CODELET_DEPS) $(GEN_HC2HC) -+# ($(PRELUDE_COMMANDS_RDFT); $(TWOVERS) $(GEN_HC2HC) $(FLAGS_HB) -n $* -dif -name hb_$* -include "hb.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#hb2_%.c: $(CODELET_DEPS) $(GEN_HC2HC) -+# ($(PRELUDE_COMMANDS_RDFT); $(TWOVERS) $(GEN_HC2HC) $(FLAGS_HB2) -n $* -dif -name hb2_$* -include "hb.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#r2cbIII_%.c: $(CODELET_DEPS) $(GEN_R2CB) -+# ($(PRELUDE_COMMANDS_RDFT); $(TWOVERS) $(GEN_R2CB) $(FLAGS_R2CB) -n $* -name r2cbIII_$* -dft-III -include "r2cbIII.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#hc2cb_%.c: $(CODELET_DEPS) $(GEN_HC2C) -+# ($(PRELUDE_COMMANDS_RDFT); $(TWOVERS) $(GEN_HC2C) $(FLAGS_HC2CB) -n $* -dif -name hc2cb_$* -include "hc2cb.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#hc2cb2_%.c: $(CODELET_DEPS) $(GEN_HC2C) -+# ($(PRELUDE_COMMANDS_RDFT); $(TWOVERS) $(GEN_HC2C) $(FLAGS_HC2CB2) -n $* -dif -name hc2cb2_$* -include "hc2cb.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#hc2cbdft_%.c: $(CODELET_DEPS) $(GEN_HC2CDFT) -+# ($(PRELUDE_COMMANDS_RDFT); $(TWOVERS) $(GEN_HC2CDFT) $(FLAGS_HC2CB) -n $* -dif -name hc2cbdft_$* -include "hc2cb.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#hc2cbdft2_%.c: $(CODELET_DEPS) $(GEN_HC2CDFT) -+# ($(PRELUDE_COMMANDS_RDFT); $(TWOVERS) $(GEN_HC2CDFT) $(FLAGS_HC2CB) -n $* -dif -name hc2cbdft2_$* -include "hc2cb.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#endif # MAINTAINER_MODE -diff --git a/rdft/scalar/r2cf/CMakeLists.txt b/rdft/scalar/r2cf/CMakeLists.txt -new file mode 100644 -index 0000000..7ffb3ee ---- /dev/null -+++ b/rdft/scalar/r2cf/CMakeLists.txt -@@ -0,0 +1,128 @@ -+# This Makefile.am specifies a set of codelets, efficient transforms -+# of small sizes, that are used as building blocks (kernels) by FFTW -+# to build up large transforms, as well as the options for generating -+# and compiling them. -+ -+# You can customize FFTW for special needs, e.g. to handle certain -+# sizes more efficiently, by adding new codelets to the lists of those -+# included by default. If you change the list of codelets, any new -+# ones you added will be automatically generated when you run the -+# bootstrap script (see "Generating your own code" in the FFTW -+# manual). -+ -+########################################################################### -+ -+########################################################################### -+# r2cf_ is a hard-coded real-to-complex FFT of size (base cases -+# of real-input FFT recursion) -+set(R2CF r2cf_2.c r2cf_3.c r2cf_4.c r2cf_5.c r2cf_6.c r2cf_7.c r2cf_8.c -+r2cf_9.c r2cf_10.c r2cf_11.c r2cf_12.c r2cf_13.c r2cf_14.c r2cf_15.c -+r2cf_16.c r2cf_32.c r2cf_64.c r2cf_128.c -+r2cf_20.c r2cf_25.c # r2cf_30.c r2cf_40.c r2cf_50.c -+) -+ -+########################################################################### -+# hf_ is a "twiddle" FFT of size , implementing a radix-r DIT -+# step for a real-input FFT. Every hf codelet must have a -+# corresponding r2cfII codelet (see below)! -+set(HF hf_2.c hf_3.c hf_4.c hf_5.c hf_6.c hf_7.c hf_8.c hf_9.c -+hf_10.c hf_12.c hf_15.c hf_16.c hf_32.c hf_64.c -+hf_20.c hf_25.c # hf_30.c hf_40.c hf_50.c -+) -+ -+# like hf, but generates part of its trig table on the fly (good for large n) -+set(HF2 hf2_4.c hf2_8.c hf2_16.c hf2_32.c -+hf2_5.c hf2_20.c hf2_25.c -+) -+ -+# an r2cf transform where the input is shifted by half a sample (output -+# is multiplied by a phase). This is needed as part of the DIT recursion; -+# every hf_ or hf2_ codelet should have a corresponding r2cfII_ -+set(R2CFII r2cfII_2.c r2cfII_3.c r2cfII_4.c r2cfII_5.c r2cfII_6.c -+r2cfII_7.c r2cfII_8.c r2cfII_9.c r2cfII_10.c r2cfII_12.c r2cfII_15.c -+r2cfII_16.c r2cfII_32.c r2cfII_64.c -+r2cfII_20.c r2cfII_25.c # r2cfII_30.c r2cfII_40.c r2cfII_50.c -+) -+ -+########################################################################### -+# hc2cf_ is a "twiddle" FFT of size , implementing a radix-r DIT -+# step for a real-input FFT with rdft2-style output. must be even. -+set(HC2CF hc2cf_2.c hc2cf_4.c hc2cf_6.c hc2cf_8.c hc2cf_10.c hc2cf_12.c -+hc2cf_16.c hc2cf_32.c -+hc2cf_20.c # hc2cf_30.c -+) -+ -+set(HC2CFDFT hc2cfdft_2.c hc2cfdft_4.c hc2cfdft_6.c hc2cfdft_8.c -+hc2cfdft_10.c hc2cfdft_12.c hc2cfdft_16.c hc2cfdft_32.c -+hc2cfdft_20.c # hc2cfdft_30.c -+) -+ -+# like hc2cf, but generates part of its trig table on the fly (good -+# for large n) -+set(HC2CF2 hc2cf2_4.c hc2cf2_8.c hc2cf2_16.c hc2cf2_32.c -+hc2cf2_20.c # hc2cf2_30.c -+) -+set(HC2CFDFT2 hc2cfdft2_4.c hc2cfdft2_8.c hc2cfdft2_16.c hc2cfdft2_32.c -+hc2cfdft2_20.c # hc2cfdft2_30.c -+) -+ -+########################################################################### -+set(ALL_CODELETS ${R2CF} ${HF} ${HF2} ${R2CFII} ${HC2CF} ${HC2CF2} ${HC2CFDFT} ${HC2CFDFT2}) -+ -+include(${CMAKE_SOURCE_DIR}/support/codelets.cmake) -+ -+set(SOLVTAB_NAME "X(solvtab_rdft_r2cf)") -+ -+set(CODLIST ${CMAKE_CURRENT_BINARY_DIR}/codlist.c) -+set(CODELET_NAME codelet_) -+ -+write_codelet_list(${CODLIST} ${CODELET_NAME} ${SOLVTAB_NAME} ${ALL_CODELETS}) -+ -+set(rdft_scalar_r2cf_SRCS ${ALL_CODELETS}) -+ -+if(BUILD_ALL_STATIC) -+ add_library(rdft_scalar_r2cf STATIC ${rdft_scalar_r2cf_SRCS}) -+else(BUILD_ALL_STATIC) -+ prepend_prefix(rdft_scalar_r2cf_SRCS) -+ set(__fftw_SRCS ${__fftw_SRCS} ${rdft_scalar_r2cf_SRCS} ${CODLIST} PARENT_SCOPE) -+endif(BUILD_ALL_STATIC) -+ -+# this is old source code which can be changed later -+# only delete codlist.c in maintainer-mode, since it is included in the dist -+# FIXME: is there a way to delete in 'make clean' only when builddir != srcdir? -+#maintainer-clean-local: -+# rm -f $(CODLIST) -+ -+#if MAINTAINER_MODE -+#FLAGS_R2CF=$(RDFT_FLAGS_COMMON) -+#FLAGS_HF=$(RDFT_FLAGS_COMMON) -+#FLAGS_HF2=$(RDFT_FLAGS_COMMON) -twiddle-log3 -precompute-twiddles -+#FLAGS_HC2CF=$(RDFT_FLAGS_COMMON) -+#FLAGS_HC2CF2=$(RDFT_FLAGS_COMMON) -twiddle-log3 -precompute-twiddles -+#FLAGS_R2CFII=$(RDFT_FLAGS_COMMON) -+# -+#r2cf_%.c: $(CODELET_DEPS) $(GEN_R2CF) -+# ($(PRELUDE_COMMANDS_RDFT); $(TWOVERS) $(GEN_R2CF) $(FLAGS_R2CF) -n $* -name r2cf_$* -include "r2cf.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#hf_%.c: $(CODELET_DEPS) $(GEN_HC2HC) -+# ($(PRELUDE_COMMANDS_RDFT); $(TWOVERS) $(GEN_HC2HC) $(FLAGS_HF) -n $* -dit -name hf_$* -include "hf.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#hf2_%.c: $(CODELET_DEPS) $(GEN_HC2HC) -+# ($(PRELUDE_COMMANDS_RDFT); $(TWOVERS) $(GEN_HC2HC) $(FLAGS_HF2) -n $* -dit -name hf2_$* -include "hf.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#r2cfII_%.c: $(CODELET_DEPS) $(GEN_R2CF) -+# ($(PRELUDE_COMMANDS_RDFT); $(TWOVERS) $(GEN_R2CF) $(FLAGS_R2CF) -n $* -name r2cfII_$* -dft-II -include "r2cfII.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#hc2cf_%.c: $(CODELET_DEPS) $(GEN_HC2C) -+# ($(PRELUDE_COMMANDS_RDFT); $(TWOVERS) $(GEN_HC2C) $(FLAGS_HC2CF) -n $* -dit -name hc2cf_$* -include "hc2cf.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#hc2cf2_%.c: $(CODELET_DEPS) $(GEN_HC2C) -+# ($(PRELUDE_COMMANDS_RDFT); $(TWOVERS) $(GEN_HC2C) $(FLAGS_HC2CF2) -n $* -dit -name hc2cf2_$* -include "hc2cf.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#hc2cfdft_%.c: $(CODELET_DEPS) $(GEN_HC2CDFT) -+# ($(PRELUDE_COMMANDS_RDFT); $(TWOVERS) $(GEN_HC2CDFT) $(FLAGS_HC2CF) -n $* -dit -name hc2cfdft_$* -include "hc2cf.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#hc2cfdft2_%.c: $(CODELET_DEPS) $(GEN_HC2CDFT) -+# ($(PRELUDE_COMMANDS_RDFT); $(TWOVERS) $(GEN_HC2CDFT) $(FLAGS_HC2CF2) -n $* -dit -name hc2cfdft2_$* -include "hc2cf.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#endif # MAINTAINER_MODE -diff --git a/rdft/scalar/r2r/CMakeLists.txt b/rdft/scalar/r2r/CMakeLists.txt -new file mode 100644 -index 0000000..1a95451 ---- /dev/null -+++ b/rdft/scalar/r2r/CMakeLists.txt -@@ -0,0 +1,100 @@ -+# This Makefile.am specifies a set of codelets, efficient transforms -+# of small sizes, that are used as building blocks (kernels) by FFTW -+# to build up large transforms, as well as the options for generating -+# and compiling them. -+ -+# You can customize FFTW for special needs, e.g. to handle certain -+# sizes more efficiently, by adding new codelets to the lists of those -+# included by default. If you change the list of codelets, any new -+# ones you added will be automatically generated when you run the -+# bootstrap script (see "Generating your own code" in the FFTW -+# manual). -+ -+########################################################################### -+ -+########################################################################### -+# The following lines specify the REDFT/RODFT/DHT sizes for which to generate -+# specialized codelets. Currently, only REDFT01/10 of size 8 (used in JPEG). -+ -+# e_ is a hard-coded REDFT FFT (DCT) of size -+set(E00 # e00_2.c e00_3.c e00_4.c e00_5.c e00_6.c e00_7.c e00_8.c -+) -+set(E01 e01_8.c # e01_2.c e01_3.c e01_4.c e01_5.c e01_6.c e01_7.c -+) -+set(E10 e10_8.c # e10_2.c e10_3.c e10_4.c e10_5.c e10_6.c e10_7.c -+) -+set(E11 # e11_2.c e11_3.c e11_4.c e11_5.c e11_6.c e11_7.c e11_8.c -+) -+ -+# o_ is a hard-coded RODFT FFT (DST) of size -+set(O00 # o00_2.c o00_3.c o00_4.c o00_5.c o00_6.c o00_7.c o00_8.c -+) -+set(O01 # o01_2.c o01_3.c o01_4.c o01_5.c o01_6.c o01_7.c o01_8.c -+) -+set(O10 # o10_2.c o10_3.c o10_4.c o10_5.c o10_6.c o10_7.c o10_8.c -+) -+set(O11 # o11_2.c o11_3.c o11_4.c o11_5.c o11_6.c o11_7.c o11_8.c -+) -+ -+# dht_ is a hard-coded DHT of size -+set(DHT # dht_2.c dht_3.c dht_4.c dht_5.c dht_6.c dht_7.c dht_8.c -+) -+ -+########################################################################### -+set(ALL_CODELETS ${E00} ${E01} ${E10} ${E11} ${O00} ${O01} ${O10} ${O11} ${DHT}) -+ -+include(${CMAKE_SOURCE_DIR}/support/codelets.cmake) -+ -+set(SOLVTAB_NAME "X(solvtab_rdft_r2r)") -+ -+set(CODLIST ${CMAKE_CURRENT_BINARY_DIR}/codlist.c) -+set(CODELET_NAME codelet_) -+ -+write_codelet_list(${CODLIST} ${CODELET_NAME} ${SOLVTAB_NAME} ${ALL_CODELETS}) -+ -+set(rdft_scalar_r2r_SRCS ${ALL_CODELETS}) -+ -+if(BUILD_ALL_STATIC) -+ add_library(rdft_scalar_r2r STATIC ${rdft_scalar_r2r_SRCS}) -+else(BUILD_ALL_STATIC) -+ prepend_prefix(rdft_scalar_r2r_SRCS) -+ set(__fftw_SRCS ${__fftw_SRCS} ${rdft_scalar_r2r_SRCS} ${CODLIST} PARENT_SCOPE) -+endif(BUILD_ALL_STATIC) -+ -+# this is old source code which can be changed later -+# only delete codlist.c in maintainer-mode, since it is included in the dist -+# FIXME: is there a way to delete in 'make clean' only when builddir != srcdir? -+#maintainer-clean-local: -+# rm -f $(CODLIST) -+ -+#if MAINTAINER_MODE -+#FLAGS_N1=$(DFT_FLAGS_COMMON) -+#FLAGS_T1=$(DFT_FLAGS_COMMON) -+#FLAGS_T2=$(DFT_FLAGS_COMMON) -twiddle-log3 -precompute-twiddles -+#FLAGS_F1=$(DFT_FLAGS_COMMON) -+#FLAGS_F2=$(DFT_FLAGS_COMMON) -twiddle-log3 -precompute-twiddles -+#FLAGS_Q1=$(DFT_FLAGS_COMMON) -reload-twiddle -+#FLAGS_Q2=$(DFT_FLAGS_COMMON) -twiddle-log3 -precompute-twiddles -+# -+#n1_%.c: $(CODELET_DEPS) $(GEN_NOTW) -+# ($(PRELUDE_COMMANDS_DFT); $(TWOVERS) $(GEN_NOTW) $(FLAGS_N1) -n $* -name n1_$* -include "n.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#t1_%.c: $(CODELET_DEPS) $(GEN_TWIDDLE) -+# ($(PRELUDE_COMMANDS_DFT); $(TWOVERS) $(GEN_TWIDDLE) $(FLAGS_T1) -n $* -name t1_$* -include "t.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#t2_%.c: $(CODELET_DEPS) $(GEN_TWIDDLE) -+# ($(PRELUDE_COMMANDS_DFT); $(TWOVERS) $(GEN_TWIDDLE) $(FLAGS_T2) -n $* -name t2_$* -include "t.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#f1_%.c: $(CODELET_DEPS) $(GEN_TWIDDLE) -+# ($(PRELUDE_COMMANDS_DFT); $(TWOVERS) $(GEN_TWIDDLE) $(FLAGS_F1) -dif -n $* -name f1_$* -include "f.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#f2_%.c: $(CODELET_DEPS) $(GEN_TWIDDLE) -+# ($(PRELUDE_COMMANDS_DFT); $(TWOVERS) $(GEN_TWIDDLE) $(FLAGS_F2) -dif -n $* -name f2_$* -include "f.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#q1_%.c: $(CODELET_DEPS) $(GEN_TWIDSQ) -+# ($(PRELUDE_COMMANDS_DFT); $(TWOVERS) $(GEN_TWIDSQ) $(FLAGS_Q1) -dif -n $* -name q1_$* -include "q.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#q2_%.c: $(CODELET_DEPS) $(GEN_TWIDSQ) -+# ($(PRELUDE_COMMANDS_DFT); $(TWOVERS) $(GEN_TWIDSQ) $(FLAGS_Q2) -dif -n $* -name q2_$* -include "q.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#endif # MAINTAINER_MODE -diff --git a/rdft/simd/CMakeLists.txt b/rdft/simd/CMakeLists.txt -new file mode 100644 -index 0000000..09191cb ---- /dev/null -+++ b/rdft/simd/CMakeLists.txt -@@ -0,0 +1,10 @@ -+add_subdirectory(codelets) -+ -+set(rdft_simd_SRCS hc2cbv.c hc2cfv.c) -+ -+if(BUILD_ALL_STATIC) -+ add_library(rdft_simd STATIC ${rdft_simd_SRCS}) -+else(BUILD_ALL_STATIC) -+ prepend_prefix(rdft_simd_SRCS) -+ set(_fftw_SRCS ${_fftw_SRCS} ${rdft_simd_SRCS} ${__fftw_SRCS} PARENT_SCOPE) -+endif(BUILD_ALL_STATIC) -diff --git a/rdft/simd/codelets/CMakeLists.txt b/rdft/simd/codelets/CMakeLists.txt -new file mode 100644 -index 0000000..1b4bc06 ---- /dev/null -+++ b/rdft/simd/codelets/CMakeLists.txt -@@ -0,0 +1,61 @@ -+# This Makefile.am specifies a set of codelets, efficient transforms -+# of small sizes, that are used as building blocks (kernels) by FFTW -+# to build up large transforms, as well as the options for generating -+# and compiling them. -+ -+# You can customize FFTW for special needs, e.g. to handle certain -+# sizes more efficiently, by adding new codelets to the lists of those -+# included by default. If you change the list of codelets, any new -+# ones you added will be automatically generated when you run the -+# bootstrap script (see "Generating your own code" in the FFTW -+# manual). -+ -+########################################################################### -+ -+set(HC2CFDFTV hc2cfdftv_2.c hc2cfdftv_4.c hc2cfdftv_6.c hc2cfdftv_8.c -+hc2cfdftv_10.c hc2cfdftv_12.c hc2cfdftv_16.c hc2cfdftv_32.c -+hc2cfdftv_20.c # hc2cfdftv_30.c -+) -+ -+set(HC2CBDFTV hc2cbdftv_2.c hc2cbdftv_4.c hc2cbdftv_6.c hc2cbdftv_8.c -+hc2cbdftv_10.c hc2cbdftv_12.c hc2cbdftv_16.c hc2cbdftv_32.c -+hc2cbdftv_20.c # hc2cbdftv_30.c -+) -+ -+########################################################################### -+set(SIMD_CODELETS ${HC2CFDFTV} ${HC2CBDFTV}) -+ -+if(HAVE_SIMD) -+ set(ALL_CODELETS ${SIMD_CODELETS}) -+else(HAVE_SIMD) -+ set(ALL_CODELETS) -+endif(HAVE_SIMD) -+ -+set(SOLVTAB_NAME "X(solvtab_rdft_simd)") -+set(CODLIST ${CMAKE_CURRENT_BINARY_DIR}/codlist.c) -+set(CODELET_NAME codelet_) -+ -+set(rdft_simd_codelets_SRCS ${ALL_CODELETS}) -+ -+# special rules for regenerating codelets. -+include(${CMAKE_SOURCE_DIR}/support/codelets.cmake) -+ -+write_codelet_list(${CODLIST} ${CODELET_NAME} ${SOLVTAB_NAME} ${ALL_CODELETS}) -+ -+if(BUILD_ALL_STATIC) -+ add_library(rdft_simd_codelets STATIC ${ALL_CODELETS}) -+else(BUILD_ALL_STATIC) -+ prepend_prefix(rdft_simd_codelets_SRCS) -+ set(__fftw_SRCS ${__fftw_SRCS} ${rdft_simd_codelets_SRCS} ${CODLIST} PARENT_SCOPE) -+endif(BUILD_ALL_STATIC) -+ -+#if MAINTAINER_MODE -+#FLAGS_HC2C=-simd $(FLAGS_COMMON) -pipeline-latency 8 -trivial-stores -variables 32 -no-generate-bytw -+# -+#hc2cfdftv_%.c: $(CODELET_DEPS) $(GEN_HC2CDFT_C) -+# ($(PRELUDE_COMMANDS_RDFT); $(TWOVERS) $(GEN_HC2CDFT_C) $(FLAGS_HC2C) -n $* -dit -name hc2cfdftv_$* -include "hc2cfv.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#hc2cbdftv_%.c: $(CODELET_DEPS) $(GEN_HC2CDFT_C) -+# ($(PRELUDE_COMMANDS_RDFT); $(TWOVERS) $(GEN_HC2CDFT_C) $(FLAGS_HC2C) -n $* -dif -sign 1 -name hc2cbdftv_$* -include "hc2cbv.h") | $(ADD_DATE) | $(INDENT) >$@ -+# -+#endif # MAINTAINER_MODE -diff --git a/reodft/CMakeLists.txt b/reodft/CMakeLists.txt -new file mode 100644 -index 0000000..51d382b ---- /dev/null -+++ b/reodft/CMakeLists.txt -@@ -0,0 +1,12 @@ -+set(reodft_SRCS conf.c reodft010e-r2hc.c -+reodft11e-radix2.c reodft11e-r2hc-odd.c redft00e-r2hc-pad.c -+rodft00e-r2hc-pad.c reodft00e-splitradix.c -+# redft00e-r2hc.c rodft00e-r2hc.c reodft11e-r2hc.c -+) -+ -+if(BUILD_ALL_STATIC) -+ add_library(reodft STATIC ${reodft_SRCS}) -+else(BUILD_ALL_STATIC) -+ prepend_prefix(reodft_SRCS) -+ set(fftw_SRCS ${fftw_SRCS} ${reodft_SRCS} ${_fftw_SRCS} PARENT_SCOPE) -+endif(BUILD_ALL_STATIC) -diff --git a/simd/CMakeLists.txt b/simd/CMakeLists.txt -new file mode 100644 -index 0000000..713be75 ---- /dev/null -+++ b/simd/CMakeLists.txt -@@ -0,0 +1,10 @@ -+set(simd_SRCS altivec.c sse.c sse2.c taint.c mips_ps.c) -+ -+add_subdirectory(nonportable) -+ -+if(BUILD_ALL_STATIC) -+ add_library(simd STATIC ${simd_SRCS}) -+else(BUILD_ALL_STATIC) -+ prepend_prefix(simd_SRCS) -+ set(fftw_SRCS ${fftw_SRCS} ${simd_SRCS} ${_fftw_SRCS} PARENT_SCOPE) -+endif(BUILD_ALL_STATIC) -diff --git a/simd/nonportable/CMakeLists.txt b/simd/nonportable/CMakeLists.txt -new file mode 100644 -index 0000000..44b6e0b ---- /dev/null -+++ b/simd/nonportable/CMakeLists.txt -@@ -0,0 +1,8 @@ -+set(simd_nonportable_SRCS sse.c sse2.c) -+ -+if(BUILD_ALL_STATIC) -+ add_library(simd_nonportable STATIC ${simd_nonportable_SRCS}) -+else(BUILD_ALL_STATIC) -+ prepend_prefix(simd_nonportable_SRCS) -+ set(_fftw_SRCS ${_fftw_SRCS} ${simd_nonportable_SRCS} PARENT_SCOPE) -+endif(BUILD_ALL_STATIC) -diff --git a/simd/simd-sse.h b/simd/simd-sse.h -index 290ed7b..ed70027 100644 ---- a/simd/simd-sse.h -+++ b/simd/simd-sse.h -@@ -1,315 +1,315 @@ --/* -- * Copyright (c) 2003, 2007-8 Matteo Frigo -- * Copyright (c) 2003, 2007-8 Massachusetts Institute of Technology -- * -- * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -- * -- */ -- --#ifndef FFTW_SINGLE --#error "SSE only works in single precision" --#endif -- --#define VL 2 /* SIMD complex vector length */ --#define ALIGNMENT 8 /* alignment for LD/ST */ --#define ALIGNMENTA 16 /* alignment for LDA/STA */ --#define SIMD_VSTRIDE_OKA(x) ((x) == 2) --#define SIMD_STRIDE_OKPAIR SIMD_STRIDE_OK -- --#define RIGHT_CPU X(have_sse) --extern int RIGHT_CPU(void); -- --/* gcc compiles the following code only when __SSE__ is defined */ --#if defined(__SSE__) || !defined(__GNUC__) -- --/* some versions of glibc's sys/cdefs.h define __inline to be empty, -- which is wrong because xmmintrin.h defines several inline -- procedures */ --#undef __inline -- --#include -- --typedef __m128 V; --#define VADD _mm_add_ps --#define VSUB _mm_sub_ps --#define VMUL _mm_mul_ps --#define VXOR _mm_xor_ps --#define SHUFPS _mm_shuffle_ps --#define STOREH(addr, val) _mm_storeh_pi((__m64 *)(addr), val) --#define STOREL(addr, val) _mm_storel_pi((__m64 *)(addr), val) --#define UNPCKH _mm_unpackhi_ps --#define UNPCKL _mm_unpacklo_ps -- --#ifdef __GNUC__ --# define DVK(var, val) const V var = __extension__ ({ \ -- static const union fvec _var = { {val, val, val, val} }; \ -- _var.v; \ -- }) --# define LDK(x) x -- -- /* we use inline asm because gcc generates slow code for -- _mm_loadh_pi(). gcc insists upon having an existing variable for -- VAL, which is however never used. Thus, it generates code to move -- values in and out the variable. Worse still, gcc-4.0 stores VAL on -- the stack, causing valgrind to complain about uninitialized reads. -- */ -- -- static inline V LD(const R *x, INT ivs, const R *aligned_like) -- { -- V var; -- (void)aligned_like; /* UNUSED */ -- __asm__("movlps %1, %0\n\tmovhps %2, %0" -- : "=x"(var) : "m"(x[0]), "m"(x[ivs])); -- return var; -- } -- --#else -- --# define DVK(var, val) const R var = K(val) --# define LDK(x) _mm_set_ps1(x) --# define LOADH(addr, val) _mm_loadh_pi(val, (const __m64 *)(addr)) --# define LOADL0(addr, val) _mm_loadl_pi(val, (const __m64 *)(addr)) -- -- static inline V LD(const R *x, INT ivs, const R *aligned_like) -- { -- V var; -- (void)aligned_like; /* UNUSED */ -- var = LOADL0(x, var); -- var = LOADH(x + ivs, var); -- return var; -- } -- --#endif -- --union fvec { -- R f[4]; -- V v; --}; -- --union uvec { -- unsigned u[4]; -- V v; --}; -- --#define VFMA(a, b, c) VADD(c, VMUL(a, b)) --#define VFNMS(a, b, c) VSUB(c, VMUL(a, b)) --#define VFMS(a, b, c) VSUB(VMUL(a, b), c) -- --#define SHUFVAL(fp0,fp1,fp2,fp3) \ -- (((fp3) << 6) | ((fp2) << 4) | ((fp1) << 2) | ((fp0))) -- -- --static inline V LDA(const R *x, INT ivs, const R *aligned_like) --{ -- (void)aligned_like; /* UNUSED */ -- (void)ivs; /* UNUSED */ -- return *(const V *)x; --} -- --static inline void ST(R *x, V v, INT ovs, const R *aligned_like) --{ -- (void)aligned_like; /* UNUSED */ -- /* WARNING: the extra_iter hack depends upon STOREL occurring -- after STOREH */ -- STOREH(x + ovs, v); -- STOREL(x, v); --} -- --static inline void STA(R *x, V v, INT ovs, const R *aligned_like) --{ -- (void)aligned_like; /* UNUSED */ -- (void)ovs; /* UNUSED */ -- *(V *)x = v; --} -- --#if 0 --/* this should be faster but it isn't. */ --static inline void STN2(R *x, V v0, V v1, INT ovs) --{ -- STA(x, SHUFPS(v0, v1, SHUFVAL(0, 1, 0, 1)), ovs, 0); -- STA(x + ovs, SHUFPS(v0, v1, SHUFVAL(2, 3, 2, 3)), ovs, 0); --} --#endif --#define STM2 ST --#define STN2(x, v0, v1, ovs) /* nop */ -- --#define STM4(x, v, ovs, aligned_like) /* no-op */ -- --#ifdef VISUAL_CXX_DOES_NOT_SUCK --static inline void STN4(R *x, V v0, V v1, V v2, V v3, INT ovs) --{ -- V x0, x1, x2, x3; -- x0 = UNPCKL(v0, v2); -- x1 = UNPCKH(v0, v2); -- x2 = UNPCKL(v1, v3); -- x3 = UNPCKH(v1, v3); -- STA(x, UNPCKL(x0, x2), 0, 0); -- STA(x + ovs, UNPCKH(x0, x2), 0, 0); -- STA(x + 2 * ovs, UNPCKL(x1, x3), 0, 0); -- STA(x + 3 * ovs, UNPCKH(x1, x3), 0, 0); --} --#else /* Visual C++ sucks */ -- --/* -- Straight from the mouth of the horse: -- -- We "reserved" the possibility of aligning arguments with -- __declspec(align(X)) passed by value by issuing this error. -- -- The first 3 parameters of type __m64 (or other MMX types) are -- passed in registers. The rest would be passed on the stack. We -- decided aligning the stack was wasteful, especially for __m128 -- parameters. Also, we thought it would be infrequent that people -- would want to pass more than 3 by value. -- -- If we didn't issue an error, we would have to binary compatibility -- in the future if we decided to align the arguments. -- -- -- Hope that explains it. -- -- -- Jason Shirk, Visual C++ Compiler Team -- This posting is provided AS IS with no warranties, and confers no rights --*/ -- --#define STN4(x, v0, v1, v2, v3, ovs) \ --{ \ -- V xxx0, xxx1, xxx2, xxx3; \ -- xxx0 = UNPCKL(v0, v2); \ -- xxx1 = UNPCKH(v0, v2); \ -- xxx2 = UNPCKL(v1, v3); \ -- xxx3 = UNPCKH(v1, v3); \ -- STA(x, UNPCKL(xxx0, xxx2), 0, 0); \ -- STA(x + ovs, UNPCKH(xxx0, xxx2), 0, 0); \ -- STA(x + 2 * ovs, UNPCKL(xxx1, xxx3), 0, 0); \ -- STA(x + 3 * ovs, UNPCKH(xxx1, xxx3), 0, 0); \ --} --#endif -- --static inline V FLIP_RI(V x) --{ -- return SHUFPS(x, x, SHUFVAL(1, 0, 3, 2)); --} -- --extern const union uvec X(sse_pmpm); --static inline V VCONJ(V x) --{ -- return VXOR(X(sse_pmpm).v, x); --} -- --static inline V VBYI(V x) --{ -- return FLIP_RI(VCONJ(x)); --} -- --static inline V VZMUL(V tx, V sr) --{ -- V tr = SHUFPS(tx, tx, SHUFVAL(0, 0, 2, 2)); -- V ti = SHUFPS(tx, tx, SHUFVAL(1, 1, 3, 3)); -- tr = VMUL(tr, sr); -- sr = VBYI(sr); -- return VADD(tr, VMUL(ti, sr)); --} -- --static inline V VZMULJ(V tx, V sr) --{ -- V tr = SHUFPS(tx, tx, SHUFVAL(0, 0, 2, 2)); -- V ti = SHUFPS(tx, tx, SHUFVAL(1, 1, 3, 3)); -- tr = VMUL(tr, sr); -- sr = VBYI(sr); -- return VSUB(tr, VMUL(ti, sr)); --} -- --static inline V VZMULI(V tx, V sr) --{ -- V tr = SHUFPS(tx, tx, SHUFVAL(0, 0, 2, 2)); -- V ti = SHUFPS(tx, tx, SHUFVAL(1, 1, 3, 3)); -- ti = VMUL(ti, sr); -- sr = VBYI(sr); -- return VSUB(VMUL(tr, sr), ti); --} -- --static inline V VZMULIJ(V tx, V sr) --{ -- V tr = SHUFPS(tx, tx, SHUFVAL(0, 0, 2, 2)); -- V ti = SHUFPS(tx, tx, SHUFVAL(1, 1, 3, 3)); -- ti = VMUL(ti, sr); -- sr = VBYI(sr); -- return VADD(VMUL(tr, sr), ti); --} -- --#define VFMAI(b, c) VADD(c, VBYI(b)) --#define VFNMSI(b, c) VSUB(c, VBYI(b)) -- --/* twiddle storage #1: compact, slower */ --#define VTW1(v,x) \ -- {TW_COS, v, x}, {TW_COS, v+1, x}, {TW_SIN, v, x}, {TW_SIN, v+1, x} --#define TWVL1 (VL) -- --static inline V BYTW1(const R *t, V sr) --{ -- const V *twp = (const V *)t; -- V tx = twp[0]; -- V tr = UNPCKL(tx, tx); -- V ti = UNPCKH(tx, tx); -- tr = VMUL(tr, sr); -- sr = VBYI(sr); -- return VADD(tr, VMUL(ti, sr)); --} -- --static inline V BYTWJ1(const R *t, V sr) --{ -- const V *twp = (const V *)t; -- V tx = twp[0]; -- V tr = UNPCKL(tx, tx); -- V ti = UNPCKH(tx, tx); -- tr = VMUL(tr, sr); -- sr = VBYI(sr); -- return VSUB(tr, VMUL(ti, sr)); --} -- --/* twiddle storage #2: twice the space, faster (when in cache) */ --#define VTW2(v,x) \ -- {TW_COS, v, x}, {TW_COS, v, x}, {TW_COS, v+1, x}, {TW_COS, v+1, x}, \ -- {TW_SIN, v, -x}, {TW_SIN, v, x}, {TW_SIN, v+1, -x}, {TW_SIN, v+1, x} --#define TWVL2 (2 * VL) -- --static inline V BYTW2(const R *t, V sr) --{ -- const V *twp = (const V *)t; -- V si = FLIP_RI(sr); -- V tr = twp[0], ti = twp[1]; -- return VADD(VMUL(tr, sr), VMUL(ti, si)); --} -- --static inline V BYTWJ2(const R *t, V sr) --{ -- const V *twp = (const V *)t; -- V si = FLIP_RI(sr); -- V tr = twp[0], ti = twp[1]; -- return VSUB(VMUL(tr, sr), VMUL(ti, si)); --} -- --/* twiddle storage #3 */ --#define VTW3(v,x) {TW_CEXP, v, x}, {TW_CEXP, v+1, x} --#define TWVL3 (VL) -- --/* twiddle storage for split arrays */ --#define VTWS(v,x) \ -- {TW_COS, v, x}, {TW_COS, v+1, x}, {TW_COS, v+2, x}, {TW_COS, v+3, x}, \ -- {TW_SIN, v, x}, {TW_SIN, v+1, x}, {TW_SIN, v+2, x}, {TW_SIN, v+3, x} --#define TWVLS (2 * VL) -- --#endif /* __SSE__ */ -+/* -+ * Copyright (c) 2003, 2007-8 Matteo Frigo -+ * Copyright (c) 2003, 2007-8 Massachusetts Institute of Technology -+ * -+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -+ * -+ */ -+ -+#ifndef FFTW_SINGLE -+#error "SSE only works in single precision" -+#endif -+ -+#define VL 2 /* SIMD complex vector length */ -+#define ALIGNMENT 8 /* alignment for LD/ST */ -+#define ALIGNMENTA 16 /* alignment for LDA/STA */ -+#define SIMD_VSTRIDE_OKA(x) ((x) == 2) -+#define SIMD_STRIDE_OKPAIR SIMD_STRIDE_OK -+ -+#define RIGHT_CPU X(have_sse) -+extern int RIGHT_CPU(void); -+ -+/* gcc compiles the following code only when __SSE__ is defined */ -+#if defined(__SSE__) || !defined(__GNUC__) -+ -+/* some versions of glibc's sys/cdefs.h define __inline to be empty, -+ which is wrong because xmmintrin.h defines several inline -+ procedures */ -+#undef __inline -+ -+#include -+ -+typedef __m128 V; -+#define VADD _mm_add_ps -+#define VSUB _mm_sub_ps -+#define VMUL _mm_mul_ps -+#define VXOR _mm_xor_ps -+#define SHUFPS _mm_shuffle_ps -+#define STOREH(addr, val) _mm_storeh_pi((__m64 *)(addr), val) -+#define STOREL(addr, val) _mm_storel_pi((__m64 *)(addr), val) -+#define UNPCKH _mm_unpackhi_ps -+#define UNPCKL _mm_unpacklo_ps -+ -+#ifdef __GNUC__ -+# define DVK(var, val) const V var = __extension__ ({ \ -+ static const union fvec _var = { {val, val, val, val} }; \ -+ _var.v; \ -+ }) -+# define LDK(x) x -+ -+ /* we use inline asm because gcc generates slow code for -+ _mm_loadh_pi(). gcc insists upon having an existing variable for -+ VAL, which is however never used. Thus, it generates code to move -+ values in and out the variable. Worse still, gcc-4.0 stores VAL on -+ the stack, causing valgrind to complain about uninitialized reads. -+ */ -+ -+ static inline V LD(const R *x, INT ivs, const R *aligned_like) -+ { -+ V var; -+ (void)aligned_like; /* UNUSED */ -+ __asm__("movlps %1, %0\n\tmovhps %2, %0" -+ : "=x"(var) : "m"(x[0]), "m"(x[ivs])); -+ return var; -+ } -+ -+#else -+ -+# define DVK(var, val) const R var = K(val) -+# define LDK(x) _mm_set_ps1(x) -+# define LOADH(addr, val) _mm_loadh_pi(val, (const __m64 *)(addr)) -+# define LOADL0(addr, val) _mm_loadl_pi(val, (const __m64 *)(addr)) -+ -+ static V LD(const R *x, INT ivs, const R *aligned_like) -+ { -+ V var; -+ (void)aligned_like; /* UNUSED */ -+ var = LOADL0(x, var); -+ var = LOADH(x + ivs, var); -+ return var; -+ } -+ -+#endif -+ -+union fvec { -+ R f[4]; -+ V v; -+}; -+ -+union uvec { -+ unsigned u[4]; -+ V v; -+}; -+ -+#define VFMA(a, b, c) VADD(c, VMUL(a, b)) -+#define VFNMS(a, b, c) VSUB(c, VMUL(a, b)) -+#define VFMS(a, b, c) VSUB(VMUL(a, b), c) -+ -+#define SHUFVAL(fp0,fp1,fp2,fp3) \ -+ (((fp3) << 6) | ((fp2) << 4) | ((fp1) << 2) | ((fp0))) -+ -+ -+static V LDA(const R *x, INT ivs, const R *aligned_like) -+{ -+ (void)aligned_like; /* UNUSED */ -+ (void)ivs; /* UNUSED */ -+ return *(const V *)x; -+} -+ -+static void ST(R *x, V v, INT ovs, const R *aligned_like) -+{ -+ (void)aligned_like; /* UNUSED */ -+ /* WARNING: the extra_iter hack depends upon STOREL occurring -+ after STOREH */ -+ STOREH(x + ovs, v); -+ STOREL(x, v); -+} -+ -+static void STA(R *x, V v, INT ovs, const R *aligned_like) -+{ -+ (void)aligned_like; /* UNUSED */ -+ (void)ovs; /* UNUSED */ -+ *(V *)x = v; -+} -+ -+#if 0 -+/* this should be faster but it isn't. */ -+static void STN2(R *x, V v0, V v1, INT ovs) -+{ -+ STA(x, SHUFPS(v0, v1, SHUFVAL(0, 1, 0, 1)), ovs, 0); -+ STA(x + ovs, SHUFPS(v0, v1, SHUFVAL(2, 3, 2, 3)), ovs, 0); -+} -+#endif -+#define STM2 ST -+#define STN2(x, v0, v1, ovs) /* nop */ -+ -+#define STM4(x, v, ovs, aligned_like) /* no-op */ -+ -+#ifdef VISUAL_CXX_DOES_NOT_SUCK -+static void STN4(R *x, V v0, V v1, V v2, V v3, INT ovs) -+{ -+ V x0, x1, x2, x3; -+ x0 = UNPCKL(v0, v2); -+ x1 = UNPCKH(v0, v2); -+ x2 = UNPCKL(v1, v3); -+ x3 = UNPCKH(v1, v3); -+ STA(x, UNPCKL(x0, x2), 0, 0); -+ STA(x + ovs, UNPCKH(x0, x2), 0, 0); -+ STA(x + 2 * ovs, UNPCKL(x1, x3), 0, 0); -+ STA(x + 3 * ovs, UNPCKH(x1, x3), 0, 0); -+} -+#else /* Visual C++ sucks */ -+ -+/* -+ Straight from the mouth of the horse: -+ -+ We "reserved" the possibility of aligning arguments with -+ __declspec(align(X)) passed by value by issuing this error. -+ -+ The first 3 parameters of type __m64 (or other MMX types) are -+ passed in registers. The rest would be passed on the stack. We -+ decided aligning the stack was wasteful, especially for __m128 -+ parameters. Also, we thought it would be infrequent that people -+ would want to pass more than 3 by value. -+ -+ If we didn't issue an error, we would have to binary compatibility -+ in the future if we decided to align the arguments. -+ -+ -+ Hope that explains it. -+ -- -+ Jason Shirk, Visual C++ Compiler Team -+ This posting is provided AS IS with no warranties, and confers no rights -+*/ -+ -+#define STN4(x, v0, v1, v2, v3, ovs) \ -+{ \ -+ V xxx0, xxx1, xxx2, xxx3; \ -+ xxx0 = UNPCKL(v0, v2); \ -+ xxx1 = UNPCKH(v0, v2); \ -+ xxx2 = UNPCKL(v1, v3); \ -+ xxx3 = UNPCKH(v1, v3); \ -+ STA(x, UNPCKL(xxx0, xxx2), 0, 0); \ -+ STA(x + ovs, UNPCKH(xxx0, xxx2), 0, 0); \ -+ STA(x + 2 * ovs, UNPCKL(xxx1, xxx3), 0, 0); \ -+ STA(x + 3 * ovs, UNPCKH(xxx1, xxx3), 0, 0); \ -+} -+#endif -+ -+static V FLIP_RI(V x) -+{ -+ return SHUFPS(x, x, SHUFVAL(1, 0, 3, 2)); -+} -+ -+extern const union uvec X(sse_pmpm); -+static V VCONJ(V x) -+{ -+ return VXOR(X(sse_pmpm).v, x); -+} -+ -+static V VBYI(V x) -+{ -+ return FLIP_RI(VCONJ(x)); -+} -+ -+static V VZMUL(V tx, V sr) -+{ -+ V tr = SHUFPS(tx, tx, SHUFVAL(0, 0, 2, 2)); -+ V ti = SHUFPS(tx, tx, SHUFVAL(1, 1, 3, 3)); -+ tr = VMUL(tr, sr); -+ sr = VBYI(sr); -+ return VADD(tr, VMUL(ti, sr)); -+} -+ -+static V VZMULJ(V tx, V sr) -+{ -+ V tr = SHUFPS(tx, tx, SHUFVAL(0, 0, 2, 2)); -+ V ti = SHUFPS(tx, tx, SHUFVAL(1, 1, 3, 3)); -+ tr = VMUL(tr, sr); -+ sr = VBYI(sr); -+ return VSUB(tr, VMUL(ti, sr)); -+} -+ -+static V VZMULI(V tx, V sr) -+{ -+ V tr = SHUFPS(tx, tx, SHUFVAL(0, 0, 2, 2)); -+ V ti = SHUFPS(tx, tx, SHUFVAL(1, 1, 3, 3)); -+ ti = VMUL(ti, sr); -+ sr = VBYI(sr); -+ return VSUB(VMUL(tr, sr), ti); -+} -+ -+static V VZMULIJ(V tx, V sr) -+{ -+ V tr = SHUFPS(tx, tx, SHUFVAL(0, 0, 2, 2)); -+ V ti = SHUFPS(tx, tx, SHUFVAL(1, 1, 3, 3)); -+ ti = VMUL(ti, sr); -+ sr = VBYI(sr); -+ return VADD(VMUL(tr, sr), ti); -+} -+ -+#define VFMAI(b, c) VADD(c, VBYI(b)) -+#define VFNMSI(b, c) VSUB(c, VBYI(b)) -+ -+/* twiddle storage #1: compact, slower */ -+#define VTW1(v,x) \ -+ {TW_COS, v, x}, {TW_COS, v+1, x}, {TW_SIN, v, x}, {TW_SIN, v+1, x} -+#define TWVL1 (VL) -+ -+static V BYTW1(const R *t, V sr) -+{ -+ const V *twp = (const V *)t; -+ V tx = twp[0]; -+ V tr = UNPCKL(tx, tx); -+ V ti = UNPCKH(tx, tx); -+ tr = VMUL(tr, sr); -+ sr = VBYI(sr); -+ return VADD(tr, VMUL(ti, sr)); -+} -+ -+static V BYTWJ1(const R *t, V sr) -+{ -+ const V *twp = (const V *)t; -+ V tx = twp[0]; -+ V tr = UNPCKL(tx, tx); -+ V ti = UNPCKH(tx, tx); -+ tr = VMUL(tr, sr); -+ sr = VBYI(sr); -+ return VSUB(tr, VMUL(ti, sr)); -+} -+ -+/* twiddle storage #2: twice the space, faster (when in cache) */ -+#define VTW2(v,x) \ -+ {TW_COS, v, x}, {TW_COS, v, x}, {TW_COS, v+1, x}, {TW_COS, v+1, x}, \ -+ {TW_SIN, v, -x}, {TW_SIN, v, x}, {TW_SIN, v+1, -x}, {TW_SIN, v+1, x} -+#define TWVL2 (2 * VL) -+ -+static V BYTW2(const R *t, V sr) -+{ -+ const V *twp = (const V *)t; -+ V si = FLIP_RI(sr); -+ V tr = twp[0], ti = twp[1]; -+ return VADD(VMUL(tr, sr), VMUL(ti, si)); -+} -+ -+static V BYTWJ2(const R *t, V sr) -+{ -+ const V *twp = (const V *)t; -+ V si = FLIP_RI(sr); -+ V tr = twp[0], ti = twp[1]; -+ return VSUB(VMUL(tr, sr), VMUL(ti, si)); -+} -+ -+/* twiddle storage #3 */ -+#define VTW3(v,x) {TW_CEXP, v, x}, {TW_CEXP, v+1, x} -+#define TWVL3 (VL) -+ -+/* twiddle storage for split arrays */ -+#define VTWS(v,x) \ -+ {TW_COS, v, x}, {TW_COS, v+1, x}, {TW_COS, v+2, x}, {TW_COS, v+3, x}, \ -+ {TW_SIN, v, x}, {TW_SIN, v+1, x}, {TW_SIN, v+2, x}, {TW_SIN, v+3, x} -+#define TWVLS (2 * VL) -+ -+#endif /* __SSE__ */ -diff --git a/simd/simd-sse.h.bak b/simd/simd-sse.h.bak -new file mode 100644 -index 0000000..99ef9da ---- /dev/null -+++ b/simd/simd-sse.h.bak -@@ -0,0 +1,319 @@ -+/* -+ * Copyright (c) 2003, 2007-8 Matteo Frigo -+ * Copyright (c) 2003, 2007-8 Massachusetts Institute of Technology -+ * -+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -+ * -+ */ -+ -+#ifndef FFTW_SINGLE -+#error "SSE only works in single precision" -+#endif -+ -+#define VL 2 /* SIMD complex vector length */ -+#define ALIGNMENT 8 /* alignment for LD/ST */ -+#define ALIGNMENTA 16 /* alignment for LDA/STA */ -+#define SIMD_VSTRIDE_OKA(x) ((x) == 2) -+#define SIMD_STRIDE_OKPAIR SIMD_STRIDE_OK -+ -+#define RIGHT_CPU X(have_sse) -+extern int RIGHT_CPU(void); -+ -+/* gcc compiles the following code only when __SSE__ is defined */ -+#if defined(__SSE__) || !defined(__GNUC__) -+ -+/* some versions of glibc's sys/cdefs.h define __inline to be empty, -+ which is wrong because xmmintrin.h defines several inline -+ procedures */ -+#undef __inline -+ -+#include -+ -+typedef __m128 V; -+#define VADD _mm_add_ps -+#define VSUB _mm_sub_ps -+#define VMUL _mm_mul_ps -+#define VXOR _mm_xor_ps -+#define SHUFPS _mm_shuffle_ps -+#define STOREH(addr, val) _mm_storeh_pi((__m64 *)(addr), val) -+#define STOREL(addr, val) _mm_storel_pi((__m64 *)(addr), val) -+#define UNPCKH _mm_unpackhi_ps -+#define UNPCKL _mm_unpacklo_ps -+ -+#ifdef __GNUC__ -+# define DVK(var, val) const V var = __extension__ ({ \ -+ static const union fvec _var = { {val, val, val, val} }; \ -+ _var.v; \ -+ }) -+# define LDK(x) x -+ -+ /* we use inline asm because gcc generates slow code for -+ _mm_loadh_pi(). gcc insists upon having an existing variable for -+ VAL, which is however never used. Thus, it generates code to move -+ values in and out the variable. Worse still, gcc-4.0 stores VAL on -+ the stack, causing valgrind to complain about uninitialized reads. -+ */ -+ -+ static inline V LD(const R *x, INT ivs, const R *aligned_like) -+ { -+ V var; -+ (void)aligned_like; /* UNUSED */ -+ __asm__("movlps %1, %0\n\tmovhps %2, %0" -+ : "=x"(var) : "m"(x[0]), "m"(x[ivs])); -+ return var; -+ } -+ -+#else -+ -+# define DVK(var, val) const R var = K(val) -+# define LDK(x) _mm_set_ps1(x) -+# define LOADH(addr, val) _mm_loadh_pi(val, (const __m64 *)(addr)) -+# define LOADL0(addr, val) _mm_loadl_pi(val, (const __m64 *)(addr)) -+ -+#ifndef __INTEL_COMPILER -+ static inline V LD(const R *x, INT ivs, const R *aligned_like) -+#else -+ static V LD(const R *x, INT ivs, const R *aligned_like) -+#endif -+ { -+ V var; -+ (void)aligned_like; /* UNUSED */ -+ var = LOADL0(x, var); -+ var = LOADH(x + ivs, var); -+ return var; -+ } -+ -+#endif -+ -+union fvec { -+ R f[4]; -+ V v; -+}; -+ -+union uvec { -+ unsigned u[4]; -+ V v; -+}; -+ -+#define VFMA(a, b, c) VADD(c, VMUL(a, b)) -+#define VFNMS(a, b, c) VSUB(c, VMUL(a, b)) -+#define VFMS(a, b, c) VSUB(VMUL(a, b), c) -+ -+#define SHUFVAL(fp0,fp1,fp2,fp3) \ -+ (((fp3) << 6) | ((fp2) << 4) | ((fp1) << 2) | ((fp0))) -+ -+ -+static inline V LDA(const R *x, INT ivs, const R *aligned_like) -+{ -+ (void)aligned_like; /* UNUSED */ -+ (void)ivs; /* UNUSED */ -+ return *(const V *)x; -+} -+ -+static inline void ST(R *x, V v, INT ovs, const R *aligned_like) -+{ -+ (void)aligned_like; /* UNUSED */ -+ /* WARNING: the extra_iter hack depends upon STOREL occurring -+ after STOREH */ -+ STOREH(x + ovs, v); -+ STOREL(x, v); -+} -+ -+static inline void STA(R *x, V v, INT ovs, const R *aligned_like) -+{ -+ (void)aligned_like; /* UNUSED */ -+ (void)ovs; /* UNUSED */ -+ *(V *)x = v; -+} -+ -+#if 0 -+/* this should be faster but it isn't. */ -+static inline void STN2(R *x, V v0, V v1, INT ovs) -+{ -+ STA(x, SHUFPS(v0, v1, SHUFVAL(0, 1, 0, 1)), ovs, 0); -+ STA(x + ovs, SHUFPS(v0, v1, SHUFVAL(2, 3, 2, 3)), ovs, 0); -+} -+#endif -+#define STM2 ST -+#define STN2(x, v0, v1, ovs) /* nop */ -+ -+#define STM4(x, v, ovs, aligned_like) /* no-op */ -+ -+#ifdef VISUAL_CXX_DOES_NOT_SUCK -+static inline void STN4(R *x, V v0, V v1, V v2, V v3, INT ovs) -+{ -+ V x0, x1, x2, x3; -+ x0 = UNPCKL(v0, v2); -+ x1 = UNPCKH(v0, v2); -+ x2 = UNPCKL(v1, v3); -+ x3 = UNPCKH(v1, v3); -+ STA(x, UNPCKL(x0, x2), 0, 0); -+ STA(x + ovs, UNPCKH(x0, x2), 0, 0); -+ STA(x + 2 * ovs, UNPCKL(x1, x3), 0, 0); -+ STA(x + 3 * ovs, UNPCKH(x1, x3), 0, 0); -+} -+#else /* Visual C++ sucks */ -+ -+/* -+ Straight from the mouth of the horse: -+ -+ We "reserved" the possibility of aligning arguments with -+ __declspec(align(X)) passed by value by issuing this error. -+ -+ The first 3 parameters of type __m64 (or other MMX types) are -+ passed in registers. The rest would be passed on the stack. We -+ decided aligning the stack was wasteful, especially for __m128 -+ parameters. Also, we thought it would be infrequent that people -+ would want to pass more than 3 by value. -+ -+ If we didn't issue an error, we would have to binary compatibility -+ in the future if we decided to align the arguments. -+ -+ -+ Hope that explains it. -+ -- -+ Jason Shirk, Visual C++ Compiler Team -+ This posting is provided AS IS with no warranties, and confers no rights -+*/ -+ -+#define STN4(x, v0, v1, v2, v3, ovs) \ -+{ \ -+ V xxx0, xxx1, xxx2, xxx3; \ -+ xxx0 = UNPCKL(v0, v2); \ -+ xxx1 = UNPCKH(v0, v2); \ -+ xxx2 = UNPCKL(v1, v3); \ -+ xxx3 = UNPCKH(v1, v3); \ -+ STA(x, UNPCKL(xxx0, xxx2), 0, 0); \ -+ STA(x + ovs, UNPCKH(xxx0, xxx2), 0, 0); \ -+ STA(x + 2 * ovs, UNPCKL(xxx1, xxx3), 0, 0); \ -+ STA(x + 3 * ovs, UNPCKH(xxx1, xxx3), 0, 0); \ -+} -+#endif -+ -+static inline V FLIP_RI(V x) -+{ -+ return SHUFPS(x, x, SHUFVAL(1, 0, 3, 2)); -+} -+ -+extern const union uvec X(sse_pmpm); -+static inline V VCONJ(V x) -+{ -+ return VXOR(X(sse_pmpm).v, x); -+} -+ -+static inline V VBYI(V x) -+{ -+ return FLIP_RI(VCONJ(x)); -+} -+ -+static inline V VZMUL(V tx, V sr) -+{ -+ V tr = SHUFPS(tx, tx, SHUFVAL(0, 0, 2, 2)); -+ V ti = SHUFPS(tx, tx, SHUFVAL(1, 1, 3, 3)); -+ tr = VMUL(tr, sr); -+ sr = VBYI(sr); -+ return VADD(tr, VMUL(ti, sr)); -+} -+ -+static inline V VZMULJ(V tx, V sr) -+{ -+ V tr = SHUFPS(tx, tx, SHUFVAL(0, 0, 2, 2)); -+ V ti = SHUFPS(tx, tx, SHUFVAL(1, 1, 3, 3)); -+ tr = VMUL(tr, sr); -+ sr = VBYI(sr); -+ return VSUB(tr, VMUL(ti, sr)); -+} -+ -+static inline V VZMULI(V tx, V sr) -+{ -+ V tr = SHUFPS(tx, tx, SHUFVAL(0, 0, 2, 2)); -+ V ti = SHUFPS(tx, tx, SHUFVAL(1, 1, 3, 3)); -+ ti = VMUL(ti, sr); -+ sr = VBYI(sr); -+ return VSUB(VMUL(tr, sr), ti); -+} -+ -+static inline V VZMULIJ(V tx, V sr) -+{ -+ V tr = SHUFPS(tx, tx, SHUFVAL(0, 0, 2, 2)); -+ V ti = SHUFPS(tx, tx, SHUFVAL(1, 1, 3, 3)); -+ ti = VMUL(ti, sr); -+ sr = VBYI(sr); -+ return VADD(VMUL(tr, sr), ti); -+} -+ -+#define VFMAI(b, c) VADD(c, VBYI(b)) -+#define VFNMSI(b, c) VSUB(c, VBYI(b)) -+ -+/* twiddle storage #1: compact, slower */ -+#define VTW1(v,x) \ -+ {TW_COS, v, x}, {TW_COS, v+1, x}, {TW_SIN, v, x}, {TW_SIN, v+1, x} -+#define TWVL1 (VL) -+ -+static inline V BYTW1(const R *t, V sr) -+{ -+ const V *twp = (const V *)t; -+ V tx = twp[0]; -+ V tr = UNPCKL(tx, tx); -+ V ti = UNPCKH(tx, tx); -+ tr = VMUL(tr, sr); -+ sr = VBYI(sr); -+ return VADD(tr, VMUL(ti, sr)); -+} -+ -+static inline V BYTWJ1(const R *t, V sr) -+{ -+ const V *twp = (const V *)t; -+ V tx = twp[0]; -+ V tr = UNPCKL(tx, tx); -+ V ti = UNPCKH(tx, tx); -+ tr = VMUL(tr, sr); -+ sr = VBYI(sr); -+ return VSUB(tr, VMUL(ti, sr)); -+} -+ -+/* twiddle storage #2: twice the space, faster (when in cache) */ -+#define VTW2(v,x) \ -+ {TW_COS, v, x}, {TW_COS, v, x}, {TW_COS, v+1, x}, {TW_COS, v+1, x}, \ -+ {TW_SIN, v, -x}, {TW_SIN, v, x}, {TW_SIN, v+1, -x}, {TW_SIN, v+1, x} -+#define TWVL2 (2 * VL) -+ -+static inline V BYTW2(const R *t, V sr) -+{ -+ const V *twp = (const V *)t; -+ V si = FLIP_RI(sr); -+ V tr = twp[0], ti = twp[1]; -+ return VADD(VMUL(tr, sr), VMUL(ti, si)); -+} -+ -+static inline V BYTWJ2(const R *t, V sr) -+{ -+ const V *twp = (const V *)t; -+ V si = FLIP_RI(sr); -+ V tr = twp[0], ti = twp[1]; -+ return VSUB(VMUL(tr, sr), VMUL(ti, si)); -+} -+ -+/* twiddle storage #3 */ -+#define VTW3(v,x) {TW_CEXP, v, x}, {TW_CEXP, v+1, x} -+#define TWVL3 (VL) -+ -+/* twiddle storage for split arrays */ -+#define VTWS(v,x) \ -+ {TW_COS, v, x}, {TW_COS, v+1, x}, {TW_COS, v+2, x}, {TW_COS, v+3, x}, \ -+ {TW_SIN, v, x}, {TW_SIN, v+1, x}, {TW_SIN, v+2, x}, {TW_SIN, v+3, x} -+#define TWVLS (2 * VL) -+ -+#endif /* __SSE__ */ -diff --git a/support/codelets.cmake b/support/codelets.cmake -new file mode 100644 -index 0000000..5161407 ---- /dev/null -+++ b/support/codelets.cmake -@@ -0,0 +1,18 @@ -+# generate file codlist.c -+ -+macro(write_codelet_list CODLIST CODELET_NAME SOLVTAB_NAME) -+ file(RELATIVE_PATH filename ${CMAKE_BINARY_DIR} ${CODLIST}) -+ message(STATUS "writing codelet list file ${filename}") -+ file(WRITE ${CODLIST} "#include \"ifftw.h\"\n\n") -+ foreach(codelet ${ARGN}) -+ string(REGEX REPLACE "([A-Za-z0-9_]*)\\.c" "\\1" codeletbase "${codelet}") -+ file(APPEND ${CODLIST} "extern void X(${CODELET_NAME}${codeletbase})(planner *);\n") -+ endforeach(codelet ${ARGN}) -+ file(APPEND ${CODLIST} "\n\nextern const solvtab ${SOLVTAB_NAME};\n") -+ file(APPEND ${CODLIST} "const solvtab ${SOLVTAB_NAME} = {\n") -+ foreach(codelet ${ARGN}) -+ string(REGEX REPLACE "([A-Za-z0-9_]*)\\.c" "\\1" codeletbase "${codelet}") -+ file(APPEND ${CODLIST} " SOLVTAB(X(${CODELET_NAME}${codeletbase})),\n") -+ endforeach(codelet ${ARGN}) -+ file(APPEND ${CODLIST} " SOLVTAB_END\n};\n") -+endmacro(write_codelet_list CODLIST CODELET_NAME SOLVTAB_NAME) -\ No newline at end of file -diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt -new file mode 100644 -index 0000000..c5ef717 ---- /dev/null -+++ b/tests/CMakeLists.txt -@@ -0,0 +1,71 @@ -+#noinst_PROGRAMS = bench -+#EXTRA_DIST = check.pl README -+ -+#if SMP -+#if !COMBINED_THREADS -+#LIBFFTWTHREADS = $(top_builddir)/threads/libfftw3@PREC_SUFFIX@_threads.la -+#endif -+#else -+#LIBFFTWTHREADS = -+#endif -+ -+set(bench_SRCS bench.c hook.c fftw-bench.c) -+add_executable(bench ${bench_SRCS}) -+target_link_libraries(bench fftw bench2 fftw) -+ -+#check-local: bench$(EXEEXT) -+# perl -w $(srcdir)/check.pl -r -c=30 -v `pwd`/bench -+# @echo "--------------------------------------------------------------" -+# @echo " FFTW transforms passed basic tests!" -+# @echo "--------------------------------------------------------------" -+#if SMP -+# perl -w $(srcdir)/check.pl -r -c=30 -v --nthreads=2 `pwd`/bench -+# @echo "--------------------------------------------------------------" -+# @echo " FFTW threaded transforms passed basic tests!" -+# @echo "--------------------------------------------------------------" -+#endif -+# -+#bigcheck: bench$(EXEEXT) -+# perl -w $(srcdir)/check.pl -a -v `pwd`/bench -+# @echo "--------------------------------------------------------------" -+# @echo " FFTW transforms passed big tests!" -+# @echo "--------------------------------------------------------------" -+#if SMP -+# perl -w $(srcdir)/check.pl -a -v --nthreads=2 `pwd`/bench -+# perl -w $(srcdir)/check.pl -a -v --nthreads=3 `pwd`/bench -+# perl -w $(srcdir)/check.pl -a -v --nthreads=10 `pwd`/bench -+# @echo "--------------------------------------------------------------" -+# @echo " FFTW threaded transforms passed big tests!" -+# @echo "--------------------------------------------------------------" -+#endif -+# -+#smallcheck: bench$(EXEEXT) -+# perl -w $(srcdir)/check.pl -r -c=1 -v `pwd`/bench -+# perl -w $(srcdir)/check.pl -r --estimate -c=5 -v `pwd`/bench -+# @echo "--------------------------------------------------------------" -+# @echo " FFTW transforms passed a few tests!" -+# @echo "--------------------------------------------------------------" -+#if SMP -+# perl -w $(srcdir)/check.pl -r --estimate -c=2 -v --nthreads=2 `pwd`/bench -+# @echo "--------------------------------------------------------------" -+# @echo " FFTW threaded transforms passed a few tests!" -+# @echo "--------------------------------------------------------------" -+#endif -+# -+#paranoid-check: bench$(EXEEXT) -+#if SMP -+# perl -w $(srcdir)/check.pl -a --patient --nthreads=10 --paranoid `pwd`/bench -+# perl -w $(srcdir)/check.pl -a --patient --nthreads=7 --paranoid `pwd`/bench -+# perl -w $(srcdir)/check.pl -a --patient --nthreads=3 --paranoid `pwd`/bench -+# perl -w $(srcdir)/check.pl -a --patient --nthreads=2 --paranoid `pwd`/bench -+#endif -+# perl -w $(srcdir)/check.pl -a --patient --paranoid `pwd`/bench -+# -+#exhaustive-check: bench$(EXEEXT) -+#if SMP -+# perl -w $(srcdir)/check.pl -a --exhaustive --nthreads=10 --paranoid `pwd`/bench -+# perl -w $(srcdir)/check.pl -a --exhaustive --nthreads=7 --paranoid `pwd`/bench -+# perl -w $(srcdir)/check.pl -a --exhaustive --nthreads=3 --paranoid `pwd`/bench -+# perl -w $(srcdir)/check.pl -a --exhaustive --nthreads=2 --paranoid `pwd`/bench -+#endif -+# perl -w $(srcdir)/check.pl -a --exhaustive --paranoid `pwd`/bench -diff --git a/threads/CMakeLists.txt b/threads/CMakeLists.txt -new file mode 100644 -index 0000000..42f9211 ---- /dev/null -+++ b/threads/CMakeLists.txt -@@ -0,0 +1,9 @@ -+set(threads_SRCS api.c conf.c threads.c openmp.c dft-vrank-geq1.c ct.c rdft-vrank-geq1.c hc2hc.c vrank-geq1-rdft2.c f77api.c -+) -+ -+if(BUILD_ALL_STATIC) -+ add_library(threads STATIC ${threads_SRCS}) -+else(BUILD_ALL_STATIC) -+ prepend_prefix(threads_SRCS) -+ set(fftw_SRCS ${fftw_SRCS} ${threads_SRCS} PARENT_SCOPE) -+endif(BUILD_ALL_STATIC) -\ No newline at end of file -diff --git a/threads/api.c b/threads/api.c -index 8b17ea9..1042d60 100644 ---- a/threads/api.c -+++ b/threads/api.c -@@ -1,81 +1,80 @@ --/* -- * Copyright (c) 2003, 2007-8 Matteo Frigo -- * Copyright (c) 2003, 2007-8 Massachusetts Institute of Technology -- * -- * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -- * -- */ -- --#include "api.h" --#include "threads.h" -- --static int threads_inited = 0; -- --static void threads_register_hooks(void) --{ -- X(mksolver_ct_hook) = X(mksolver_ct_threads); -- X(mksolver_hc2hc_hook) = X(mksolver_hc2hc_threads); --} -- --static void threads_unregister_hooks(void) --{ -- X(mksolver_ct_hook) = 0; -- X(mksolver_hc2hc_hook) = 0; --} -- --/* should be called before all other FFTW functions! */ --int X(init_threads)(void) --{ -- if (!threads_inited) { -- planner *plnr; -- -- if (X(ithreads_init)()) -- return 0; -- -- threads_register_hooks(); -- -- /* this should be the first time the_planner is called, -- and hence the time it is configured */ -- plnr = X(the_planner)(); -- X(threads_conf_standard)(plnr); -- -- threads_inited = 1; -- } -- return 1; --} -- -- --void X(cleanup_threads)(void) --{ -- X(cleanup)(); -- if (threads_inited) { -- X(threads_cleanup)(); -- threads_unregister_hooks(); -- threads_inited = 0; -- } --} -- --void X(plan_with_nthreads)(int nthreads) --{ -- planner *plnr; -- -- if (!threads_inited) { -- X(cleanup)(); -- X(init_threads)(); -- } -- A(threads_inited); -- plnr = X(the_planner)(); -- plnr->nthr = X(imax)(1, nthreads); --} -+/* -+ * Copyright (c) 2003, 2007-8 Matteo Frigo -+ * Copyright (c) 2003, 2007-8 Massachusetts Institute of Technology -+ * -+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -+ * -+ */ -+ -+#include "api.h" -+#include "threads.h" -+ -+static int threads_inited = 0; -+ -+static void threads_register_hooks(void) -+{ -+ X(mksolver_ct_hook) = X(mksolver_ct_threads); -+ X(mksolver_hc2hc_hook) = X(mksolver_hc2hc_threads); -+} -+ -+static void threads_unregister_hooks(void) -+{ -+ X(mksolver_ct_hook) = 0; -+ X(mksolver_hc2hc_hook) = 0; -+} -+ -+/* should be called before all other FFTW functions! */ -+int X(init_threads)(void) -+{ -+ if (!threads_inited) { -+ planner *plnr; -+ -+ if (X(ithreads_init)()) -+ return 0; -+ -+ threads_register_hooks(); -+ -+ /* this should be the first time the_planner is called, -+ and hence the time it is configured */ -+ plnr = X(the_planner)(); -+ X(threads_conf_standard)(plnr); -+ -+ threads_inited = 1; -+ } -+ return 1; -+} -+ -+void X(cleanup_threads)(void) -+{ -+ X(cleanup)(); -+ if (threads_inited) { -+ X(threads_cleanup)(); -+ threads_unregister_hooks(); -+ threads_inited = 0; -+ } -+} -+ -+void X(plan_with_nthreads)(int nthreads) -+{ -+ planner *plnr; -+ -+ if (!threads_inited) { -+ X(cleanup)(); -+ X(init_threads)(); -+ } -+ A(threads_inited); -+ plnr = X(the_planner)(); -+ plnr->nthr = X(imax)(1, nthreads); -+} diff --git a/3rdparty/ext_fftw3/libfftw.py b/3rdparty/ext_fftw3/libfftw.py deleted file mode 100644 index bfc29a3da3..0000000000 --- a/3rdparty/ext_fftw3/libfftw.py +++ /dev/null @@ -1,28 +0,0 @@ -# -*- coding: utf-8 -*- -import info - - -class subinfo(info.infoclass): - - def setTargets(self): - self.targets['3.2.2'] = 'http://www.fftw.org/fftw-3.2.2.tar.gz' - self.targetDigests['3.2.2'] = 'd43b799eedfb9408f62f9f056f5e8a645618467b' - self.targetInstSrc['3.2.2'] = "fftw-3.2.2" - self.patchToApply['3.2.2'] = [('fftw-3.2.2-20111221.diff', 1), - ('fftw-3.2.2-20130818.diff', 1)] - self.shortDescription = "a C subroutine library for computing the discrete Fourier transform (DFT)" - - self.defaultTarget = '3.2.2' - - def setDependencies(self): - self.buildDependencies['virtual/base'] = 'default' - -from Package.CMakePackageBase import * - - -class Package(CMakePackageBase): - - def __init__(self): - CMakePackageBase.__init__(self) - self.supportsNinja = False - self.subinfo.options.configure.defines = "-DFFTW_SINGLE=ON -DFFTW_DOUBLE=OFF -DBUILD_BENCHMARKS=OFF" diff --git a/3rdparty/ext_gsl/CMakeLists.txt b/3rdparty/ext_gsl/CMakeLists.txt index 056f34a45c..3bd8182f47 100644 --- a/3rdparty/ext_gsl/CMakeLists.txt +++ b/3rdparty/ext_gsl/CMakeLists.txt @@ -1,15 +1,30 @@ # # From https://github.com/ampl/gsl. which adds CMake support to gsl # +if (ANDROID) SET(EXTPREFIX_gsl "${EXTPREFIX}" ) ExternalProject_Add( ext_gsl DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL http://files.kde.org/krita/build/dependencies/gsl-2.3.0.tar.gz URL_MD5 7e0478f7c5e62696fef480b9a46f708c PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/gsl-android.patch INSTALL_DIR ${EXTPREFIX_gsl} CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${EXTPREFIX_gsl} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DBUILD_SHARED_LIBS=ON -DBUILD_TESTING=OFF UPDATE_COMMAND "" ) +else() +SET(EXTPREFIX_gsl "${EXTPREFIX}" ) +ExternalProject_Add( ext_gsl + + DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} + URL http://files.kde.org/krita/build/dependencies/gsl-2.3.0.tar.gz + URL_MD5 7e0478f7c5e62696fef480b9a46f708c + + INSTALL_DIR ${EXTPREFIX_gsl} + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${EXTPREFIX_gsl} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DBUILD_TESTING=OFF + UPDATE_COMMAND "" +) + +endif() diff --git a/3rdparty/ext_openjpeg/CMakeLists.txt b/3rdparty/ext_openjpeg/CMakeLists.txt new file mode 100755 index 0000000000..3e34540089 --- /dev/null +++ b/3rdparty/ext_openjpeg/CMakeLists.txt @@ -0,0 +1,14 @@ +SET(PREFIX_ext_openjpeg "${EXTPREFIX}" ) +ExternalProject_Add( ext_openjpeg + DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} + URL http://files.kde.org/krita/build/dependencies/openjpeg-version.2.1.tar.gz + URL_MD5 3e1c451c087f8462955426da38aa3b3d + + INSTALL_DIR ${PREFIX_ext_openjpeg} + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${PREFIX_ext_openjpeg} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} + + UPDATE_COMMAND "" + ALWAYS 0 + DEPENDS ext_png ext_tiff ext_lcms2 ext_zlib +) + diff --git a/3rdparty/ext_python/CMakeLists.txt b/3rdparty/ext_python/CMakeLists.txt index 58ca1a873c..6496c12368 100644 --- a/3rdparty/ext_python/CMakeLists.txt +++ b/3rdparty/ext_python/CMakeLists.txt @@ -1,77 +1,77 @@ SET(PREFIX_ext_python "${EXTPREFIX}" ) if (UNIX) if (APPLE) set(PYTHON_VERSION "3.5") ExternalProject_Add( ext_python DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL http://files.kde.org/krita/build/dependencies/Python-3.5.2.tar.gz URL_MD5 ea334d398990037a4b8be324bd475c83 PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/pyport_osx.diff COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/osx_fixappinstall.diff CONFIGURE_COMMAND /configure MACOSX_DEPLOYMENT_TARGET=10.11 -prefix=${PREFIX_ext_python} ${GLOBAL_AUTOMAKE_PROFILE} --with-cxx-main=/usr/bin/g++ --without-ensurepip --disable-tests --without-test --without-tests --enable-framework=${PREFIX_ext_python}/lib BUILD_COMMAND make INSTALL_COMMAND make install COMMAND ${CMAKE_COMMAND} -E copy ${PREFIX_ext_python}/bin/python3 ${PREFIX_ext_python}/bin/python COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/sitecustomize.py ${PREFIX_ext_python}/lib/Python.framework/Versions/Current/lib/python${PYTHON_VERSION}/ COMMAND ${CMAKE_COMMAND} -E create_symlink ./lib/python${PYTHON_VERSION}/site-packages ${PREFIX_ext_python}/lib/Python.framework/Versions/Current/site-packages - # CMake FindPythonLib can't find framework libraries, lack of mantainer for Python + # CMake FindPythonLib can't find framework libraries, lack of maintainer for Python COMMAND find ${PREFIX_ext_python}/lib/Python.framework/Versions/Current/lib -type l -d 1 | grep -o "[^/]*$" | xargs -I FILE ${CMAKE_COMMAND} -E create_symlink ./Python.framework/Python ${PREFIX_ext_python}/lib/FILE UPDATE_COMMAND "" ALWAYS 0 ) else() ExternalProject_Add( ext_python DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL http://files.kde.org/krita/build/dependencies/Python-3.5.2.tar.gz URL_MD5 ea334d398990037a4b8be324bd475c83 CONFIGURE_COMMAND /configure --prefix=${PREFIX_ext_python} ${GLOBAL_AUTOMAKE_PROFILE} --enable-shared BUILD_COMMAND make INSTALL_COMMAND make install COMMAND ${CMAKE_COMMAND} -E copy ${PREFIX_ext_python}/bin/python3 ${PREFIX_ext_python}/bin/python UPDATE_COMMAND "" ALWAYS 0 ) endif() elseif(MINGW) if("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") ExternalProject_Add( ext_python DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL https://www.python.org/ftp/python/3.6.2/python-3.6.2-embed-amd64.zip URL_MD5 0fdfe9f79e0991815d6fc1712871c17f INSTALL_DIR ${PREFIX_ext_python} CONFIGURE_COMMAND "" BUILD_COMMAND ${CMAKE_COMMAND} -E echo deploying python3 64-bit binary INSTALL_COMMAND ${CMAKE_COMMAND} -E copy_directory / ${PREFIX_ext_python}/python COMMAND ${CMAKE_COMMAND} -E copy /python3.dll ${PREFIX_ext_python}/bin COMMAND ${CMAKE_COMMAND} -E copy /python36.dll ${PREFIX_ext_python}/bin COMMAND ${CMAKE_COMMAND} -E copy /vcruntime140.dll ${PREFIX_ext_python}/bin UPDATE_COMMAND "" ) else("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") ExternalProject_Add( ext_python DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL https://www.python.org/ftp/python/3.6.2/python-3.6.2-embed-win32.zip URL_MD5 2ca4768fdbadf6e670e97857bfab83e8 INSTALL_DIR ${PREFIX_ext_python} CONFIGURE_COMMAND "" BUILD_COMMAND ${CMAKE_COMMAND} -E echo deploying python3 32-bit binary INSTALL_COMMAND ${CMAKE_COMMAND} -E copy_directory / ${PREFIX_ext_python}/python COMMAND ${CMAKE_COMMAND} -E copy /python3.dll ${PREFIX_ext_python}/bin COMMAND ${CMAKE_COMMAND} -E copy /python36.dll ${PREFIX_ext_python}/bin COMMAND ${CMAKE_COMMAND} -E copy /vcruntime140.dll ${PREFIX_ext_python}/bin UPDATE_COMMAND "" ) endif("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") endif() diff --git a/CMakeLists.txt b/CMakeLists.txt index f625bf640f..1bb3e7ae15 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,919 +1,926 @@ project(krita) message(STATUS "Using CMake version: ${CMAKE_VERSION}") cmake_minimum_required(VERSION 3.0.0 FATAL_ERROR) set(MIN_QT_VERSION 5.9.0) set(MIN_FRAMEWORKS_VERSION 5.44.0) set( CMAKE_CXX_STANDARD 11 ) set( CMAKE_CXX_STANDARD_REQUIRED ON ) if (POLICY CMP0002) cmake_policy(SET CMP0002 OLD) endif() if (POLICY CMP0017) cmake_policy(SET CMP0017 NEW) endif () if (POLICY CMP0022) cmake_policy(SET CMP0022 OLD) endif () if (POLICY CMP0026) cmake_policy(SET CMP0026 OLD) endif() if (POLICY CMP0042) cmake_policy(SET CMP0042 NEW) endif() if (POLICY CMP0046) cmake_policy(SET CMP0046 OLD) endif () if (POLICY CMP0059) cmake_policy(SET CMP0059 OLD) endif() if (POLICY CMP0063) cmake_policy(SET CMP0063 OLD) endif() if (POLICY CMP0054) cmake_policy(SET CMP0054 OLD) endif() if (POLICY CMP0064) cmake_policy(SET CMP0064 OLD) endif() if (POLICY CMP0071) cmake_policy(SET CMP0071 OLD) endif() if (APPLE) set(APPLE_SUPPRESS_X11_WARNING TRUE) set(KDE_SKIP_RPATH_SETTINGS TRUE) set(CMAKE_MACOSX_RPATH 1) set(BUILD_WITH_INSTALL_RPATH 1) add_definitions(-mmacosx-version-min=10.12 -Wno-macro-redefined -Wno-deprecated-register) endif() if (CMAKE_COMPILER_IS_GNUCXX AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.9 AND NOT WIN32) add_compile_options($<$:-Wno-suggest-override> -Wextra) endif() ###################### ####################### ## Constants defines ## ####################### ###################### # define common versions of Krita applications, used to generate kritaversion.h # update these version for every release: set(KRITA_VERSION_STRING "4.3.0-prealpha") # Major version: 3 for 3.x, 4 for 4.x, etc. set(KRITA_STABLE_VERSION_MAJOR 4) # Minor version: 0 for 4.0, 1 for 4.1, etc. set(KRITA_STABLE_VERSION_MINOR 3) # Bugfix release version, or 0 for before the first stable release set(KRITA_VERSION_RELEASE 0) # the 4th digit, really only used for the Windows installer: # - [Pre-]Alpha: Starts from 0, increment 1 per release # - Beta: Starts from 50, increment 1 per release # - Stable: Set to 100, bump to 101 if emergency update is needed set(KRITA_VERSION_REVISION 0) set(KRITA_ALPHA 1) # uncomment only for Alpha #set(KRITA_BETA 1) # uncomment only for Beta #set(KRITA_RC 1) # uncomment only for RC set(KRITA_YEAR 2018) # update every year if(NOT DEFINED KRITA_ALPHA AND NOT DEFINED KRITA_BETA AND NOT DEFINED KRITA_RC) set(KRITA_STABLE 1) # do not edit endif() message(STATUS "Krita version: ${KRITA_VERSION_STRING}") # Define the generic version of the Krita libraries here # This makes it easy to advance it when the next Krita release comes. # 14 was the last GENERIC_KRITA_LIB_VERSION_MAJOR of the previous Krita series # (2.x) so we're starting with 15 in 3.x series, 16 in 4.x series if(KRITA_STABLE_VERSION_MAJOR EQUAL 4) math(EXPR GENERIC_KRITA_LIB_VERSION_MAJOR "${KRITA_STABLE_VERSION_MINOR} + 16") else() # let's make sure we won't forget to update the "16" message(FATAL_ERROR "Reminder: please update offset == 16 used to compute GENERIC_KRITA_LIB_VERSION_MAJOR to something bigger") endif() set(GENERIC_KRITA_LIB_VERSION "${GENERIC_KRITA_LIB_VERSION_MAJOR}.0.0") set(GENERIC_KRITA_LIB_SOVERSION "${GENERIC_KRITA_LIB_VERSION_MAJOR}") LIST (APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/modules") LIST (APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/kde_macro") # fetch git revision for the current build set(KRITA_GIT_SHA1_STRING "") set(KRITA_GIT_BRANCH_STRING "") include(GetGitRevisionDescription) get_git_head_hash(GIT_SHA1) get_git_branch(GIT_BRANCH) if(GIT_SHA1) string(SUBSTRING ${GIT_SHA1} 0 7 GIT_SHA1) set(KRITA_GIT_SHA1_STRING ${GIT_SHA1}) if(GIT_BRANCH) set(KRITA_GIT_BRANCH_STRING ${GIT_BRANCH}) else() set(KRITA_GIT_BRANCH_STRING "(detached HEAD)") endif() endif() # create test make targets enable_testing() # collect list of broken tests, empty here to start fresh with each cmake run set(KRITA_BROKEN_TESTS "" CACHE INTERNAL "KRITA_BROKEN_TESTS") ############ ############# ## Options ## ############# ############ include(FeatureSummary) if (WIN32) option(USE_DRMINGW "Support the Dr. Mingw crash handler (only on windows)" ON) add_feature_info("Dr. Mingw" USE_DRMINGW "Enable the Dr. Mingw crash handler") if (MINGW) option(USE_MINGW_HARDENING_LINKER "Enable DEP (NX), ASLR and high-entropy ASLR linker flags (mingw-w64)" ON) add_feature_info("Linker Security Flags" USE_MINGW_HARDENING_LINKER "Enable DEP (NX), ASLR and high-entropy ASLR linker flags") if (USE_MINGW_HARDENING_LINKER) set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--dynamicbase -Wl,--nxcompat -Wl,--disable-auto-image-base") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--dynamicbase -Wl,--nxcompat -Wl,--disable-auto-image-base") set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--dynamicbase -Wl,--nxcompat -Wl,--disable-auto-image-base") if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") # Enable high-entropy ASLR for 64-bit # The image base has to be >4GB for HEASLR to be enabled. # The values used here are kind of arbitrary. set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--high-entropy-va -Wl,--image-base,0x140000000") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--high-entropy-va -Wl,--image-base,0x180000000") set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--high-entropy-va -Wl,--image-base,0x180000000") endif ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") else (USE_MINGW_HARDENING_LINKER) message(WARNING "Linker Security Flags not enabled!") endif (USE_MINGW_HARDENING_LINKER) endif (MINGW) endif () option(HIDE_SAFE_ASSERTS "Don't show message box for \"safe\" asserts, just ignore them automatically and dump a message to the terminal." ON) configure_file(config-hide-safe-asserts.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-hide-safe-asserts.h) add_feature_info("Hide Safe Asserts" HIDE_SAFE_ASSERTS "Don't show message box for \"safe\" asserts, just ignore them automatically and dump a message to the terminal.") option(USE_LOCK_FREE_HASH_TABLE "Use lock free hash table instead of blocking." ON) configure_file(config-hash-table-implementaion.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-hash-table-implementaion.h) add_feature_info("Lock free hash table" USE_LOCK_FREE_HASH_TABLE "Use lock free hash table instead of blocking.") option(FOUNDATION_BUILD "A Foundation build is a binary release build that can package some extra things like color themes. Linux distributions that build and install Krita into a default system location should not define this option to true." OFF) add_feature_info("Foundation Build" FOUNDATION_BUILD "A Foundation build is a binary release build that can package some extra things like color themes. Linux distributions that build and install Krita into a default system location should not define this option to true.") option(KRITA_ENABLE_BROKEN_TESTS "Enable tests that are marked as broken" OFF) add_feature_info("Enable Broken Tests" KRITA_ENABLE_BROKEN_TESTS "Runs broken test when \"make test\" is invoked (use -DKRITA_ENABLE_BROKEN_TESTS=ON to enable).") option(LIMIT_LONG_TESTS "Run long running unittests in a limited quick mode" ON) configure_file(config-limit-long-tests.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-limit-long-tests.h) add_feature_info("Limit long tests" LIMIT_LONG_TESTS "Run long running unittests in a limited quick mode") option(ENABLE_PYTHON_2 "Enables the compiler to look for Python 2.7 instead of Python 3. Some packaged scripts are not compatible with Python 2 and this should only be used if you really have to use 2.7." OFF) option(BUILD_KRITA_QT_DESIGNER_PLUGINS "Build Qt Designer plugins for Krita widgets" OFF) add_feature_info("Build Qt Designer plugins" BUILD_KRITA_QT_DESIGNER_PLUGINS "Builds Qt Designer plugins for Krita widgets (use -DBUILD_KRITA_QT_DESIGNER_PLUGINS=ON to enable).") include(MacroJPEG) ######################################################### ## Look for Python3 It is also searched by KF5, ## ## so we should request the correct version in advance ## ######################################################### function(TestCompileLinkPythonLibs OUTPUT_VARNAME) include(CheckCXXSourceCompiles) set(CMAKE_REQUIRED_INCLUDES ${PYTHON_INCLUDE_PATH}) set(CMAKE_REQUIRED_LIBRARIES ${PYTHON_LIBRARIES}) if (MINGW) set(CMAKE_REQUIRED_DEFINITIONS -D_hypot=hypot) endif (MINGW) unset(${OUTPUT_VARNAME} CACHE) CHECK_CXX_SOURCE_COMPILES(" #include int main(int argc, char *argv[]) { Py_InitializeEx(0); }" ${OUTPUT_VARNAME}) endfunction() if(MINGW) if(ENABLE_PYTHON_2) message(FATAL_ERROR "Python 2.7 is not supported on Windows at the moment.") else(ENABLE_PYTHON_2) find_package(PythonInterp 3.6 EXACT) find_package(PythonLibs 3.6 EXACT) endif(ENABLE_PYTHON_2) if (PYTHONLIBS_FOUND AND PYTHONINTERP_FOUND) if(ENABLE_PYTHON_2) find_package(PythonLibrary 2.7) else(ENABLE_PYTHON_2) find_package(PythonLibrary 3.6) endif(ENABLE_PYTHON_2) TestCompileLinkPythonLibs(CAN_USE_PYTHON_LIBS) if (NOT CAN_USE_PYTHON_LIBS) message(FATAL_ERROR "Compiling with Python library failed, please check whether the architecture is correct. Python will be disabled.") endif (NOT CAN_USE_PYTHON_LIBS) endif (PYTHONLIBS_FOUND AND PYTHONINTERP_FOUND) else(MINGW) if(ENABLE_PYTHON_2) find_package(PythonInterp 2.7) find_package(PythonLibrary 2.7) else(ENABLE_PYTHON_2) find_package(PythonInterp 3.0) find_package(PythonLibrary 3.0) endif(ENABLE_PYTHON_2) endif(MINGW) ######################## ######################### ## Look for KDE and Qt ## ######################### ######################## find_package(ECM 5.22 REQUIRED NOMODULE) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR}) include(ECMOptionalAddSubdirectory) include(ECMAddAppIcon) include(ECMSetupVersion) include(ECMMarkNonGuiExecutable) include(ECMGenerateHeaders) include(GenerateExportHeader) include(ECMMarkAsTest) include(ECMInstallIcons) include(CMakePackageConfigHelpers) include(WriteBasicConfigVersionFile) include(CheckFunctionExists) include(KDEInstallDirs) include(KDECMakeSettings) include(KDECompilerSettings) # do not reorder to be alphabetical: this is the order in which the frameworks # depend on each other. find_package(KF5 ${MIN_FRAMEWORKS_VERSION} REQUIRED COMPONENTS Config WidgetsAddons Completion CoreAddons GuiAddons I18n ItemModels ItemViews WindowSystem Archive ) find_package(Qt5 ${MIN_QT_VERSION} REQUIRED COMPONENTS Core Gui Widgets Xml Network PrintSupport Svg Test Concurrent ) if (ANDROID) find_package(Qt5 ${MIN_QT_VERSION} REQUIRED COMPONENTS AndroidExtras ) endif() if (WIN32) set(CMAKE_REQUIRED_INCLUDES ${Qt5Core_INCLUDE_DIRS}) set(CMAKE_REQUIRED_LIBRARIES ${Qt5Core_LIBRARIES}) CHECK_CXX_SOURCE_COMPILES(" #include int main(int argc, char *argv[]) { QCoreApplication::setAttribute(Qt::AA_MSWindowsUseWinTabAPI); } " QT_HAS_WINTAB_SWITCH ) unset(CMAKE_REQUIRED_INCLUDES) unset(CMAKE_REQUIRED_LIBRARIES) option(USE_QT_TABLET_WINDOWS "Do not use Krita's forked Wintab and Windows Ink support on Windows, but leave everything to Qt." ON) add_feature_info("Use Qt's Windows Tablet Support" USE_QT_TABLET_WINDOWS "Do not use Krita's forked Wintab and Windows Ink support on Windows, but leave everything to Qt.") configure_file(config_use_qt_tablet_windows.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config_use_qt_tablet_windows.h) endif () set(CMAKE_REQUIRED_INCLUDES ${Qt5Core_INCLUDE_DIRS} ${Qt5Gui_INCLUDE_DIRS}) set(CMAKE_REQUIRED_LIBRARIES ${Qt5Core_LIBRARIES} ${Qt5Gui_LIBRARIES}) CHECK_CXX_SOURCE_COMPILES(" #include int main(int argc, char *argv[]) { QSurfaceFormat fmt; fmt.setColorSpace(QSurfaceFormat::scRGBColorSpace); fmt.setColorSpace(QSurfaceFormat::bt2020PQColorSpace); } " HAVE_HDR ) configure_file(config-hdr.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-hdr.h) CHECK_CXX_SOURCE_COMPILES(" #include int main(int argc, char *argv[]) { QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::Round); QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::RoundPreferFloor); QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough); } " HAVE_HIGH_DPI_SCALE_FACTOR_ROUNDING_POLICY ) configure_file(config-high-dpi-scale-factor-rounding-policy.h.in ${CMAKE_CURRENT_BINARY_DIR}/config-high-dpi-scale-factor-rounding-policy.h) if (WIN32) CHECK_CXX_SOURCE_COMPILES(" #include int main(int argc, char *argv[]) { QWindowsWindowFunctions::setHasBorderInFullScreenDefault(true); } " HAVE_SET_HAS_BORDER_IN_FULL_SCREEN_DEFAULT ) configure_file(config-set-has-border-in-full-screen-default.h.in ${CMAKE_CURRENT_BINARY_DIR}/config-set-has-border-in-full-screen-default.h) endif (WIN32) unset(CMAKE_REQUIRED_INCLUDES) unset(CMAKE_REQUIRED_LIBRARIES) include (MacroAddFileDependencies) include (MacroBoolTo01) include (MacroEnsureOutOfSourceBuild) macro_ensure_out_of_source_build("Compiling Krita inside the source directory is not possible. Please refer to the build instruction https://community.kde.org/Krita#Build_Instructions") # Note: OPTIONAL_COMPONENTS does not seem to be reliable # (as of ECM 5.15.0, CMake 3.2) find_package(Qt5Multimedia ${MIN_QT_VERSION}) set_package_properties(Qt5Multimedia PROPERTIES DESCRIPTION "Qt multimedia integration" URL "http://www.qt.io/" TYPE OPTIONAL PURPOSE "Optionally used to provide sound support for animations") macro_bool_to_01(Qt5Multimedia_FOUND HAVE_QT_MULTIMEDIA) configure_file(config-qtmultimedia.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-qtmultimedia.h ) if (NOT APPLE) find_package(Qt5Quick ${MIN_QT_VERSION}) set_package_properties(Qt5Quick PROPERTIES DESCRIPTION "QtQuick" URL "http://www.qt.io/" TYPE OPTIONAL PURPOSE "Optionally used for the touch gui for Krita") macro_bool_to_01(Qt5Quick_FOUND HAVE_QT_QUICK) find_package(Qt5QuickWidgets ${MIN_QT_VERSION}) set_package_properties(Qt5QuickWidgets PROPERTIES DESCRIPTION "QtQuickWidgets" URL "http://www.qt.io/" TYPE OPTIONAL PURPOSE "Optionally used for the touch gui for Krita") endif() if (NOT WIN32 AND NOT APPLE AND NOT ANDROID) find_package(Qt5 ${MIN_QT_VERSION} REQUIRED X11Extras) find_package(Qt5DBus ${MIN_QT_VERSION}) set(HAVE_DBUS ${Qt5DBus_FOUND}) set_package_properties(Qt5DBus PROPERTIES DESCRIPTION "Qt DBUS integration" URL "http://www.qt.io/" TYPE OPTIONAL PURPOSE "Optionally used to provide a dbus api on Linux") find_package(KF5Crash ${MIN_FRAMEWORKS_VERSION}) macro_bool_to_01(KF5Crash_FOUND HAVE_KCRASH) set_package_properties(KF5Crash PROPERTIES DESCRIPTION "KDE's Crash Handler" URL "http://api.kde.org/frameworks-api/frameworks5-apidocs/kcrash/html/index.html" TYPE OPTIONAL PURPOSE "Optionally used to provide crash reporting on Linux") find_package(X11 REQUIRED COMPONENTS Xinput) set(HAVE_X11 TRUE) add_definitions(-DHAVE_X11) else() set(HAVE_DBUS FALSE) set(HAVE_X11 FALSE) endif() add_definitions( -DQT_USE_QSTRINGBUILDER -DQT_STRICT_ITERATORS -DQT_NO_SIGNALS_SLOTS_KEYWORDS -DQT_NO_URL_CAST_FROM_STRING -DQT_USE_FAST_CONCATENATION -DQT_USE_FAST_OPERATOR_PLUS ) #if (${Qt5_VERSION} VERSION_GREATER "5.14.0" ) # add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50F00) #elseif (${Qt5_VERSION} VERSION_GREATER "5.13.0" ) # add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50E00) #elseif (${Qt5_VERSION} VERSION_GREATER "5.12.0" ) # add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50D00) #elseif (${Qt5_VERSION} VERSION_GREATER "5.11.0" ) # add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50C00) #if(${Qt5_VERSION} VERSION_GREATER "5.10.0" ) # add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50B00) #if(${Qt5_VERSION} VERSION_GREATER "5.9.0" ) # add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50A00) #else() add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50900) #endif() add_definitions(-DQT_DEPRECATED_WARNINGS) add_definitions(-DTRANSLATION_DOMAIN=\"krita\") # # The reason for this mode is that the Debug mode disable inlining # if(CMAKE_COMPILER_IS_GNUCXX) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fext-numeric-literals") endif() option(KRITA_DEVS "For Krita developers. This modifies the DEBUG build type to use -O3 -g, while still enabling Q_ASSERT. This is necessary because the Qt5 cmake modules normally append QT_NO_DEBUG to any build type that is not labeled Debug") if (KRITA_DEVS) set(CMAKE_CXX_FLAGS_DEBUG "-O3 -g" CACHE STRING "" FORCE) endif() if(UNIX) set(CMAKE_REQUIRED_LIBRARIES "${CMAKE_REQUIRED_LIBRARIES};m") endif() if(WIN32) if(MSVC) # C4522: 'class' : multiple assignment operators specified set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -wd4522") endif() endif() # KDECompilerSettings adds the `--export-all-symbols` linker flag. # We don't really need it. if(MINGW) string(REPLACE "-Wl,--export-all-symbols" "" CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS}") string(REPLACE "-Wl,--export-all-symbols" "" CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS}") endif(MINGW) if(MINGW) # Hack CMake's variables to tell AR to create thin archives to reduce unnecessary writes. # Source of definition: https://github.com/Kitware/CMake/blob/v3.14.1/Modules/Platform/Windows-GNU.cmake#L128 # Thin archives: https://sourceware.org/binutils/docs/binutils/ar.html#index-thin-archives macro(mingw_use_thin_archive lang) foreach(rule CREATE_SHARED_MODULE CREATE_SHARED_LIBRARY LINK_EXECUTABLE) string(REGEX REPLACE "( [^ T]+) " "\\1T " CMAKE_${lang}_${rule} "${CMAKE_${lang}_${rule}}") endforeach() endmacro() mingw_use_thin_archive(CXX) endif(MINGW) # enable exceptions globally kde_enable_exceptions() set(KRITA_DEFAULT_TEST_DATA_DIR ${CMAKE_SOURCE_DIR}/sdk/tests/data/) macro(macro_add_unittest_definitions) add_definitions(-DFILES_DATA_DIR="${CMAKE_CURRENT_SOURCE_DIR}/data/") add_definitions(-DFILES_OUTPUT_DIR="${CMAKE_CURRENT_BINARY_DIR}") add_definitions(-DFILES_DEFAULT_DATA_DIR="${KRITA_DEFAULT_TEST_DATA_DIR}") add_definitions(-DSYSTEM_RESOURCES_DATA_DIR="${CMAKE_SOURCE_DIR}/krita/data/") endmacro() # overcome some platform incompatibilities if(WIN32) include_directories(${CMAKE_CURRENT_SOURCE_DIR}/winquirks) add_definitions(-D_USE_MATH_DEFINES) add_definitions(-DNOMINMAX) set(WIN32_PLATFORM_NET_LIBS ws2_32.lib netapi32.lib) endif() # set custom krita plugin installdir if (ANDROID) # use default ABI if (NOT ANDROID_ABI) set (ANDROID_ABI armeabi-v7a) endif() set (ANDROID_SDK_ROOT $ENV{ANDROID_SDK_ROOT}) set (KRITA_PLUGIN_INSTALL_DIR ${LIB_INSTALL_DIR}) # set (DATA_INSTALL_DIR ${CMAKE_INSTALL_PREFIX}/assets) else() set (KRITA_PLUGIN_INSTALL_DIR ${LIB_INSTALL_DIR}/kritaplugins) endif() ########################### ############################ ## Required dependencies ## ############################ ########################### # FIXME: Still hardcoded if (ANDROID) set (Boost_INCLUDE_DIR ${CMAKE_CURRENT_BINARY_DIR}/i/${ANDROID_ABI}/include/boost-1_69) set (Boost_LIBRARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/i/${ANDROID_ABI}/lib) set (KF5_LIBRARIES ${CMAKE_CURRENT_BINARY_DIR}/kf5/kde/install/lib) endif() find_package(PNG REQUIRED) list (APPEND ANDROID_EXTRA_LIBS ${PNG_LIBRARY}) if (APPLE) # this is not added correctly on OSX -- see http://forum.kde.org/viewtopic.php?f=139&t=101867&p=221242#p221242 include_directories(SYSTEM ${PNG_INCLUDE_DIR}) endif() add_definitions(-DBOOST_ALL_NO_LIB) find_package(Boost 1.55 REQUIRED COMPONENTS system) include_directories(SYSTEM ${Boost_INCLUDE_DIRS}) ## ## Test for GNU Scientific Library ## find_package(GSL) set_package_properties(GSL PROPERTIES URL "http://www.gnu.org/software/gsl" TYPE RECOMMENDED PURPOSE "Required by Krita's Transform tool.") macro_bool_to_01(GSL_FOUND HAVE_GSL) configure_file(config-gsl.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-gsl.h ) if (GSL_FOUND) list (APPEND ANDROID_EXTRA_LIBS ${GSL_LIBRARIES} ${GSL_CBLAS_LIBRARIES}) endif() ########################### ############################ ## Optional dependencies ## ############################ ########################### find_package(ZLIB) set_package_properties(ZLIB PROPERTIES DESCRIPTION "Compression library" URL "http://www.zlib.net/" TYPE OPTIONAL PURPOSE "Optionally used by the G'Mic and the PSD plugins") macro_bool_to_01(ZLIB_FOUND HAVE_ZLIB) find_package(OpenEXR) set_package_properties(OpenEXR PROPERTIES DESCRIPTION "High dynamic-range (HDR) image file format" URL "http://www.openexr.com" TYPE OPTIONAL PURPOSE "Required by the Krita OpenEXR filter") macro_bool_to_01(OPENEXR_FOUND HAVE_OPENEXR) set(LINK_OPENEXR_LIB) if(OPENEXR_FOUND) include_directories(SYSTEM ${OPENEXR_INCLUDE_DIR}) set(LINK_OPENEXR_LIB ${OPENEXR_LIBRARIES}) add_definitions(${OPENEXR_DEFINITIONS}) endif() find_package(TIFF) set_package_properties(TIFF PROPERTIES DESCRIPTION "TIFF Library and Utilities" URL "http://www.remotesensing.org/libtiff" TYPE OPTIONAL PURPOSE "Required by the Krita TIFF filter") if (TIFF_FOUND) list (APPEND ANDROID_EXTRA_LIBS ${TIFF_LIBRARY}) endif() find_package(JPEG) set_package_properties(JPEG PROPERTIES DESCRIPTION "Free library for JPEG image compression. Note: libjpeg8 is NOT supported." URL "http://www.libjpeg-turbo.org" TYPE OPTIONAL PURPOSE "Required by the Krita JPEG filter") if (JPEG_FOUND) list (APPEND ANDROID_EXTRA_LIBS ${JPEG_LIBRARY}) endif() find_package(GIF) set_package_properties(GIF PROPERTIES DESCRIPTION "Library for loading and saving gif files." URL "http://giflib.sourceforge.net/" TYPE OPTIONAL PURPOSE "Required by the Krita GIF filter") if (GIF_FOUND) list (APPEND ANDROID_EXTRA_LIBS ${GIF_LIBRARY}) endif() find_package(HEIF "1.3.0") set_package_properties(HEIF PROPERTIES DESCRIPTION "Library for loading and saving heif files." URL "https://github.com/strukturag/libheif" TYPE OPTIONAL PURPOSE "Required by the Krita HEIF filter") +find_package(OpenJPEG "2.3.0") +set_package_properties(OpenJPEG PROPERTIES + DESCRIPTION "Library for loading and saving jp2000 files." + URL "http://www.openjpeg.org/" + TYPE OPTIONAL + PURPOSE "Required by the Krita JP2000 filter") + set(LIBRAW_MIN_VERSION "0.16") find_package(LibRaw ${LIBRAW_MIN_VERSION}) set_package_properties(LibRaw PROPERTIES DESCRIPTION "Library to decode RAW images" URL "http://www.libraw.org" TYPE OPTIONAL PURPOSE "Required to build the raw import plugin") find_package(FFTW3) set_package_properties(FFTW3 PROPERTIES DESCRIPTION "A fast, free C FFT library" URL "http://www.fftw.org/" TYPE OPTIONAL PURPOSE "Required by the Krita for fast convolution operators and some G'Mic features") macro_bool_to_01(FFTW3_FOUND HAVE_FFTW3) if (FFTW3_FOUND) list (APPEND ANDROID_EXTRA_LIBS ${FFTW3_LIBRARY}) endif() find_package(OCIO) set_package_properties(OCIO PROPERTIES DESCRIPTION "The OpenColorIO Library" URL "http://www.opencolorio.org" TYPE OPTIONAL PURPOSE "Required by the Krita LUT docker") macro_bool_to_01(OCIO_FOUND HAVE_OCIO) set_package_properties(PythonLibrary PROPERTIES DESCRIPTION "Python Library" URL "http://www.python.org" TYPE OPTIONAL PURPOSE "Required by the Krita PyQt plugin") macro_bool_to_01(PYTHONLIBS_FOUND HAVE_PYTHONLIBS) find_package(SIP "4.19.13") set_package_properties(SIP PROPERTIES DESCRIPTION "Support for generating SIP Python bindings" URL "https://www.riverbankcomputing.com/software/sip/download" TYPE OPTIONAL PURPOSE "Required by the Krita PyQt plugin") macro_bool_to_01(SIP_FOUND HAVE_SIP) find_package(PyQt5 "5.6.0") set_package_properties(PyQt5 PROPERTIES DESCRIPTION "Python bindings for Qt5." URL "https://www.riverbankcomputing.com/software/pyqt/download5" TYPE OPTIONAL PURPOSE "Required by the Krita PyQt plugin") macro_bool_to_01(PYQT5_FOUND HAVE_PYQT5) ## ## Look for OpenGL ## # TODO: see if there is a better check for QtGui being built with opengl support (and thus the QOpenGL* classes) if(Qt5Gui_OPENGL_IMPLEMENTATION) message(STATUS "Found QtGui OpenGL support") else() message(FATAL_ERROR "Did NOT find QtGui OpenGL support. Check your Qt configuration. You cannot build Krita without Qt OpenGL support.") endif() ## ## Test for eigen3 ## find_package(Eigen3 3.0 REQUIRED) set_package_properties(Eigen3 PROPERTIES DESCRIPTION "C++ template library for linear algebra" URL "http://eigen.tuxfamily.org" TYPE REQUIRED) ## ## Test for exiv2 ## find_package(LibExiv2 0.16 REQUIRED) if (ANDROID) list (APPEND ANDROID_EXTRA_LIBS ${LibExiv2_LIBRARIES}) # because libexiv2 depends on libexpat and it is installed in the same folder get_filename_component (_base_dir ${LibExiv2_LIBRARIES} DIRECTORY) list (APPEND ANDROID_EXTRA_LIBS ${_base_dir}/libexpat.so) endif() ## ## Test for lcms ## find_package(LCMS2 2.4 REQUIRED) set_package_properties(LCMS2 PROPERTIES DESCRIPTION "LittleCMS Color management engine" URL "http://www.littlecms.com" TYPE REQUIRED PURPOSE "Will be used for color management and is necessary for Krita") if(LCMS2_FOUND) if(NOT ${LCMS2_VERSION} VERSION_LESS 2040 ) set(HAVE_LCMS24 TRUE) endif() set(HAVE_REQUIRED_LCMS_VERSION TRUE) set(HAVE_LCMS2 TRUE) endif() list (APPEND ANDROID_EXTRA_LIBS ${LCMS2_LIBRARIES}) ## ## Test for Vc ## set(OLD_CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ) set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/modules ) set(HAVE_VC FALSE) if (NOT ${CMAKE_SYSTEM_PROCESSOR} MATCHES "arm") if(NOT MSVC) find_package(Vc 1.1.0) set_package_properties(Vc PROPERTIES DESCRIPTION "Portable, zero-overhead SIMD library for C++" URL "https://github.com/VcDevel/Vc" TYPE OPTIONAL PURPOSE "Required by the Krita for vectorization") macro_bool_to_01(Vc_FOUND HAVE_VC) endif() endif() configure_file(config-vc.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-vc.h ) if(HAVE_VC) message(STATUS "Vc found!") set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/vc") include (VcMacros) if(Vc_COMPILER_IS_CLANG) set(ADDITIONAL_VC_FLAGS "-ffp-contract=fast") if(NOT WIN32) set(ADDITIONAL_VC_FLAGS "${ADDITIONAL_VC_FLAGS} -fPIC") endif() elseif (NOT MSVC) set(ADDITIONAL_VC_FLAGS "-fabi-version=0 -ffp-contract=fast") if(NOT WIN32) set(ADDITIONAL_VC_FLAGS "${ADDITIONAL_VC_FLAGS} -fPIC") endif() endif() macro(ko_compile_for_all_implementations_no_scalar _objs _src) vc_compile_for_all_implementations(${_objs} ${_src} FLAGS ${ADDITIONAL_VC_FLAGS} ONLY SSE2 SSSE3 SSE4_1 AVX AVX2+FMA+BMI2) endmacro() macro(ko_compile_for_all_implementations _objs _src) vc_compile_for_all_implementations(${_objs} ${_src} FLAGS ${ADDITIONAL_VC_FLAGS} ONLY Scalar SSE2 SSSE3 SSE4_1 AVX AVX2+FMA+BMI2) endmacro() endif() set(CMAKE_MODULE_PATH ${OLD_CMAKE_MODULE_PATH} ) add_definitions(${QT_DEFINITIONS} ${QT_QTDBUS_DEFINITIONS}) ## ## Test endianness ## include (TestBigEndian) test_big_endian(CMAKE_WORDS_BIGENDIAN) ## ## Test for qt-poppler ## find_package(Poppler COMPONENTS Qt5) set_package_properties(Poppler PROPERTIES DESCRIPTION "A PDF rendering library" URL "http://poppler.freedesktop.org" TYPE OPTIONAL PURPOSE "Required by the Krita PDF filter.") ## ## Test for quazip ## find_package(QuaZip 0.6) set_package_properties(QuaZip PROPERTIES DESCRIPTION "A library for reading and writing zip files" URL "https://stachenov.github.io/quazip/" TYPE REQUIRED PURPOSE "Needed for reading and writing KRA and ORA files" ) # FIXME: better way to do this? list (APPEND ANDROID_EXTRA_LIBS ${QUAZIP_LIBRARIES} ${EXPAT_LIBRARY} ${KF5_LIBRARIES}/libKF5Completion.so ${KF5_LIBRARIES}/libKF5WindowSystem.so ${KF5_LIBRARIES}/libKF5WidgetsAddons.so ${KF5_LIBRARIES}/libKF5ItemViews.so ${KF5_LIBRARIES}/libKF5ItemModels.so ${KF5_LIBRARIES}/libKF5GuiAddons.so ${KF5_LIBRARIES}/libKF5I18n.so ${KF5_LIBRARIES}/libKF5CoreAddons.so ${KF5_LIBRARIES}/libKF5ConfigGui.so ${KF5_LIBRARIES}/libKF5ConfigCore.so ${KF5_LIBRARIES}/libKF5Archive.so) ## ## Test for Atomics ## include(CheckAtomic) ############################ ############################# ## Add Krita helper macros ## ############################# ############################ include(MacroKritaAddBenchmark) #################### ##################### ## Define includes ## ##################### #################### # for config.h and includes (if any?) include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_SOURCE_DIR}/interfaces ) add_subdirectory(libs) add_subdirectory(plugins) if (BUILD_TESTING) add_subdirectory(benchmarks) endif() add_subdirectory(krita) configure_file(KoConfig.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/KoConfig.h ) configure_file(config_convolution.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config_convolution.h) configure_file(config-ocio.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-ocio.h ) check_function_exists(powf HAVE_POWF) configure_file(config-powf.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-powf.h) if(WIN32) include(${CMAKE_CURRENT_LIST_DIR}/packaging/windows/installer/ConfigureInstallerNsis.cmake) endif() message("\nBroken tests:") foreach(tst ${KRITA_BROKEN_TESTS}) message(" * ${tst}") endforeach() feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/po OR EXISTS ${CMAKE_CURRENT_BINARY_DIR}/po ) find_package(KF5I18n CONFIG REQUIRED) ki18n_install(po) endif() if(DEFINED QTANDROID_EXPORTED_TARGET AND NOT TARGET "create-apk") set (_CMAKE_ANDROID_DIR "${ECM_DIR}/../toolchain") list(LENGTH QTANDROID_EXPORTED_TARGET targetsCount) include(${_CMAKE_ANDROID_DIR}/ECMAndroidDeployQt.cmake) math(EXPR last "${targetsCount}-1") foreach(idx RANGE 0 ${last}) list(GET QTANDROID_EXPORTED_TARGET ${idx} exportedTarget) list(GET ANDROID_APK_DIR ${idx} APK_DIR) if(APK_DIR AND NOT EXISTS "${ANDROID_APK_DIR}/AndroidManifest.xml" AND IS_ABSOLUTE ANDROID_APK_DIR) message(FATAL_ERROR "Cannot find ${APK_DIR}/AndroidManifest.xml according to ANDROID_APK_DIR. ${ANDROID_APK_DIR} ${exportedTarget}") elseif(NOT APK_DIR) get_filename_component(_qt5Core_install_prefix "${Qt5Core_DIR}/../../../" ABSOLUTE) set(APK_DIR "${_qt5Core_install_prefix}/src/android/templates/") endif() ecm_androiddeployqt("${exportedTarget}" "${ECM_ADDITIONAL_FIND_ROOT_PATH}") set_target_properties(create-apk-${exportedTarget} PROPERTIES ANDROID_APK_DIR "${APK_DIR}") endforeach() elseif(ANDROID) message(STATUS "You can export a target by specifying -DQTANDROID_EXPORTED_TARGET= and -DANDROID_APK_DIR=") endif() diff --git a/HACKING b/HACKING index 566e11e9ae..25b3040b47 100644 --- a/HACKING +++ b/HACKING @@ -1,165 +1,165 @@ Since 1999, people have been hacking on Krita. Everyone brought their own coding style, their own code conventions, their own likes and dislikes. Me, (Boudewijn that is), I like indents of four spaces, and no scope prefixes for variables. However, in the interests of consistency, these are the rules new code should adhere to: See also http://techbase.kde.org/Policies/Kdelibs_Coding_Style -- that document is leading. Qt vs STD vs Boost: In general, use the Qt classes wherever possible, even if someone tells you that the STD class is better or whatever. We're dealing with a big project with lots of developers here and we should keep the code as consistent and easy to grasp as possible. Since people need to know Qt in the first place, keep to Qt. Discuss deviations on #krita C++11 and C++14 Yes, but. Avoid lambdas (except when replacing the use of QSignalMapper. Sigh.). Avoid the new sig/slot connection syntax _unless_ you are porting all of Krita to the new syntax. Sure, it has some advantages, but having two different ways of doing the same thing is begging for trouble and comprehension problems. For now, keep using Q_FOREACH, we're using it all over the place. auto is fine, when using in for loops. Don't go use it in other places. Before using other new features, discuss on #krita so we can expand this list. Our minimum gcc version is 4.5, shipped with Ubuntu 12.04 Indentation With four spaces. Use the default kdelibs indentation (http://techbase.kde.org/Policies/Kdelibs_Coding_Style) Includes Avoid as much as possible #includes in header files; use forward declarations of classes. Initializers Avoid as much as possible initializers in the body of the constructor. Use initializer lists instead. Write the initializers as follows Class(A a, B b) : Subclass(a) , m_b(b) { } Note the location of the colon and comma. It is also okay to use initializers like, and maybe even preferred where it works. int m_something {0}; Scope prefixes Use only m_ for class-level variables. No other scope prefixes; no g_, l_, no 'p' for pointer variables. Shared pointers Use shared pointers wherever possible. Prefer Qt's shared pointer classes to our home-grown shared ppointer classes. Getter/setter Krita doesn't use Qt's properties -- yet. If you want to introduce use of properties, convert any and all classes in Krita before committing. Getter/setters are named 'x() for getters and setX(int x) for setters. If you come across violations of this rule, change the code. Class naming If you use a well-known design pattern, name the class according to the design pattern. All files should start with 'Kis', all classes with the 'Kis' prefix. This filename should be the same as the classname: KisNewClass.h, KisNewClass. Function naming Functions should be named in camelBackedFashion, to conform to Qt's standards. If you encounter functions in c_style_like_this, feel free to rename. Also: verbNoun -- i.e., rotateLayer, not layer_rotate. The latter is a true c-ism, introduced by a language that needs to prefix the 'class' name to every function in order to have something that's not quite OO. Variable/Parameter names Variable/parameter names start with a lower case letter. A name composed of different words is done in camelBackedStyle. Designer Krita has started to use designer. All dialogs and all widgets that have a layout manager must be done in designer. Do not add code or signal/slot connections in designer. Enums All enums should be prefixed with 'enum'. Namespaces Currently, we only use anonymous namespaces for things like undo commands. For the rest, some classes have a 'Kis' prefix, others don't. This should be made consistent, and we might want to use namespaces to keep all of Krita inside. Files and classes It's preferred (and strongly preferred) to have only one class per .h/.cpp file. (Which is logical, because otherwise you won't be able to keep to the naming scheme.) Spaces Keep the source airy and open. In particular, there should be empty lines between function declarations and definitions. Slots and signals Prefix slots with slot and signals with sig: slotUpdateSelection, sigSelectionUpdated. Boolean operators Use the standard !, !=, ==, && etc style, not the "not", "and" etc. style. Keep krita code using one, easily recognizable, C++ style. Boudewijn Rempt With Krita now supporting Python scripting, we need guidelines for these as well. These guidelines are preliminary and may be further refined in the future. To keep it simple, we have chosen to follow the style guide suggested by Python: PEP8. All rules should be followed, except the max limit of 79 characters per line. As this can reduce readability in some cases, this rule is optional. The full PEP8 specification is available here: https://www.python.org/dev/peps/pep-0008/ To check compliance you can run pep8.py against the code. You can also use autopep8.py to automatically fix detected compliance issues. pep8.py can be downloaded via Python's package manager (pip) [https://pypi.python.org/pypi/pep8], or your distribution's package manager. autopep8.py can also be downloaded via Python's package manager [https://pypi.python.org/pypi/autopep8], or your distribution's package manager. Both of these scripts come bundled with the PyDev plugin, which is available for Eclipse and other IDEs. The PyDev integration can be configured to visually highlight portions of the code which is not in compliance, as well as run autopep8 via shortcuts. pep8.py and autopep8.py can suppress select rules via the "--ignore" command line argument. -To ignore the 79 charachers per line rule, pep8.py can be called like this: +To ignore the 79 characters per line rule, pep8.py can be called like this: pep8.py --ignore=E501 You can read more about the error codes and what they mean here: http://pep8.readthedocs.io/en/release-1.7.x/intro.html#error-codes diff --git a/README.android.md b/README.android.md index 3c557fdc09..9c53507a99 100644 --- a/README.android.md +++ b/README.android.md @@ -1,58 +1,58 @@ # Building Krita for Android First of all, I use linux to do my builds and testing. Although, -they _should_ work on Windows/macOS, I cannott give any guarentee +they _should_ work on Windows/macOS, I cannot give any guarantee that it will. ## Setting up Android SDK and NDK -We right now use android ndk version `r18b` to do our builds. So, +We right now use Android NDK version `r18b` to do our builds. So, I would recommend to use that. Download it from [google's website](https://developer.android.com/ndk/downloads/older_releases.html) then extract it. Next, Android SDK. You can either download Android Studio or just the `sdk-tools`. Both could be downloaded from [google's website](https://developer.android.com/studio). -If you downloaded Android Studio then open sdk manager and download +If you downloaded Android Studio then open SDK manager and download `Android SDK Build-Tools`. (more info: https://developer.android.com/studio/intro/update#sdk-manager) If you download just `sdk-tools`, then, extract it and run: ```shell cd /tools/bin ./sdkmanager --licenses ./sdkmanager platform-tools ./sdkmanager "platforms;android-21" ./sdkmanager "platforms;android-28" # for androiddeployqt ./sdkmanager "build-tools;28.0.2" ``` If you get some `ClasNotFoundException` it might be because `java` version is set to `11`. For `sdkmanager` to work, set it to `8` and then run it again. That's the only dependency we have to manage manually! ## Building Krita Now, to build krita, run `/packaging/android/androidbuild.sh --help` and pass the required arguments. Example: ```shell ./androidbuild.sh -p=all --src=/home/sh_zam/workspace/krita --build-type=Debug --build-root=/home/sh_zam/workspace/build-krita-android --ndk-path=/home/sh_zam/Android/Sdk/ndk-bundle --sdk-path=/home/sh_zam/Android/Sdk --api-level=21 --android-abi=armeabi-v7a ``` That's all! ## Installing Krita APK To install run `adb install -d -r /krita_build_apk/build/outputs/apk/debug/krita_build_apk-debug.apk`. `adb` should be in `/platform-tools/` ## Crash If Krita crashes you can look up the logs using `adb logcat` diff --git a/build-tools/windows/build.cmd b/build-tools/windows/build.cmd index 671bcb63a0..fb543193e3 100644 --- a/build-tools/windows/build.cmd +++ b/build-tools/windows/build.cmd @@ -1,817 +1,818 @@ @echo off setlocal enabledelayedexpansion goto begin :: Subroutines :find_on_path out_variable file_name set %1=%~f$PATH:2 goto :EOF :get_dir_path out_variable file_path set %1=%~dp2 goto :EOF :get_full_path out_variable file_path setlocal set FULL_PATH=%~f2 if not exist "%FULL_PATH%" ( set FULL_PATH= ) else ( if exist "%FULL_PATH%\" ( set FULL_PATH= ) ) endlocal & set "%1=%FULL_PATH%" goto :EOF :get_full_path_dir out_variable file_path setlocal set FULL_PATH=%~dp2 if not exist "%FULL_PATH%" ( set FULL_PATH= ) endlocal & set "%1=%FULL_PATH%" goto :EOF :prompt_for_string out_variable prompt set /p %1=%~2^> goto :EOF :prompt_for_positive_integer out_variable prompt setlocal call :prompt_for_string USER_INPUT "%~2" if "%USER_INPUT%" == "" set USER_INPUT=0 set /a RESULT=%USER_INPUT% if not %RESULT% GTR 0 ( set RESULT= ) endlocal & set "%1=%RESULT%" goto :EOF :prompt_for_file out_variable prompt setlocal :prompt_for_file__retry call :prompt_for_string USER_INPUT "%~2" if "%USER_INPUT%" == "" ( endlocal set %1= goto :EOF ) call :get_full_path RESULT "%USER_INPUT%" if "%RESULT%" == "" ( echo Input does not point to valid file! set USER_INPUT= goto prompt_for_file__retry ) endlocal & set "%1=%RESULT%" goto :EOF :prompt_for_dir out_variable prompt setlocal :prompt_for_dir__retry call :prompt_for_string USER_INPUT "%~2" if "%USER_INPUT%" == "" ( endlocal set %1= goto :EOF ) call :get_full_path_dir RESULT "%USER_INPUT%\" if "%RESULT%" == "" ( echo Input does not point to valid dir! set USER_INPUT= goto prompt_for_dir__retry ) endlocal & set "%1=%RESULT%" goto :EOF :usage echo Usage: echo %~n0 [--no-interactive] [ OPTIONS ... ] echo. echo Basic options: echo --no-interactive Run without interactive prompts echo When not specified, the script will prompt echo for some of the parameters. echo --jobs ^ Set parallel jobs count when building echo Defaults to no. of logical cores echo --skip-deps Skips (re)building of deps echo --skip-krita Skips (re)building of Krita echo --cmd Launch a cmd prompt instead of building. echo The environment is set up like the build echo environment with some helper command macros. echo. echo Path options: echo --src-dir ^ Specify Krita source dir echo If unspecified, this will be determined from echo the script location. echo --download-dir ^ Specify deps download dir echo Can be omitted if --skip-deps is used echo --deps-build-dir ^ Specify deps build dir echo Can be omitted if --skip-deps is used echo --deps-install-dir ^ Specify deps install dir echo --krita-build-dir ^ Specify Krita build dir echo Can be omitted if --skip-krita is used echo --krita-install-dir ^ Specify Krita install dir echo Can be omitted if --skip-krita is used echo. goto :EOF :usage_and_exit call :usage exit /b :usage_and_fail call :usage exit /b 100 :: ---------------------------- :begin echo Krita build script for Windows echo. :: command-line args parsing set ARG_NO_INTERACTIVE= set ARG_JOBS= set ARG_SKIP_DEPS= set ARG_SKIP_KRITA= set ARG_SRC_DIR= set ARG_DOWNLOAD_DIR= set ARG_DEPS_BUILD_DIR= set ARG_DEPS_INSTALL_DIR= set ARG_KRITA_BUILD_DIR= set ARG_KRITA_INSTALL_DIR= set ARG_CMD= :args_parsing_loop set CURRENT_MATCHED= if not "%1" == "" ( if "%1" == "--no-interactive" ( set ARG_NO_INTERACTIVE=1 set CURRENT_MATCHED=1 ) if "%1" == "--jobs" ( if not "%ARG_JOBS%" == "" ( echo ERROR: Arg --jobs specified more than once 1>&2 echo. goto usage_and_fail ) set /a "ARG_JOBS=%2" if not !ARG_JOBS! GTR 0 ( echo ERROR: Arg --jobs is not a positive integer 1>&2 echo. goto usage_and_fail ) shift /2 set CURRENT_MATCHED=1 ) if "%1" == "--skip-deps" ( set ARG_SKIP_DEPS=1 set CURRENT_MATCHED=1 ) if "%1" == "--skip-krita" ( set ARG_SKIP_KRITA=1 set CURRENT_MATCHED=1 ) if "%1" == "--src-dir" ( if not "%ARG_SRC_DIR%" == "" ( echo ERROR: Arg --src-dir specified more than once 1>&2 echo. goto usage_and_fail ) if not exist "%~f2\" ( echo ERROR: Arg --src-dir does not point to a directory 1>&2 echo. goto usage_and_fail ) call :get_dir_path ARG_SRC_DIR "%~f2\" shift /2 set CURRENT_MATCHED=1 ) if "%1" == "--download-dir" ( if not "%ARG_DOWNLOAD_DIR%" == "" ( echo ERROR: Arg --download-dir specified more than once 1>&2 echo. goto usage_and_fail ) if "%~f2" == "" ( echo ERROR: Arg --download-dir does not point to a valid path 1>&2 echo. goto usage_and_fail ) call :get_dir_path ARG_DOWNLOAD_DIR "%~f2\" shift /2 set CURRENT_MATCHED=1 ) if "%1" == "--deps-build-dir" ( if not "%ARG_DEPS_BUILD_DIR%" == "" ( echo ERROR: Arg --deps-build-dir specified more than once 1>&2 echo. goto usage_and_fail ) if "%~f2" == "" ( echo ERROR: Arg --deps-build-dir does not point to a valid path 1>&2 echo. goto usage_and_fail ) call :get_dir_path ARG_DEPS_BUILD_DIR "%~f2\" shift /2 set CURRENT_MATCHED=1 ) if "%1" == "--deps-install-dir" ( if not "%ARG_DEPS_INSTALL_DIR%" == "" ( echo ERROR: Arg --deps-install-dir specified more than once 1>&2 echo. goto usage_and_fail ) if "%~f2" == "" ( echo ERROR: Arg --deps-install-dir does not point to a valid path 1>&2 echo. goto usage_and_fail ) call :get_dir_path ARG_DEPS_INSTALL_DIR "%~f2\" shift /2 set CURRENT_MATCHED=1 ) if "%1" == "--krita-build-dir" ( if not "%ARG_KRITA_BUILD_DIR%" == "" ( echo ERROR: Arg --krita-build-dir specified more than once 1>&2 echo. goto usage_and_fail ) if "%~f2" == "" ( echo ERROR: Arg --krita-build-dir does not point to a valid path 1>&2 echo. goto usage_and_fail ) call :get_dir_path ARG_KRITA_BUILD_DIR "%~f2\" shift /2 set CURRENT_MATCHED=1 ) if "%1" == "--krita-install-dir" ( if not "%ARG_KRITA_INSTALL_DIR%" == "" ( echo ERROR: Arg --krita-install-dir specified more than once 1>&2 echo. goto usage_and_fail ) if "%~f2" == "" ( echo ERROR: Arg --krita-install-dir does not point to a valid path 1>&2 echo. goto usage_and_fail ) call :get_dir_path ARG_KRITA_INSTALL_DIR "%~f2\" shift /2 set CURRENT_MATCHED=1 ) if "%1" == "--cmd" ( set ARG_CMD=1 set CURRENT_MATCHED=1 ) if "%1" == "--help" ( goto usage_and_exit ) if not "!CURRENT_MATCHED!" == "1" ( echo ERROR: Unknown option %1 1>&2 echo. goto usage_and_fail ) shift /1 goto args_parsing_loop ) if "%ARG_NO_INTERACTIVE%" == "1" ( echo Non-interactive mode ) else ( echo Interactive mode :: Trick to pause on exit call :real_begin pause exit /b !ERRORLEVEL! ) :real_begin echo. if "%ARG_SKIP_DEPS%" == "1" ( if "%ARG_SKIP_KRITA%" == "1" ( echo ERROR: You cannot skip both deps and Krita 1>&2 echo. exit /b 102 ) echo Building of deps will be skipped. ) else ( if "%ARG_SKIP_KRITA%" == "1" ( echo Building of Krita will be skipped. ) else ( echo Both deps and Krita will be built. ) ) :: Check environment config if "%CMAKE_EXE%" == "" ( call :find_on_path CMAKE_EXE cmake.exe if "!CMAKE_EXE!" == "" ( if not "%ARG_NO_INTERACTIVE%" == "1" ( call :prompt_for_file CMAKE_EXE "Provide path to cmake.exe" ) if "!CMAKE_EXE!" == "" ( echo ERROR: CMake not found! 1>&2 exit /b 102 ) ) else ( echo Found CMake on PATH: !CMAKE_EXE! if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is this correct? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( call :prompt_for_file CMAKE_EXE "Provide path to cmake.exe" if "!CMAKE_EXE!" == "" ( echo ERROR: CMake not found! 1>&2 exit /b 102 ) ) ) ) ) echo CMake: %CMAKE_EXE% if "%SEVENZIP_EXE%" == "" ( call :find_on_path SEVENZIP_EXE 7z.exe if "!SEVENZIP_EXE!" == "" ( set "SEVENZIP_EXE=%ProgramFiles%\7-Zip\7z.exe" if "!SEVENZIP_EXE!" == "" ( set "SEVENZIP_EXE=%ProgramFiles(x86)%\7-Zip\7z.exe" ) if "!SEVENZIP_EXE!" == "" ( echo 7-Zip not found ) ) ) if "%SEVENZIP_EXE%" == "" ( echo 7-Zip: %SEVENZIP_EXE% ) if "%MINGW_BIN_DIR%" == "" ( call :find_on_path MINGW_BIN_DIR_MAKE_EXE mingw32-make.exe if "!MINGW_BIN_DIR_MAKE_EXE!" == "" ( if not "%ARG_NO_INTERACTIVE%" == "1" ( call :prompt_for_file MINGW_BIN_DIR_MAKE_EXE "Provide path to mingw32-make.exe of mingw-w64" ) if "!MINGW_BIN_DIR_MAKE_EXE!" == "" ( echo ERROR: mingw-w64 not found! 1>&2 exit /b 102 ) call :get_dir_path MINGW_BIN_DIR "!MINGW_BIN_DIR_MAKE_EXE!" ) else ( call :get_dir_path MINGW_BIN_DIR "!MINGW_BIN_DIR_MAKE_EXE!" echo Found mingw on PATH: !MINGW_BIN_DIR! if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is this correct? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( call :prompt_for_file MINGW_BIN_DIR_MAKE_EXE "Provide path to mingw32-make.exe of mingw-w64" if "!MINGW_BIN_DIR_MAKE_EXE!" == "" ( echo ERROR: mingw-w64 not found! 1>&2 exit /b 102 ) call :get_dir_path MINGW_BIN_DIR "!MINGW_BIN_DIR_MAKE_EXE!" ) ) ) ) echo mingw-w64: %MINGW_BIN_DIR% if "%PYTHON_BIN_DIR%" == "" ( call :find_on_path PYTHON_BIN_DIR_PYTHON_EXE python.exe if "!PYTHON_BIN_DIR_PYTHON_EXE!" == "" ( if not "%ARG_NO_INTERACTIVE%" == "1" ( call :prompt_for_file PYTHON_BIN_DIR_PYTHON_EXE "Provide path to python.exe of Python 3.6.2" ) if "!PYTHON_BIN_DIR_PYTHON_EXE!" == "" ( echo ERROR: Python not found! 1>&2 exit /b 102 ) call :get_dir_path PYTHON_BIN_DIR "!PYTHON_BIN_DIR_PYTHON_EXE!" ) else ( call :get_dir_path PYTHON_BIN_DIR "!PYTHON_BIN_DIR_PYTHON_EXE!" echo Found Python on PATH: !PYTHON_BIN_DIR! if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is this correct? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( call :prompt_for_file PYTHON_BIN_DIR_PYTHON_EXE "Provide path to python.exe of Python 3.6.2" if "!PYTHON_BIN_DIR_PYTHON_EXE!" == "" ( echo ERROR: Python not found! 1>&2 exit /b 102 ) call :get_dir_path PYTHON_BIN_DIR "!PYTHON_BIN_DIR_PYTHON_EXE!" ) ) ) ) echo Python: %PYTHON_BIN_DIR% if "%ARG_SKIP_DEPS%" == "1" goto skip_windows_sdk_dir_check if "%WindowsSdkDir%" == "" if not "%ProgramFiles(x86)%" == "" set "WindowsSdkDir=%ProgramFiles(x86)%\Windows Kits\10" if "%WindowsSdkDir%" == "" set "WindowsSdkDir=%ProgramFiles(x86)%\Windows Kits\10" if exist "%WindowsSdkDir%\" ( pushd "%WindowsSdkDir%" if exist "bin\x64\fxc.exe" ( set HAVE_FXC_EXE=1 if "%WindowsSdkVerBinPath%" == "" set "WindowsSdkVerBinPath=%WindowsSdkDir%" ) else ( for /f "delims=" %%a in ('dir /a:d /b "bin\10.*"') do ( if exist "bin\%%a\x64\fxc.exe" ( set HAVE_FXC_EXE=1 if "%WindowsSdkVerBinPath%" == "" set "WindowsSdkVerBinPath=%WindowsSdkDir%\bin\%%a\" ) ) ) popd ) set QT_ENABLE_DYNAMIC_OPENGL=ON if not "%HAVE_FXC_EXE%" == "1" ( set WindowsSdkDir= echo Windows SDK 10 with fxc.exe not found echo Qt will *not* be built with ANGLE ^(dynamic OpenGL^) support. if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is this ok? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( exit /b 102 ) ) set QT_ENABLE_DYNAMIC_OPENGL=OFF ) else echo Windows SDK 10 with fxc.exe found on %WindowsSdkDir% :skip_windows_sdk_dir_check if not "%ARG_JOBS%" == "" ( set "PARALLEL_JOBS=%ARG_JOBS%" ) if "%PARALLEL_JOBS%" == "" ( echo Number of logical CPU cores detected: %NUMBER_OF_PROCESSORS% echo Enabling %NUMBER_OF_PROCESSORS% parallel jobs set PARALLEL_JOBS=%NUMBER_OF_PROCESSORS% if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is this correct? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( call :prompt_for_positive_integer PARALLEL_JOBS "Provide no. of parallel jobs" if "!PARALLEL_JOBS!" == "" ( echo ERROR: Invalid job count! 1>&2 exit /b 102 ) ) ) ) echo Parallel jobs count: %PARALLEL_JOBS% if not "%ARG_SRC_DIR%" == "" ( set "KRITA_SRC_DIR=%ARG_SRC_DIR%" ) if "%KRITA_SRC_DIR%" == "" ( :: Check whether this looks like to be in the source tree set "_temp=%~dp0" if "!_temp:~-21!" == "\build-tools\windows\" ( if exist "!_temp:~0,-21!\CMakeLists.txt" ( if exist "!_temp:~0,-21!\3rdparty\CMakeLists.txt" ( set "KRITA_SRC_DIR=!_temp:~0,-21!\" echo Script is running inside Krita src dir ) ) ) ) if "%KRITA_SRC_DIR%" == "" ( if not "%ARG_NO_INTERACTIVE%" == "1" ( call :prompt_for_dir KRITA_SRC_DIR "Provide path of Krita src dir" ) if "!KRITA_SRC_DIR!" == "" ( echo ERROR: Krita src dir not found! 1>&2 exit /b 102 ) ) echo Krita src: %KRITA_SRC_DIR% if "%ARG_SKIP_DEPS%" == "1" goto skip_deps_args_check if not "%ARG_DOWNLOAD_DIR%" == "" ( set "DEPS_DOWNLOAD_DIR=%ARG_DOWNLOAD_DIR%" ) if "%DEPS_DOWNLOAD_DIR%" == "" ( set DEPS_DOWNLOAD_DIR=%CD%\d\ echo Using default deps download dir: !DEPS_DOWNLOAD_DIR! if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is this ok? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( call :prompt_for_dir DEPS_DOWNLOAD_DIR "Provide path of depps download dir" ) ) if "!DEPS_DOWNLOAD_DIR!" == "" ( echo ERROR: Deps download dir not set! 1>&2 exit /b 102 ) ) echo Deps download dir: %DEPS_DOWNLOAD_DIR% if not "%ARG_DEPS_BUILD_DIR%" == "" ( set "DEPS_BUILD_DIR=%ARG_DEPS_BUILD_DIR%" ) if "%DEPS_BUILD_DIR%" == "" ( set DEPS_BUILD_DIR=%CD%\b_deps\ echo Using default deps build dir: !DEPS_BUILD_DIR! if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is this ok? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( call :prompt_for_dir DEPS_BUILD_DIR "Provide path of deps build dir" ) ) if "!DEPS_BUILD_DIR!" == "" ( echo ERROR: Deps build dir not set! 1>&2 exit /b 102 ) ) echo Deps build dir: %DEPS_BUILD_DIR% :skip_deps_args_check if not "%ARG_DEPS_INSTALL_DIR%" == "" ( set "DEPS_INSTALL_DIR=%ARG_DEPS_INSTALL_DIR%" ) if "%DEPS_INSTALL_DIR%" == "" ( set DEPS_INSTALL_DIR=%CD%\i_deps\ echo Using default deps install dir: !DEPS_INSTALL_DIR! if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is this ok? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( call :prompt_for_dir DEPS_INSTALL_DIR "Provide path of deps install dir" ) ) if "!DEPS_INSTALL_DIR!" == "" ( echo ERROR: Deps install dir not set! 1>&2 exit /b 102 ) ) echo Deps install dir: %DEPS_INSTALL_DIR% if "%ARG_SKIP_KRITA%" == "1" goto skip_krita_args_check if not "%ARG_KRITA_BUILD_DIR%" == "" ( set "KRITA_BUILD_DIR=%ARG_KRITA_BUILD_DIR%" ) if "%KRITA_BUILD_DIR%" == "" ( set KRITA_BUILD_DIR=%CD%\b\ echo Using default Krita build dir: !KRITA_BUILD_DIR! if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is this ok? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( call :prompt_for_dir KRITA_BUILD_DIR "Provide path of Krita build dir" ) ) if "!KRITA_BUILD_DIR!" == "" ( echo ERROR: Krita build dir not set! 1>&2 exit /b 102 ) ) echo Krita build dir: %KRITA_BUILD_DIR% if not "%ARG_KRITA_INSTALL_DIR%" == "" ( set "KRITA_INSTALL_DIR=%ARG_KRITA_INSTALL_DIR%" ) if "%KRITA_INSTALL_DIR%" == "" ( set KRITA_INSTALL_DIR=%CD%\i\ echo Using default Krita install dir: !KRITA_INSTALL_DIR! if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is this ok? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( call :prompt_for_dir KRITA_INSTALL_DIR "Provide path of Krita install dir" ) ) if "!KRITA_INSTALL_DIR!" == "" ( echo ERROR: Krita install dir not set! 1>&2 exit /b 102 ) ) echo Krita install dir: %KRITA_INSTALL_DIR% :skip_krita_args_check echo. if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is the above ok? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( exit /b 1 ) echo. ) :: Initialize clean PATH set PATH=%SystemRoot%\system32;%SystemRoot%;%SystemRoot%\System32\Wbem;%SYSTEMROOT%\System32\WindowsPowerShell\v1.0\ set PATH=%MINGW_BIN_DIR%;%PYTHON_BIN_DIR%;%PATH% echo Creating dirs... if NOT "%ARG_SKIP_DEPS%" == "1" ( mkdir %DEPS_DOWNLOAD_DIR% if errorlevel 1 ( if not exist "%DEPS_DOWNLOAD_DIR%\" ( echo ERROR: Cannot create deps download dir! 1>&2 exit /b 103 ) ) mkdir %DEPS_BUILD_DIR% if errorlevel 1 ( if not exist "%DEPS_BUILD_DIR%\" ( echo ERROR: Cannot create deps build dir! 1>&2 exit /b 103 ) ) mkdir %DEPS_INSTALL_DIR% if errorlevel 1 ( if not exist "%DEPS_INSTALL_DIR%\" ( echo ERROR: Cannot create deps install dir! 1>&2 exit /b 103 ) ) ) if NOT "%ARG_SKIP_KRITA%" == "1" ( mkdir %KRITA_BUILD_DIR% if errorlevel 1 ( if not exist "%KRITA_BUILD_DIR%\" ( echo ERROR: Cannot create Krita build dir! 1>&2 exit /b 103 ) ) mkdir %KRITA_INSTALL_DIR% if errorlevel 1 ( if not exist "%KRITA_INSTALL_DIR%\" ( echo ERROR: Cannot create Krita install dir! 1>&2 exit /b 103 ) ) ) echo. set CMAKE_BUILD_TYPE=RelWithDebInfo set QT_ENABLE_DEBUG_INFO=OFF :: Paths for CMake set "BUILDDIR_DOWNLOAD_CMAKE=%DEPS_DOWNLOAD_DIR:\=/%" set "BUILDDIR_DOWNLOAD_CMAKE=%BUILDDIR_DOWNLOAD_CMAKE: =\ %" set "BUILDDIR_DEPS_INSTALL_CMAKE=%DEPS_INSTALL_DIR:\=/%" set "BUILDDIR_DEPS_INSTALL_CMAKE=%BUILDDIR_DEPS_INSTALL_CMAKE: =\ %" set "BUILDDIR_KRITA_INSTALL_CMAKE=%KRITA_INSTALL_DIR:\=/%" set "BUILDDIR_KRITA_INSTALL_CMAKE=%BUILDDIR_KRITA_INSTALL_CMAKE: =\ %" set PATH=%DEPS_INSTALL_DIR%\bin;%PATH% if not "%GETTEXT_SEARCH_PATH%" == "" ( set PATH=!PATH!;!GETTEXT_SEARCH_PATH! ) :: Prepare the CMake command lines set CMDLINE_CMAKE_DEPS="%CMAKE_EXE%" "%KRITA_SRC_DIR%\3rdparty" ^ -DSUBMAKE_JOBS=%PARALLEL_JOBS% ^ -DQT_ENABLE_DEBUG_INFO=%QT_ENABLE_DEBUG_INFO% ^ -DQT_ENABLE_DYNAMIC_OPENGL=%QT_ENABLE_DYNAMIC_OPENGL% ^ -DEXTERNALS_DOWNLOAD_DIR=%BUILDDIR_DOWNLOAD_CMAKE% ^ -DINSTALL_ROOT=%BUILDDIR_DEPS_INSTALL_CMAKE% ^ -G "MinGW Makefiles" ^ -DUSE_QT_TABLET_WINDOWS=ON ^ -DCMAKE_BUILD_TYPE=%CMAKE_BUILD_TYPE% set CMDLINE_CMAKE_KRITA="%CMAKE_EXE%" "%KRITA_SRC_DIR%\." ^ -DBoost_DEBUG=OFF ^ -DBOOST_INCLUDEDIR=%BUILDDIR_DEPS_INSTALL_CMAKE%/include ^ -DBOOST_ROOT=%BUILDDIR_DEPS_INSTALL_CMAKE% ^ -DBOOST_LIBRARYDIR=%BUILDDIR_DEPS_INSTALL_CMAKE%/lib ^ -DCMAKE_PREFIX_PATH=%BUILDDIR_DEPS_INSTALL_CMAKE% ^ -DCMAKE_INSTALL_PREFIX=%BUILDDIR_KRITA_INSTALL_CMAKE% ^ -DBUILD_TESTING=OFF ^ -DHAVE_MEMORY_LEAK_TRACKER=OFF ^ -DFOUNDATION_BUILD=ON ^ -DUSE_QT_TABLET_WINDOWS=ON ^ + -DHIDE_SAFE_ASSERTS=OFF ^ -Wno-dev ^ -G "MinGW Makefiles" ^ -DCMAKE_BUILD_TYPE=%CMAKE_BUILD_TYPE% :: Launch CMD prompt if requested if "%ARG_CMD%" == "1" ( doskey cmake-deps=cmd /c "pushd %DEPS_BUILD_DIR% && %CMDLINE_CMAKE_DEPS%" doskey cmake-krita=cmd /c "pushd %KRITA_BUILD_DIR% && %CMDLINE_CMAKE_KRITA%" doskey make-deps=cmd /c "pushd %DEPS_BUILD_DIR% && "%CMAKE_EXE%" --build . --config %CMAKE_BUILD_TYPE% --target $*" doskey make-krita=cmd /c "pushd %KRITA_BUILD_DIR% && "%CMAKE_EXE%" --build . --config %CMAKE_BUILD_TYPE% --target install -- -j%PARALLEL_JOBS%" echo. title Krita build - %KRITA_SRC_DIR% ^(deps: %DEPS_BUILD_DIR%, krita: %KRITA_BUILD_DIR%^) echo You're now in the build environment. echo The following macros are available: echo cmake-deps echo -- Run CMake for the deps. echo make-deps ^ echo -- Run build for the specified deps target. The target name should echo include the `ext_` prefix, e.g. `ext_qt`. echo cmake-krita echo -- Run CMake for Krita. echo make-krita echo -- Run build for Krita's `install` target. echo. echo For more info, type `doskey /macros` to view the macro commands. cmd /k exit ) if "%ARG_SKIP_DEPS%" == "1" goto skip_build_deps pushd %DEPS_BUILD_DIR% if errorlevel 1 ( echo ERROR: Cannot enter deps build dir! 1>&2 exit /b 104 ) echo Running CMake for deps... @echo on %CMDLINE_CMAKE_DEPS% @if errorlevel 1 ( @echo ERROR: CMake configure failed! 1>&2 @exit /b 104 ) @echo off echo. set EXT_TARGETS=patch png2ico zlib lzma gettext openssl qt boost exiv2 fftw3 eigen3 set EXT_TARGETS=%EXT_TARGETS% ilmbase jpeg lcms2 ocio openexr png tiff gsl vc libraw set EXT_TARGETS=%EXT_TARGETS% giflib freetype poppler kwindowsystem drmingw gmic set EXT_TARGETS=%EXT_TARGETS% python sip pyqt set EXT_TARGETS=%EXT_TARGETS% quazip for %%a in (%EXT_TARGETS%) do ( echo Building ext_%%a... "%CMAKE_EXE%" --build . --config %CMAKE_BUILD_TYPE% --target ext_%%a if errorlevel 1 ( echo ERROR: Building of ext_%%a failed! 1>&2 exit /b 105 ) ) echo. echo ******** Built deps ******** popd :skip_build_deps if "%ARG_SKIP_KRITA%" == "1" goto skip_build_krita pushd %KRITA_BUILD_DIR% if errorlevel 1 ( echo ERROR: Cannot enter Krita build dir! 1>&2 exit /b 104 ) echo Running CMake for Krita... @echo on %CMDLINE_CMAKE_KRITA% @if errorlevel 1 ( @echo ERROR: CMake configure failed! 1>&2 @exit /b 104 ) @echo off echo. echo Building Krita... "%CMAKE_EXE%" --build . --config %CMAKE_BUILD_TYPE% --target install -- -j%PARALLEL_JOBS% if errorlevel 1 ( echo ERROR: Building of Krita failed! 1>&2 exit /b 105 ) echo. echo ******** Built Krita ******** popd :skip_build_krita echo Krita build completed! diff --git a/cmake/modules/FindOpenJPEG.cmake b/cmake/modules/FindOpenJPEG.cmake new file mode 100644 index 0000000000..b5c2ecaf54 --- /dev/null +++ b/cmake/modules/FindOpenJPEG.cmake @@ -0,0 +1,29 @@ +# - Try to find OpenJPEG +# Once done, this will define +# +# OpenJPEG_FOUND - system has OpenJPEG +# OpenJPEG_INCLUDE_DIRS - the OpenJPEG include directories +# OpenJPEG_LIBRARIES - link these to use OpenJPEG + +include(LibFindMacros) + +# Use pkg-config to get hints about paths +libfind_pkg_check_modules(OpenJPEG_PKGCONF libopenjp2) + +# Include dir +find_path(OpenJPEG_INCLUDE_DIR + NAMES openjpeg.h + HINTS ${OpenJPEG_PKGCONF_INCLUDE_DIRS} +) + +# Finally the library itself +find_library(OpenJPEG_LIBRARY + NAMES openjp2 + HINTS ${OpenJPEG_PKGCONF_LIBRARY_DIRS} +) + +# Set the include dir variables and the libraries and let libfind_process do the rest. +# NOTE: Singular variables for this library, plural for libraries this lib depends on. +set(OpenJPEG_PROCESS_INCLUDES OpenJPEG_INCLUDE_DIR) +set(OpenJPEG_PROCESS_LIBS OpenJPEG_LIBRARY) +libfind_process(OpenJPEG) diff --git a/krita/CMakeLists.txt b/krita/CMakeLists.txt index 7a43235875..82a00458ea 100644 --- a/krita/CMakeLists.txt +++ b/krita/CMakeLists.txt @@ -1,113 +1,113 @@ project(krita) include_directories(SYSTEM ${EIGEN3_INCLUDE_DIR} ${Vc_INCLUDE_DIR} ) add_subdirectory( dtd ) add_subdirectory( data ) add_subdirectory( integration ) -# Install the application icons following the the freedesktop icon theme spec +# Install the application icons following the freedesktop icon theme spec add_subdirectory( pics/app ) if (ANDROID) include_directories (${Qt5AndroidExtras_INCLUDE_DIRS}) endif() set(krita_SRCS main.cc) # Set the application icon on the application if (NOT APPLE) file(GLOB ICON_SRCS "${CMAKE_CURRENT_SOURCE_DIR}/pics/app/*-apps-krita.png") else() set(ICON_SRCS "${CMAKE_CURRENT_SOURCE_DIR}/pics/app/16-apps-krita.png" "${CMAKE_CURRENT_SOURCE_DIR}/pics/app/32-apps-krita.png" "${CMAKE_CURRENT_SOURCE_DIR}/pics/app/48-apps-krita.png" "${CMAKE_CURRENT_SOURCE_DIR}/pics/app/128-apps-krita.png" "${CMAKE_CURRENT_SOURCE_DIR}/pics/app/256-apps-krita.png" "${CMAKE_CURRENT_SOURCE_DIR}/pics/app/512-apps-krita.png" "${CMAKE_CURRENT_SOURCE_DIR}/pics/app/1024-apps-krita.png" ) endif() ecm_add_app_icon(krita_SRCS ICONS ${ICON_SRCS}) # Install the mimetype icons ecm_install_icons(ICONS "${CMAKE_CURRENT_SOURCE_DIR}/pics/mimetypes/16-mimetypes-application-x-krita.png" "${CMAKE_CURRENT_SOURCE_DIR}/pics/mimetypes/22-mimetypes-application-x-krita.png" "${CMAKE_CURRENT_SOURCE_DIR}/pics/mimetypes/32-mimetypes-application-x-krita.png" "${CMAKE_CURRENT_SOURCE_DIR}/pics/mimetypes/48-mimetypes-application-x-krita.png" "${CMAKE_CURRENT_SOURCE_DIR}/pics/mimetypes/64-mimetypes-application-x-krita.png" "${CMAKE_CURRENT_SOURCE_DIR}/pics/mimetypes/128-mimetypes-application-x-krita.png" "${CMAKE_CURRENT_SOURCE_DIR}/pics/mimetypes/256-mimetypes-application-x-krita.png" "${CMAKE_CURRENT_SOURCE_DIR}/pics/mimetypes/512-mimetypes-application-x-krita.png" "${CMAKE_CURRENT_SOURCE_DIR}/pics/mimetypes/1024-mimetypes-application-x-krita.png" DESTINATION ${KDE_INSTALL_ICONDIR} THEME hicolor) # separate listing, both used by Krita and KritaSketch set(krita_QRCS ${CMAKE_SOURCE_DIR}/krita/krita.qrc ${CMAKE_SOURCE_DIR}/krita/pics/Breeze-dark/breeze-dark-icons.qrc ${CMAKE_SOURCE_DIR}/krita/pics/Breeze-light/breeze-light-icons.qrc ${CMAKE_SOURCE_DIR}/krita/pics/layerbox/layerbox-icons.qrc ${CMAKE_SOURCE_DIR}/krita/pics/layerbox/svg/layerbox-svg-icons.qrc ${CMAKE_SOURCE_DIR}/krita/pics/layers/layers-icons.qrc ${CMAKE_SOURCE_DIR}/krita/pics/misc-light/misc-light-icons.qrc ${CMAKE_SOURCE_DIR}/krita/pics/misc-dark/misc-dark-icons.qrc ${CMAKE_SOURCE_DIR}/krita/pics/paintops/paintops-icons.qrc ${CMAKE_SOURCE_DIR}/krita/pics/tools/SVG/16/tools-svg-16-icons.qrc ${CMAKE_SOURCE_DIR}/krita/pics/tool_transform/tool-transform-icons.qrc ${CMAKE_SOURCE_DIR}/krita/pics/svg/svg-icons.qrc ${CMAKE_SOURCE_DIR}/libs/flake/flake.qrc ${CMAKE_SOURCE_DIR}/libs/widgets/kritawidgets.qrc ${CMAKE_SOURCE_DIR}/pics/icons.qrc ${CMAKE_SOURCE_DIR}/krita/data/aboutdata/aboutdata.qrc ${CMAKE_SOURCE_DIR}/krita/data/shaders/shaders.qrc ${CMAKE_SOURCE_DIR}/krita/data/cursors/cursors.qrc CACHE INTERNAL "krita_QRCS" ) qt5_add_resources(krita_SRCS ${krita_QRCS}) if (ANDROID) add_library(krita SHARED ${krita_SRCS}) target_link_libraries(krita PRIVATE Qt5::AndroidExtras) else() add_executable(krita ${krita_SRCS}) endif() target_link_libraries(krita PRIVATE kritaui Qt5::Core Qt5::Gui Qt5::Widgets Qt5::Xml Qt5::Network Qt5::PrintSupport Qt5::Svg Qt5::Concurrent) if(HAVE_KCRASH) target_link_libraries(krita PRIVATE KF5::Crash) endif() if (APPLE) set_target_properties(krita PROPERTIES INSTALL_RPATH "@loader_path/../../../../lib;@loader_path/../lib;@loader_path/../Frameworks;@executable_path/../lib;@executable_path/../Frameworks") set_target_properties(krita PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist.template) set_target_properties(krita PROPERTIES MACOSX_BUNDLE_GUI_IDENTIFIER "org.krita") set_target_properties(krita PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "Krita") set_target_properties(krita PROPERTIES MACOSX_BUNDLE_ICON_FILE "krita_SRCS.icns") set_target_properties(krita PROPERTIES MACOSX_BUNDLE_LONG_VERSION_STRING ${KRITA_VERSION_STRING}) set_target_properties(krita PROPERTIES MACOSX_BUNDLE_SHORT_VERSION_STRING ${KRITA_VERSION_STRING}) set_target_properties(krita PROPERTIES MACOSX_BUNDLE_VERSION ${KRITA_VERSION_STRING}) set_target_properties(krita PROPERTIES MACOSX_BUNDLE_COPYRIGHT "GNU Public License, V2 or, at your option, any later version.") endif () install(TARGETS krita ${INSTALL_TARGETS_DEFAULT_ARGS}) install(PROGRAMS org.kde.krita.desktop DESTINATION ${XDG_APPS_INSTALL_DIR}) install(FILES krita.action kritamenu.action DESTINATION ${DATA_INSTALL_DIR}/krita/actions) install(FILES org.kde.krita.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR} ) install(DIRECTORY DESTINATION ${DATA_INSTALL_DIR}/krita/shortcuts) diff --git a/krita/doc/strokes/strokes_documentation.org b/krita/doc/strokes/strokes_documentation.org index 2c3a5917a3..a2141f906e 100644 --- a/krita/doc/strokes/strokes_documentation.org +++ b/krita/doc/strokes/strokes_documentation.org @@ -1,705 +1,705 @@ #+TITLE: Strokes Documentation #+AUTHOR: Dmitry Kazakov #+EMAIL: dimula73@gmail.com * Strokes queue ** Strokes, jobs... What it is all about? (theory) *** Structure of a stroke An abstraction of a /stroke/ represents a complete action performed by a user. This action can be canceled when it has not been finished yet, or can be undone after it's undo data has been added to the undo stack. Every stroke consists of a set of /stroke jobs/. Every job sits in a queue and does a part of work that the stroke as a whole must perform on an image. A stroke job cannot be canceled while execution and you cannot undo a single job of the stroke without canceling the whole stroke. *Example:* Lets look at how the Freehand Tool works. Every time the user paints a single line on a canvas it creates a /stroke/. This stroke consists of several /stroke jobs/: one job initializes indirect painting device and starts a transaction, several jobs paint dabs of a canvas and the last job merges indirect painting device into the canvas and commit the undo information. The jobs of the stroke can demand special order of their execution. That is the way how they will be executed on a multi-core machine. Every job can be either of the type: - =CONCURRENT= :: /concurrent/ job may be executed in parallel with any other concurrent job of the stroke as well as with any update job executed by the scheduler *Example:* in Scale Image action each job scales its own layer. All the jobs are executed in parallel. - =SEQUENTIAL= :: if the job is /sequential/, no other job may interleave with this one. It means that when the scheduler encounters a sequential job, it waits until all the other stroke jobs are done, starts the sequential job and will not start any other job until this job is finished. Note that a sequential job can be executed in parallel with update jobs those merge layers and masks. *Example:* All the jobs of the Freehand Tool are sequential because you cannot rearrange the painting of dabs. And more than that, you cannot mix the creation of the transaction with painting of anything on a canvas. - =BARRIER= :: /barrier/ jobs are special. They created to allow stroke jobs to synchronize with updates when needed. A barrier job works like a sequential one: it does not allow two stroke jobs to be executed simultaneously, but it has one significant addition. A barrier job will not start its execution until /all/ the updates (those were requested with =setDirty()= calls before) has finished their execution. Such behavior is really useful for the case when you need to perform some action after the changes you requested in previous jobs are done and the projection of the image does now correspond the changes you've just done. *Example:* in Scale Image action the signals of the image like =sigSizeChanged= should be emitted after all the work is done and all the updates are finished. So it runs as a barrier job. See =KisProcessingApplicator= class for details. Besides one of the types above a job may be defined as =EXCLUSIVE=. Exclusive property makes the job to be executed on the scheduler exclusively. It means that there will be no other jobs (strokes or updates) executed in parallel to this one. *** The queue of strokes The strokes themselves are stored in a queue and executed one by one. This is important to know that any two jobs owned by different strokes cannot be executed simultaneously. That is the first job of a stroke starts its execution only /after/ the last job of the previous stroke has finished. The stroke is just a container for jobs. It stores some information about the work done, like =id()= and =name()=. Alongside storing this information it can affect the order of execution of jobs as well. The stroke can be defined /exclusive/. The meaning of this resembles the behavior of stroke job's exclusive property. /Exclusive stroke/ is a stroke that executes its jobs with all the updates blocked. The execution of updates will start only after the stroke is finished. ** Implementation (practice) *** Implementation of a stroke #+CAPTION: Overview of stroke classes [[./img/strokes_queue_internals.png]] Each stroke is represented by a =KisStroke= object. It has all the basic manipulating methods like: =addJob()=, =endStroke()= and =cancelStroke()=. The behavior of a stroke is defined by a /stroke strategy/ (KisStrokeStrategy class). This strategy is passed to the KisStroke object during construction and owned by the stroke. Each stroke job is represented by =KisStrokeJob= object. The queue of =KisStrokeJob= objects is stored in every stroke object. This very object is used for actual running the job (=KisUpdateJobItem= calls =KisStrokeJob::run()= method while running). The behavior of the stroke job is defined by a strategy (=KisStrokeStrategy=) and a data (=KisStrokeJobData=). Those two objects are passed during the construction of the KisStrokeJob object. A stroke can have four types of jobs: - initialization - canceling - finishing - actual painting (named as 'dab' in the code) During construction the stroke asks its strategy to create strategies for all the four types of job. Then it uses these strategies on creation of jobs on corresponding events: initialization, canceling, finishing and when the user calls =addJob()= method. The strategies define all the properties of strokes and stroke jobs we were talking above. The data class is used for passing information to the stroke by high-level code. *Example:* =FreehandStrokeStrategy::Data= accepts such information as: =node=, =painter=, =paintInformation=, =dragDistance= Other information that is common to the whole stroke like names of the paintOp, compositeOp are passed directly to the constructor of the stroke strategy. *** Execution of strokes by =KisStrokesQueue= The key class of the strokes' execution is =KisStrokesQueue=. The most important method that is responsible for applying all the rules about interleaving of jobs mentioned above is =KisStrokesQueue::processOneJob=. This method is called by the update scheduler each time a free thread appears. First it gets the number of merge and stroke jobs currently executing in the updater context. Then it checks all the rules one by one. *** Canceling and undo information trick It was stated above that a stroke can be canceled in each moment of time. That happens when a user calls =KisStroke::cancelStroke()= method. When it is requested the stroke drops all the jobs those are present in its queue and has not been started yet. Then it enqueues a special kind of job named /cancel job/ that reverts all the work done by the stroke. This is used for interactive canceling of tools' strokes. Taking into account that the strokes can be reverted, we cannot use =QUndoStack= capabilities directly. We should add commands to the stack /after/ they have been executed. This resembles the way how =KisTransactionData= works: its first redo() method doesn't do anything because everything has already been painted on a device. Here in strokes this "after-effect-addition" is implemented in general way. Strokes work with a special kind of undo adapter: =KisPostExecutionUndoAdapter=. This adapter wraps the commands in a special wrapper that puts them into the stack without calling =redo()= and controls their threaded =undo()= and =redo()= operations. See information about =KisPostExecutionUndoAdapter= in a separate document. *** Queues balancing So we ended up with a solution where our scheduler has two queues that it should spread between limited amount of threads. Of course there should be some algorithm that balances the queues. Ideally, we should balance them by the total area of image the queue should process. But we cannot achieve that currently. So the formula for size metrics is quite simple: ~updatesMetric = ~ ~strokesMetric = * ~ Balancing formula: ~balancingRatio = / ~ *** Starting a stroke The main entry point to strokes for the user is =KisStrokesFacade= interface. This interfaces provides four methods: =startStroke()=, =addJob()=, =endStroke()= and =cancelStroke()=. So every time you work with strokes you should work using this interface. *Note:* KisImage and KisUpdateScheduler both implement this interface, so you can use them as a strokes facade. But please try not to store pointers to the whole image. Try store a link to interface only, if possible. So if you want to start a stroke you should do the following: 1) Create a stroke strategy 2) Start a stroke with: =KisStrokeId strokeId = strokesFacade->startStroke(myStrategy);= *Note:* you'll get a KisStrokeId handle for the stroke you created. This handle will be used in all the other methods for controlling the stroke. This handle is introduced, because several users can access the strokes facade simultaneously, so there may be several strokes opened simultaneously. It's important to understand that even when several strokes are opened simultaneously, only one of them executes on the cpu. All the other strokes will be delayed until it is finished. 3) Create a data for your stroke job 4) Add a job to the execution queue: =strokesFacade->addJob(strokeId, myData);= 5) You may add as many jobs as you wish 6) End or cancel the stroke: =strokesFacade->endStroke(strokeId);= or =strokesFacade->cancelStroke(strokeId);= * Strokes public API ** Simplified stroke classes As you might noticed the internal strokes API is quite complex. If you decide to create your own stroke you need to create at least six new classes: - stroke strategy class - four stroke jobs strategies (init, finish, cancel, dab) - data that will be passes to a dab-strategy-based job That is not really a good solution for a public API, so we introduced an adapter that simplifies all these stuff. The class is called =KisSimpleStrokeStrategy=. It allows you to define all the jobs you need in a single class. #+CAPTION: Simple stroke classes [[./img/strokes_simplified_api.png]] This class has four virtual methods those you can use as callbacks. When you need to use one of them just override it in your own class and add activation of the corresponding callback to the constructor of your class: #+BEGIN_SRC c++ class MyOwnStroke : public KisSimpleStrokeStrategy { MyOwnStroke() { enableJob(KisSimpleStrokeStrategy::JOB_INIT); enableJob(KisSimpleStrokeStrategy::JOB_FINISH); enableJob(KisSimpleStrokeStrategy::JOB_CANCEL); enableJob(KisSimpleStrokeStrategy::JOB_DAB); } void initStrokeCallback() { } void finishStrokeCallback() { } void cancelStrokeCallback() { } void doStrokeCallback(KisStrokeJobData *data) { Q_UNUSED(data); } }; #+END_SRC Internally, =KisSimpleStrokeStrategy= creates all the job strategies needed for the lowlevel API. And these internal job strategies call the callbacks of the parental class. *Important:* Notice that the job data passed to /init/, /finish/ and /cancel/ jobs is always null. It means that these jobs will always be /sequential/ and /non-exclusive/. That is done intentionally to simplify the API. At the same time that is a limitation of the API. But currently, this is perfectly enough for us. ** Unit-testing of the strokes One of the benefits of using the strokes is that you are able to test them separately from the UI using a common infrastructure. *** =utils::StrokeTester= class That is a really simple class that you can use to test your own stroke. It test the following aspects of your stroke: - canceling of the stroke - working with indirect painting activated - testing updates of the image projection after your stroke - working with a layer that is not connected to any image The result of the execution is compared against the reference png files those you create manually while writing your test. *** How to write your own test You can check examples in =MoveStrokeTest= and =FreehandStrokeTest= tests. 1) You need to inherit your tester class from =utils::StrokeTester=. The constructor of that class accepts the name of your stroke (it'll be used for generating filenames), size of the image and a filename of the preset for the paintOp. #+BEGIN_SRC c++ StrokeTester(const QString &name, const QSize &imageSize, const QString &presetFileName = "autobrush_300px.kpp"); #+END_SRC 2) Then you need to override at least two methods: #+BEGIN_SRC c++ KisStrokeStrategy* createStroke(bool indirectPainting, KisResourcesSnapshotSP resources, KisPainter *painter, KisImageWSP image); void addPaintingJobs(KisImageWSP image, KisResourcesSnapshotSP resources, KisPainter *painter); #+END_SRC If you thing you need it you may do some corrections for the image and active node in the following method: #+BEGIN_SRC c++ void initImage(KisImageWSP image, KisNodeSP activeNode); #+END_SRC 3) Run your test in a testing slot: #+BEGIN_SRC c++ void MyStrokeTest::testStroke() { MyTester tester(); tester.test(); } #+END_SRC 4) During the first run the test will report you many fails and will generate you several files with actual result of the test. You need to check these files, then move them into the tests' data folder: =tests/data//= 5) After you copied the files the tester will compare the actual result against these very files. That means it'll catch all the changes in the work of your stroke, so you'll be able to catch all the regressions automatically. ** Predefined classes for usage as base classes *** =KisPainterBasedStrokeStrategy= This class can be used for the strokes those work with the node using a painter (or painters like in =KisToolMultihand=). This class accepts resources snapshot (=KisResourcesSnapshot=) and a painter (painters). Initialization, finishing and canceling callbacks of this class do all the work for dealing with indirect painting support, creation of transaction, reverting the stroke on canceling. This base class is used for =FreehandStroke= mostly. *** =KisStrokeStrategyUndoCommandBased= It is obvious from the name of the class that it works with undo commands. In constructor you define which method of undo command should be used undo() or redo(). Afterwards, you just add commands to the stroke and they are executed with any the sequentiality constraints. This stroke strategy does all the work for adding the commands to the undo adapter and for canceling them if needed. ** Example classes - =KisPainterBasedStrokeStrategy= - =FreehandStrokeStrategy= - =KisStrokeStrategyUndoCommandBased= - =MoveStrokeStrategy= * Internals of the freehand tool #+CAPTION: Freehand tool classes [[./img/freehand_tool_internals.png]] ** Motivation for so many classes We need to share the codebase between at least four classes: =KisToolFreehand=, =KisToolMultihand=, =KisScratchPad=. All these classes paint on a canvas with =KisPainter=, so they share quite much common code. ** KisResourcesSnapshot After we introduced the strokes, the moments of time when user paints with mouse and when the line is actually painted on the canvas do not coincide. It means that by the time a thread starts actual changing the device, the contents of =KoCanvasResourceProvider= might have already changed. So before we start a stroke we should create a snapshot of all the resources we have and pass this snapshot to the stroke. For this purpose we introduced =KisResourcesSnapshot= class. It solves two problems at the same time: first it stores all the resources we might have and second it encapsulates the algorithm of loading these resources into a =KisPainter= object. So this class is really easy to use. You just create the snapshot and then just load all the resources to the painter when needed. #+BEGIN_SRC c++ KisResourcesSnapshotSP resources = new KisResourcesSnapshot(image, undoAdapter, resourceManager); KisPainter painter; painter.begin(device, selection); resources->setupPainter(&painter); // paint something painter.end(); #+END_SRC In our implementation this class is usually created by =KisToolFreehandHelper= and passed to the =KisPainterBasedStrokeStrategy= class. The latter one creates painters and initializes them using =setupPainter()=. ** =KisToolFreehand= and =KisScratchPad= The freehand tool is split into four classes: - =KisToolFreehand= :: highlevel tool class that get the mouse events form the Ko-classes and distributes events among internal classes. - =KisToolPaintingInformationBuilder= :: converts mouse events represented by =KoPointerEvent= objects into =KisPaintInformation= objects. - =KisRecordingAdapter= :: stays in charge of adding recording information into the image's action recorder. This class has two purposes: first we need to be able to disable recording for the scratch pad (then we just pass NULL instead of a recording adapter), second when the strokes are able to do their own recording, it'll be easier to port the freehand tool to it. - =KisToolFreehandHelper= :: this is the main class that combines all the classes we were talking above. It accepts a mouse event, converts it using a painting information builder into the paint information, notifies recording adapter, takes the snapshot of resources and finally starts a stroke. Then it populates the stroke with stroke jobs, when the user moves the mouse (=paint(event)= method) and finishes the stroke in the end. Such splitting allows us to use the same classes in both =KisToolFreehand= and =KisScratchPad=. The only difference between them is that the scratch pad doesn't have a recording adapter at all, and uses base class =KisPaintingInformationBuilder= instead of =KisToolPaintingInformationBuilder=. The latter differs from the former one in a way that it supports painting assistants (=adjustDocumentPoint()= method), complex coordinate transformations with =KisCoordinatesConverter= (=documentToImage()= method) and perspective painting (=calculatePerspective()= method). The rest of the code is shared. ** =KisToolMultihand= Multihand tool uses the same classes. The only difference, it has a couple of modifications in its helper (=KisToolMultihandHelper=), those allow it to have several painters at the same time. The tool's class inherits the freehand tool's class and just substitutes the helper with its own (with =resetHelper()= method). * Scheduled Undo/Redo ** Two ways of working with undo commands The key problem of designing the undo system for strokes was that there are two ways of working with undo commands. That is we have two types of commands actually: - /Qt-like command/ - command's redo() method is executed while the command is added into the undo stack - /Transaction-like command/ - the command is added to the stack /after/ its action has already been performed. It means that the first redo() of this command (the one that is called by undo stack) does nothing. That is a transaction-like command just saves undo data for the future and does not perform anything on addition. You already know that our strokes can be reverted on the go, it means that the stroke's undo command should be added to the undo stack only /after/ all the actions of the stroke have been performed. So it looks like the stroke's commands are /transaction-like/. But there is another problem: the stroke should be able to execute regular undo commands those are not transaction-like (like is it done in =KisStrokeStrategyUndoCommand=). More than that, undo and redo of for such strokes should be performed with the same sequentiality properties (read "undo/redo operations should be threaded as well"). It follows that the undo commands generated by the stroke should be wrapped in a special /wrapper command/, lets call it =KisSavedCommand=, that hold the following properties: - the wrapper skips the first redo(). It means the wrapped command's redo() method will not be called on its addition to the stack. Obviously, it is not needed, because the action has already been performed by the stroke itself. - when undo stack calls to undo/redo methods of the wrapper-command, the command creates a stroke (=KisStrokeStrategyUndoCommandBased=) and runs the wrapped command in a context of this stroke. - a special /macro wrapper command/, lets call is =KisSavedMacroCommand=, should be able to save all the commands executed by a stroke and undo/redo all of them in the original order with original sequentiality properties (concurrent, sequential, barrier, exclusive). That is exactly what we have: =KisSavedUndoCommand= skips the first redo and runs undo()/redo() of an internal command in a separate stroke. We have =KisSavedMacroCommand= as well to save the contents of the whole stroke. #+CAPTION: Scheduled commands [[./img/scheduled_undo_redo.png]] ** New Undo Adapters Well, it would be quite insane to ask all the users of strokes to wrap their commands into wrapper, so we introduced a separate undo adapter for strokes: =KisPostExecutionUndoAdapter=. This adapter wraps your command and puts it into the undo stack automatically. This is the only adapter we can use inside strokes, that is why all the strokes accept the pointer to it. For the legacy code we still have =KisUndoAdapter=, but now we call it "legacy undo adapter". It works as usual: it adds a command to undo stack directly, so it gets executed right in the moment of addition. But there still is one trick. Stroke's commands come to the undo stack asynchronously, so if we try to simply add a command to the stack, we can catch a race condition easily. That's why the legacy undo adapter must guard itself from strokes with locking the strokes system. That is done with a special kind of lock =barrierLock()=. This barrier lock differs from a regular lock in a way that it ways for all the running /strokes/ are finished, while a regular lock waits for all the running /stroke jobs/ are done. That's the only difference. The same race conditions problem applies to the undo()/redo() signals from the UI. The user may request the undo operation while the stroke is adding its commands. This will surely lead to a crash. We solved this problem in a bit hacky way: we hacked =QUndoStack= and made it's undo()/redo() slots virtual. After that we overridden the stack with our own, and changed these methods to block the strokes while undo()/redo() is happening. We use =tryBarrierLock()= there, because it is easier to cancel the undo than to wait until all the strokes are finished. ** Undo Adapters and Undo Stores Well, we have two types of undo adapters now (not counting =KisSurrrogateUndoAdapter=). It's obvious that they should share some code. That is why we split the work with the actual undo stack into a separate class =KisUndoStore=. So now the undo store defines "where to store the undo data", and undo adapter defines "how to adapt krita's commands to qt's stack". There are additional types of store classes for using in tests and for special purposes. #+CAPTION: Undo Adapter vs Undo Store [[./img/undo_adapters.png]] * Processings framework ** Motivation In Krita we have many actions which have common structure of execution. Take a look at actions like Scale Image, Rotate Image, Change Color Space - all of them have common phases: 1) Lock the image 2) Do the processing of nodes 3) Unlock the image 4) Emit setDirty() calls and update the projection of the nodes 5) Wait until all the setDirty()'es are finished 6) Emit image's signals like sigImageSizeChanged More than that, you should pay attention to the fact that all these actions should support undo/redo operations. And the last two phases cannot be implemented as usual qt-commands inside a usual macro, because they should always be executed /in the end/ of the action (in qt commands are executed in reverse order during undo operations, that is not what we want). And, btw, it would be really good idea to have multithreading support for such actions, because some of them (like Scale Image) may be quite slow. =KisNodeVisitor= cannot fit all these requirements, because it has important design limitations: first, walking through nodes is implemented inside the visitor itself and, second, emitting signals is put into visitors as well. These two limitations prevent the code to be shared between actions. That is why we introduced new shiny =KisProcessingVisitor= and a separate framework for them. ** Processing visitors #+CAPTION: Processing framework [[./img/processings_framework.png]] The key class of the processing framework is =KisProcessingVisitor=. Its main difference from the old visitor is that it is extremely simple. It performs one task only, it processes one node. And that is all. It does no locking, performs no updates, emits no signals. It just processes (that is, changes the content) a single node. You can look at the reference implementation of it in =KisCropProcessingVisitor= and =KisTransformProcessingVisitor=. The key idea of this framework is to keep the processings as simple as possible. So the rest of the work is done by external classes, those are shared between all the processings. We have one such class. Its name is =KisProcessingApplicator=. This class performs several tasks: - creates a stroke. So all the actions executed with this applicator will be undo/redo'able. - applies a visitor to a requested node. - applies a visitor recursively to a node and all its children. Note, that you can choose any sequentiality property for the execution of your visitor. It means that the visitors can be applied to nodes concurrently in multithreaded way. - applies a usual qt-command to the image. Sequentiality properties may vary as well. - emits setDirty() calls for all the nodes which need it. It is done in efficient way, so no nodes are updated twice. - emits image signals /after/ all the actions and updates are finished. Lets look at an example: #+BEGIN_SRC c++ void KisImage::resizeImageImpl(const QRect& newRect, bool cropLayers) { if(newRect == bounds()) return; QString actionName = cropLayers ? i18n("Crop Image") : i18n("Resize Image"); (1) KisImageSignalVector emitSignals; (2) emitSignals << SizeChangedSignal << ModifiedSignal; (3) KisProcessingApplicator applicator(this, m_d->rootLayer, KisProcessingApplicator::RECURSIVE, emitSignals, actionName); if(cropLayers || !newRect.topLeft().isNull()) { (4) KisProcessingVisitorSP visitor = new KisCropProcessingVisitor(newRect, cropLayers, true); (5) applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT); } (6) applicator.applyCommand(new KisImageResizeCommand(this, newRect.size())); (7) applicator.end(); } #+END_SRC In lines (1) and (2) we create a list of signals we should emit after the execution of the applicator. This list should be passed to the /constructor/ of the applicator (3) (the list is passed to the constructor instead of end() function, because we face a limitation connected with the internals of the implementation of undo for processings, I doubt it can create any troubles). In the line (3) we create a recursive applicator. In lines (4) and (5) we create a visitor and apply it to nodes recursively in a multithreaded way. *Warning:* the visitor is shared between all the threads so it should be written in a /thread-safe/ way. In line (6) we apply a command sequentially, it means that it'll be executed right after /all/ the threads with visitors has finished. Line (7) closes the stroke an tells it to perform all the updates and emit all the signals. ** Implementation of =KisProcessingApplicator= The applicator is based on the "undo command"-based stroke (=KisStrokeStrategyUndoCommandBased=). It starts the stroke in the constructor and adds undo commands to it on every user request. The processings are inernally wrapped into a special command (=KisProcessingCommand=). This command has its own undo stack that collects the transactions executed by the processing. This can be easily achieved with our undo adapters interface. The command just defines its own =KisSurrogateUndoAdapter= and passes it to the processing. Processing adds its transactions to the fake adapter. And later, the command just uses the undo stack to undo/redo actions executed by the transaction. The applicator defines several internal commands as well: =UpdateCommand= and =EmitSignalsCommand=. These commands are added to the beginning and to the end of every stroke, so that they can be executed in the end of both undo and redo operations. The parameter =finalUpdate= controls whether the command is executed during its redo() or undo() operation. ** Emission of signals trick After actions have been moved to separate threads, problems with image signals appeared. When everything was executed in a single thread the connection of signals like =sigAboutToAddNode= and =sigNodeHasBeenAdded= worked as /Qt::DirectConnection/. So these signals were effectively function calls. After we moved the actions to a separate thread, all of them became /Qt::QueuedConnection/. I guess you know what it means. They simply lost all their sense. So we had to start to use /Qt::BlockingQueuedConnection/. But there is another problem with it. Some of the (old) code is still executed in a context of the UI thread and they emit signals as well. So all that code causes deadlocks when using =Qt::BlockingQueuedConnection=. That is why we had to introduce =KisImageSignalRouter=. This class checks which thread emits the signal and emits it either using =Qt::DirectConnection= or =Qt::BlockingQueuedConnection=. So no deadlocks are possible. ** Progress reporting The fact that a processing visitor does a really simple task (processes a single node) that is very easy to report progress using progress bars in the layer box. We just need to use progress - pxoxy of the node we process (=KisNodeProgressProxy=). Our - processings framework provides even easier way of doing this. You - just need to instantiate a =ProgressHelper= object and ask it to - create a =KoUpdater= object for you. And all is done. You can see - an example in =KisTransformProcessingVisitor= class. + proxy of the node we process (=KisNodeProgressProxy=). Our + processings framework provides an even easier way of doing this. + You just need to instantiate a =ProgressHelper= object and ask it + to create a =KoUpdater= object for you. And all is done. You can + see an example in =KisTransformProcessingVisitor= class. ** Testing Usage of a common framework makes testing really simple. There is a separate unittest in image's tests folder: =KisProcessingsTest=. To test a processing you need to write just a couple of lines. Everything is done by =BaseProcessingTest= helper class. This class will run your processing and compare results against reference png files those are stored in data folder. If there are some problems found, it'll dump result files to the current directory. diff --git a/krita/main.cc b/krita/main.cc index cd369afa17..a75c3a8f34 100644 --- a/krita/main.cc +++ b/krita/main.cc @@ -1,595 +1,596 @@ /* * Copyright (c) 1999 Matthias Elter * Copyright (c) 2002 Patrick Julien * Copyright (c) 2015 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "data/splash/splash_screen.xpm" #include "data/splash/splash_holidays.xpm" #include "data/splash/splash_screen_x2.xpm" #include "data/splash/splash_holidays_x2.xpm" #include "KisDocument.h" #include "kis_splash_screen.h" #include "KisPart.h" #include "KisApplicationArguments.h" #include #include "input/KisQtWidgetsTweaker.h" #include #include #ifdef Q_OS_ANDROID #include #endif #if defined Q_OS_WIN #include "config_use_qt_tablet_windows.h" #include #ifndef USE_QT_TABLET_WINDOWS #include #include #else #include #endif #include "config-high-dpi-scale-factor-rounding-policy.h" #include "config-set-has-border-in-full-screen-default.h" #ifdef HAVE_SET_HAS_BORDER_IN_FULL_SCREEN_DEFAULT #include #endif #include #endif #if defined HAVE_KCRASH #include #elif defined USE_DRMINGW namespace { void tryInitDrMingw() { wchar_t path[MAX_PATH]; QString pathStr = QCoreApplication::applicationDirPath().replace(L'/', L'\\') + QStringLiteral("\\exchndl.dll"); if (pathStr.size() > MAX_PATH - 1) { return; } int pathLen = pathStr.toWCharArray(path); path[pathLen] = L'\0'; // toWCharArray doesn't add NULL terminator HMODULE hMod = LoadLibraryW(path); if (!hMod) { return; } // No need to call ExcHndlInit since the crash handler is installed on DllMain auto myExcHndlSetLogFileNameA = reinterpret_cast(GetProcAddress(hMod, "ExcHndlSetLogFileNameA")); if (!myExcHndlSetLogFileNameA) { return; } // Set the log file path to %LocalAppData%\kritacrash.log QString logFile = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation).replace(L'/', L'\\') + QStringLiteral("\\kritacrash.log"); myExcHndlSetLogFileNameA(logFile.toLocal8Bit()); } } // namespace #endif #ifdef Q_OS_WIN namespace { typedef enum ORIENTATION_PREFERENCE { ORIENTATION_PREFERENCE_NONE = 0x0, ORIENTATION_PREFERENCE_LANDSCAPE = 0x1, ORIENTATION_PREFERENCE_PORTRAIT = 0x2, ORIENTATION_PREFERENCE_LANDSCAPE_FLIPPED = 0x4, ORIENTATION_PREFERENCE_PORTRAIT_FLIPPED = 0x8 } ORIENTATION_PREFERENCE; typedef BOOL WINAPI (*pSetDisplayAutoRotationPreferences_t)( ORIENTATION_PREFERENCE orientation ); void resetRotation() { QLibrary user32Lib("user32"); if (!user32Lib.load()) { qWarning() << "Failed to load user32.dll! This really should not happen."; return; } pSetDisplayAutoRotationPreferences_t pSetDisplayAutoRotationPreferences = reinterpret_cast(user32Lib.resolve("SetDisplayAutoRotationPreferences")); if (!pSetDisplayAutoRotationPreferences) { dbgKrita << "Failed to load function SetDisplayAutoRotationPreferences"; return; } bool result = pSetDisplayAutoRotationPreferences(ORIENTATION_PREFERENCE_NONE); dbgKrita << "SetDisplayAutoRotationPreferences(ORIENTATION_PREFERENCE_NONE) returned" << result; } } // namespace #endif #ifdef Q_OS_ANDROID extern "C" JNIEXPORT void JNICALL Java_org_krita_android_JNIWrappers_saveState(JNIEnv* /*env*/, jobject /*obj*/, jint /*n*/) { if (!KisPart::exists()) return; KisPart *kisPart = KisPart::instance(); QList> list = kisPart->documents(); for (QPointer &doc: list) { doc->autoSaveOnPause(); } const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); kritarc.setValue("canvasState", "OPENGL_SUCCESS"); } extern "C" JNIEXPORT void JNICALL Java_org_krita_android_JNIWrappers_exitFullScreen(JNIEnv* /*env*/, jobject /*obj*/, jint /*n*/) { if (!KisPart::exists()) return; KisMainWindow *mainWindow = KisPart::instance()->currentMainwindow(); mainWindow->viewFullscreen(false); } __attribute__ ((visibility ("default"))) #endif extern "C" int main(int argc, char **argv) { // The global initialization of the random generator qsrand(time(0)); bool runningInKDE = !qgetenv("KDE_FULL_SESSION").isEmpty(); #if defined HAVE_X11 qputenv("QT_QPA_PLATFORM", "xcb"); #endif // Workaround a bug in QNetworkManager qputenv("QT_BEARER_POLL_TIMEOUT", QByteArray::number(-1)); // A per-user unique string, without /, because QLocalServer cannot use names with a / in it QString key = "Krita4" + QStandardPaths::writableLocation(QStandardPaths::HomeLocation).replace("/", "_"); key = key.replace(":", "_").replace("\\","_"); QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts, true); QCoreApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings, true); QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps, true); QCoreApplication::setAttribute(Qt::AA_DisableShaderDiskCache, true); #ifdef HAVE_HIGH_DPI_SCALE_FACTOR_ROUNDING_POLICY // This rounding policy depends on a series of patches to Qt related to // https://bugreports.qt.io/browse/QTBUG-53022. These patches are applied // in ext_qt for WIndows (patches 0031-0036). // // The rounding policy can be set externally by setting the environment // variable `QT_SCALE_FACTOR_ROUNDING_POLICY` to one of the following: // Round: Round up for .5 and above. // Ceil: Always round up. // Floor: Always round down. // RoundPreferFloor: Round up for .75 and above. // PassThrough: Don't round. // // The default is set to RoundPreferFloor for better behaviour than before, // but can be overridden by the above environment variable. QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::RoundPreferFloor); #endif #ifdef Q_OS_ANDROID const QString write_permission = "android.permission.WRITE_EXTERNAL_STORAGE"; const QStringList permissions = { write_permission }; const QtAndroid::PermissionResultMap resultHash = QtAndroid::requestPermissionsSync(QStringList(permissions)); if (resultHash[write_permission] == QtAndroid::PermissionResult::Denied) { // TODO: show a dialog and graciously exit dbgKrita << "Permission denied by the user"; } else { dbgKrita << "Permission granted"; } #endif const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); bool singleApplication = true; bool enableOpenGLDebug = false; bool openGLDebugSynchronous = false; bool logUsage = true; { singleApplication = kritarc.value("EnableSingleApplication", true).toBool(); if (kritarc.value("EnableHiDPI", true).toBool()) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); } if (!qgetenv("KRITA_HIDPI").isEmpty()) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); } #ifdef HAVE_HIGH_DPI_SCALE_FACTOR_ROUNDING_POLICY if (kritarc.value("EnableHiDPIFractionalScaling", true).toBool()) { QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough); } #endif if (!qgetenv("KRITA_OPENGL_DEBUG").isEmpty()) { enableOpenGLDebug = true; } else { enableOpenGLDebug = kritarc.value("EnableOpenGLDebug", false).toBool(); } if (enableOpenGLDebug && (qgetenv("KRITA_OPENGL_DEBUG") == "sync" || kritarc.value("OpenGLDebugSynchronous", false).toBool())) { openGLDebugSynchronous = true; } KisConfig::RootSurfaceFormat rootSurfaceFormat = KisConfig::rootSurfaceFormat(&kritarc); KisOpenGL::OpenGLRenderer preferredRenderer = KisOpenGL::RendererAuto; logUsage = kritarc.value("LogUsage", true).toBool(); #ifdef Q_OS_WIN const QString preferredRendererString = kritarc.value("OpenGLRenderer", "angle").toString(); #else const QString preferredRendererString = kritarc.value("OpenGLRenderer", "auto").toString(); #endif preferredRenderer = KisOpenGL::convertConfigToOpenGLRenderer(preferredRendererString); const KisOpenGL::RendererConfig config = KisOpenGL::selectSurfaceConfig(preferredRenderer, rootSurfaceFormat, enableOpenGLDebug); KisOpenGL::setDefaultSurfaceConfig(config); KisOpenGL::setDebugSynchronous(openGLDebugSynchronous); #ifdef Q_OS_WIN // HACK: https://bugs.kde.org/show_bug.cgi?id=390651 resetRotation(); #endif } if (logUsage) { KisUsageLogger::initialize(); } QString root; QString language; { // Create a temporary application to get the root QCoreApplication app(argc, argv); Q_UNUSED(app); root = KoResourcePaths::getApplicationRoot(); QSettings languageoverride(configPath + QStringLiteral("/klanguageoverridesrc"), QSettings::IniFormat); languageoverride.beginGroup(QStringLiteral("Language")); language = languageoverride.value(qAppName(), "").toString(); } #ifdef Q_OS_LINUX { QByteArray originalXdgDataDirs = qgetenv("XDG_DATA_DIRS"); if (originalXdgDataDirs.isEmpty()) { // We don't want to completely override the default originalXdgDataDirs = "/usr/local/share/:/usr/share/"; } qputenv("XDG_DATA_DIRS", QFile::encodeName(root + "share") + ":" + originalXdgDataDirs); } #else qputenv("XDG_DATA_DIRS", QFile::encodeName(root + "share")); #endif dbgKrita << "Setting XDG_DATA_DIRS" << qgetenv("XDG_DATA_DIRS"); // Now that the paths are set, set the language. First check the override from the language // selection dialog. dbgKrita << "Override language:" << language; bool rightToLeft = false; if (!language.isEmpty()) { KLocalizedString::setLanguages(language.split(":")); // And override Qt's locale, too qputenv("LANG", language.split(":").first().toLocal8Bit()); QLocale locale(language.split(":").first()); QLocale::setDefault(locale); const QStringList rtlLanguages = QStringList() << "ar" << "dv" << "he" << "ha" << "ku" << "fa" << "ps" << "ur" << "yi"; if (rtlLanguages.contains(language.split(':').first())) { rightToLeft = true; } } else { dbgKrita << "Qt UI languages:" << QLocale::system().uiLanguages() << qgetenv("LANG"); // And if there isn't one, check the one set by the system. QLocale locale = QLocale::system(); if (locale.name() != QStringLiteral("en")) { QStringList uiLanguages = locale.uiLanguages(); for (QString &uiLanguage : uiLanguages) { // This list of language codes that can have a specifier should // be extended whenever we have translations that need it; right // now, only en, pt, zh are in this situation. if (uiLanguage.startsWith("en") || uiLanguage.startsWith("pt")) { uiLanguage.replace(QChar('-'), QChar('_')); } else if (uiLanguage.startsWith("zh-Hant") || uiLanguage.startsWith("zh-TW")) { uiLanguage = "zh_TW"; } else if (uiLanguage.startsWith("zh-Hans") || uiLanguage.startsWith("zh-CN")) { uiLanguage = "zh_CN"; } } for (int i = 0; i < uiLanguages.size(); i++) { QString uiLanguage = uiLanguages[i]; // Strip the country code int idx = uiLanguage.indexOf(QChar('-')); if (idx != -1) { uiLanguage = uiLanguage.left(idx); uiLanguages.replace(i, uiLanguage); } } dbgKrita << "Converted ui languages:" << uiLanguages; qputenv("LANG", uiLanguages.first().toLocal8Bit()); #ifdef Q_OS_MAC // See https://bugs.kde.org/show_bug.cgi?id=396370 KLocalizedString::setLanguages(QStringList() << uiLanguages.first()); #else KLocalizedString::setLanguages(QStringList() << uiLanguages); #endif } } #if defined Q_OS_WIN && defined USE_QT_TABLET_WINDOWS && defined QT_HAS_WINTAB_SWITCH const bool forceWinTab = !KisConfig::useWin8PointerInputNoApp(&kritarc); QCoreApplication::setAttribute(Qt::AA_MSWindowsUseWinTabAPI, forceWinTab); if (qEnvironmentVariableIsEmpty("QT_WINTAB_DESKTOP_RECT") && qEnvironmentVariableIsEmpty("QT_IGNORE_WINTAB_MAPPING")) { QRect customTabletRect; KisDlgCustomTabletResolution::Mode tabletMode = KisDlgCustomTabletResolution::getTabletMode(&customTabletRect); KisDlgCustomTabletResolution::applyConfiguration(tabletMode, customTabletRect); } #endif // first create the application so we can create a pixmap KisApplication app(key, argc, argv); + KisUsageLogger::writeHeader(); + KisOpenGL::initialize(); #ifdef HAVE_SET_HAS_BORDER_IN_FULL_SCREEN_DEFAULT if (QCoreApplication::testAttribute(Qt::AA_UseDesktopOpenGL)) { QWindowsWindowFunctions::setHasBorderInFullScreenDefault(true); } #endif - KisUsageLogger::writeHeader(); if (!language.isEmpty()) { if (rightToLeft) { app.setLayoutDirection(Qt::RightToLeft); } else { app.setLayoutDirection(Qt::LeftToRight); } } KLocalizedString::setApplicationDomain("krita"); dbgKrita << "Available translations" << KLocalizedString::availableApplicationTranslations(); dbgKrita << "Available domain translations" << KLocalizedString::availableDomainTranslations("krita"); #ifdef Q_OS_WIN QDir appdir(KoResourcePaths::getApplicationRoot()); QString path = qgetenv("PATH"); qputenv("PATH", QFile::encodeName(appdir.absolutePath() + "/bin" + ";" + appdir.absolutePath() + "/lib" + ";" + appdir.absolutePath() + "/Frameworks" + ";" + appdir.absolutePath() + ";" + path)); dbgKrita << "PATH" << qgetenv("PATH"); #endif if (qApp->applicationDirPath().contains(KRITA_BUILD_DIR)) { qFatal("FATAL: You're trying to run krita from the build location. You can only run Krita from the installation location."); } #if defined HAVE_KCRASH KCrash::initialize(); #elif defined USE_DRMINGW tryInitDrMingw(); #endif // If we should clear the config, it has to be done as soon as possible after // KisApplication has been created. Otherwise the config file may have been read // and stored in a KConfig object we have no control over. app.askClearConfig(); KisApplicationArguments args(app); if (singleApplication && app.isRunning()) { // only pass arguments to main instance if they are not for batch processing // any batch processing would be done in this separate instance const bool batchRun = args.exportAs() || args.exportSequence(); if (!batchRun) { QByteArray ba = args.serialize(); if (app.sendMessage(ba)) { return 0; } } } if (!runningInKDE) { // Icons in menus are ugly and distracting app.setAttribute(Qt::AA_DontShowIconsInMenus); } app.installEventFilter(KisQtWidgetsTweaker::instance()); if (!args.noSplash()) { // then create the pixmap from an xpm: we cannot get the // location of our datadir before we've started our components, // so use an xpm. QDate currentDate = QDate::currentDate(); QWidget *splash = 0; if (currentDate > QDate(currentDate.year(), 12, 4) || currentDate < QDate(currentDate.year(), 1, 9)) { splash = new KisSplashScreen(app.applicationVersion(), QPixmap(splash_holidays_xpm), QPixmap(splash_holidays_x2_xpm)); } else { splash = new KisSplashScreen(app.applicationVersion(), QPixmap(splash_screen_xpm), QPixmap(splash_screen_x2_xpm)); } app.setSplashScreen(splash); } #if defined Q_OS_WIN KisConfig cfg(false); bool supportedWindowsVersion = true; QOperatingSystemVersion osVersion = QOperatingSystemVersion::current(); if (osVersion.type() == QOperatingSystemVersion::Windows) { if (osVersion.majorVersion() >= QOperatingSystemVersion::Windows7.majorVersion()) { supportedWindowsVersion = true; } else { supportedWindowsVersion = false; if (cfg.readEntry("WarnedAboutUnsupportedWindows", false)) { QMessageBox::information(0, i18nc("@title:window", "Krita: Warning"), i18n("You are running an unsupported version of Windows: %1.\n" "This is not recommended. Do not report any bugs.\n" "Please update to a supported version of Windows: Windows 7, 8, 8.1 or 10.", osVersion.name())); cfg.writeEntry("WarnedAboutUnsupportedWindows", true); } } } #ifndef USE_QT_TABLET_WINDOWS { if (cfg.useWin8PointerInput() && !KisTabletSupportWin8::isAvailable()) { cfg.setUseWin8PointerInput(false); } if (!cfg.useWin8PointerInput()) { bool hasWinTab = KisTabletSupportWin::init(); if (!hasWinTab && supportedWindowsVersion) { if (KisTabletSupportWin8::isPenDeviceAvailable()) { // Use WinInk automatically cfg.setUseWin8PointerInput(true); } else if (!cfg.readEntry("WarnedAboutMissingWinTab", false)) { if (KisTabletSupportWin8::isAvailable()) { QMessageBox::information(nullptr, i18n("Krita Tablet Support"), i18n("Cannot load WinTab driver and no Windows Ink pen devices are found. If you have a drawing tablet, please make sure the tablet driver is properly installed."), QMessageBox::Ok, QMessageBox::Ok); } else { QMessageBox::information(nullptr, i18n("Krita Tablet Support"), i18n("Cannot load WinTab driver. If you have a drawing tablet, please make sure the tablet driver is properly installed."), QMessageBox::Ok, QMessageBox::Ok); } cfg.writeEntry("WarnedAboutMissingWinTab", true); } } } if (cfg.useWin8PointerInput()) { KisTabletSupportWin8 *penFilter = new KisTabletSupportWin8(); if (penFilter->init()) { // penFilter.registerPointerDeviceNotifications(); app.installNativeEventFilter(penFilter); dbgKrita << "Using Win8 Pointer Input for tablet support"; } else { dbgKrita << "No Win8 Pointer Input available"; delete penFilter; } } } #elif defined QT_HAS_WINTAB_SWITCH // Check if WinTab/WinInk has actually activated const bool useWinTabAPI = app.testAttribute(Qt::AA_MSWindowsUseWinTabAPI); if (useWinTabAPI != !cfg.useWin8PointerInput()) { cfg.setUseWin8PointerInput(useWinTabAPI); } #endif #endif - - if (!app.start(args)) { - return 1; - } - app.setAttribute(Qt::AA_CompressHighFrequencyEvents, false); // Set up remote arguments. QObject::connect(&app, SIGNAL(messageReceived(QByteArray,QObject*)), &app, SLOT(remoteArguments(QByteArray,QObject*))); QObject::connect(&app, SIGNAL(fileOpenRequest(QString)), &app, SLOT(fileOpenRequested(QString))); // Hardware information KisUsageLogger::write("\nHardware Information\n"); KisUsageLogger::write(QString(" GPU Acceleration: %1").arg(kritarc.value("OpenGLRenderer", "auto").toString())); KisUsageLogger::write(QString(" Memory: %1 Mb").arg(KisImageConfig(true).totalRAM())); KisUsageLogger::write(QString(" Number of Cores: %1").arg(QThread::idealThreadCount())); KisUsageLogger::write(QString(" Swap Location: %1\n").arg(KisImageConfig(true).swapDir())); KisConfig(true).logImportantSettings(); + if (!app.start(args)) { + KisUsageLogger::log("Could not start Krita Application"); + return 1; + } + int state = app.exec(); { QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); kritarc.setValue("canvasState", "OPENGL_SUCCESS"); } if (logUsage) { KisUsageLogger::close(); } return state; } diff --git a/krita/org.kde.krita.appdata.xml b/krita/org.kde.krita.appdata.xml index 544ca092ea..c549db0adc 100644 --- a/krita/org.kde.krita.appdata.xml +++ b/krita/org.kde.krita.appdata.xml @@ -1,378 +1,381 @@ org.kde.krita org.kde.krita.desktop CC0-1.0 GPL-3.0-only Krita Foundation Fundació Krita Fundació Krita Krita Foundation Krita Foundation Krita Foundation Fundación Krita Krita Fundazioa Krita Foundation La Fondation Krita Fundación Krita Asas Krita Fondazione Krita Krita Foundation Krita Foundation Krita Foundation Fundacja Krity Fundação do Krita Krita Foundation Nadácia Krita Krita-stiftelsen Krita Vakfı Фундація Krita xxKrita Foundationxx Krita 基金会 Krita 基金會 foundation@krita.org Krita كريتا Krita Krita Krita 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 رسم رقميّ، حريّة إبداعيّة 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 Margolan digitala, sormen askatasuna 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 Digital teikning – kreativ fridom 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 je potpuni digitalni umjetnički studio.

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

El 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 arte lantegi digital osoa da.

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는 디지털 예술 스튜디오입니다.

Krita is de digitale kunststudio vol mogelijkheden.

Krita er ei funksjonsrik digital teiknestove.

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.

Zirriborratzeko eta margotzeko ezin hobea da, eta margolan digitalen fitxategiak hutsetik sortzeko muturretik-muturrera konponbide bat aurkezten du, maisuentzako mailakoa.

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.

Passar perfekt for både teikning og måling, og dekkjer alle ledd i prosessen med å laga digitale måleri frå grunnen av.

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 aukera bikaina da kontzeptuzko artea, komikiak, errendatzeko ehundurak eta «matte» margolanak sortzeko. Kritak kolore-espazio ugari onartzen ditu hala nola GBU eta CMYK, 8 eta 16 biteko osoko kanaletan, baita 16 eta 32 biteko koma-higikorreko kanaletan.

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 channel floating point 16 dan 32 bit.

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는 컨셉 아트, 만화, 렌더링용 텍스처, 풍경화 등을 그릴 때 사용할 수 있는 완벽한 도구입니다. RGB, CMYK와 같은 여러 색 공간 및 8비트/16비트 정수 채널, 16비트/32비트 부동 소수점 채널을 지원합니다.

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 er det ideelle valet dersom du vil laga konseptskisser, teikneseriar, teksturar for 3D-rendering eller «matte paintings». Programmet støttar fleire fargerom, både RGB- og CMYK-baserte, med 8- og 16-bits heiltals- eller flyttalskanalar.

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 avançats de pinzells, filtres impressionants i moltes característiques útils que fan el Krita molt productiu.

Gaudiu pintant amb els motors avançats de pinzells, filtres impressionants i moltes característiques ú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.

Marrazten ondo pasa ezazu, isipu motor aurreratuekin, iragazki txundigarriekin eta eginbide praktiko ugariekin, zeintzuek Krita ikaragarri emankorra egiten duten.

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

Krita의 고급 브러시 엔진, 다양한 필터, 여러 도움이 되는 기능으로 생산성을 즐겁게 향상시킬 수 있습니다.

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

Leik deg med avanserte penselmotorar og fantastiske biletfilter – og mange andre nyttige funksjonar som gjer deg produktiv med Krita.

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://docs.krita.org/KritaFAQ.html https://krita.org/support-us/donations/ https://docs.krita.org/ https://docs.krita.org/en/untranslatable_pages/reporting_bugs.html Krita is a full-featured digital painting studio. Krita és un estudi de pintura digital ple de funcionalitats. Krita és un estudi de pintura digital ple de funcionalitats. Krita ist ein digitales Zeichenstudio mit umfangreichen Funktionen. Krita is a full-featured digital painting studio. Krita es un completo estudio de dibujo digital. Krita é un estudio completo de debuxo dixital. Krita adalah studio pelukisan digital dengan fitur yang lengkap. Krita è uno studio d'arte digitale completo. Krita는 다기능 디지털 예술 스튜디오입니다. Krita is een digitale schilderstudio vol mogelijkheden. Krita er ei funksjonsrik digital teiknestove. Krita jest pełnowymiarowym, cyfrowym studiem artystycznym. O Krita é um estúdio de arte digital completo. O Krita é um estúdio de pintura digital completo. Krita je plnohodnotné digitálne maliarske štúdio. Krita är en fullfjädrad digital konststudio. Krita — повноцінний комплекс для цифрового малювання. xxKrita is a full-featured digital painting studio.xx Krita 是一款功能齐全的数字绘画工作室软件。 Krita 是全功能的數位繪圖工作室。 https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_001.png The startup window now also gives you the latest news about Krita. La finestra d'inici ara proporciona les darreres notícies quant al Krita. La finestra d'inici ara proporciona les darreres noticies quant al Krita. The startup window now also gives you the latest news about Krita. La ventana de bienvenida también le proporciona ahora las últimas noticias sobre Krita. Agora a xanela de inicio tamén fornece as últimas novas sobre Krita. Window pemulaian kini juga memberikan kamu kabar terkini tentang Krita. La finestra di avvio ora fornisce anche le ultime novità su Krita. 시작 창에서 Krita의 최신 소식을 볼 수 있습니다. Het opstartvenster geeft u nu ook you het laatste nieuws over Krita. Oppstartsvindauget viser no siste nytt om Krita. Okno początkowe teraz wyświetla wieści o Kricie. A janela inicial agora também lhe dá as últimas notícias sobre o Krita. A janela de inicialização agora também mostra as últimas notícias sobre o Krita. V úvodnom okne sa tiež nachádzajú najnovšie správy o Krita. Startfönstret ger nu också senaste nytt om Krita. У початковому вікні програми ви можете бачити найсвіжіші новини щодо Krita. xxThe startup window now also gives you the latest news about Krita.xx 启动画面现在可以为你呈现与 Krita 有关的最新资讯。 開始視窗也提供給您關於 Krita 的最新消息。 https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_002.png There are over ten immensely powerful brush engines. Hi ha uns deu motors de pinzell immensament potents. Hi ha uns deu motors de pinzell immensament potents. There are over ten immensely powerful brush engines. Existen unos diez inmensamente potentes motores de pinceles. Hai máis de dez motores de pinceis inmensamente potentes. Ada lebih dari sepuluh mesin kuas yang sangat manjur. Ci sono oltre dieci motori di pennelli incredibilmente potenti. 10가지 종류의 강력한 브러시 엔진을 사용할 수 있습니다. Er zijn meer dan tien immens krachtige penseelengines. Det finst meir enn ti enormt kraftige penselmotorar. Istnieje ponad dziesięć zaawansowanych silników pędzli. Existem mais de dez motores de pincéis extremamente poderosos. Mais de dez engines de pincéis incrivelmente poderosos disponíveis. Existuje viac ako desať nesmierne výkonných štetcových enginov. Det finns mer än tio enormt kraftfulla penselgränssnitt. У програмі передбачено понад десяток надзвичайно потужних рушіїв пензлів. xxThere are over ten immensely powerful brush engines.xx 它具备超过十种相当强大的笔刷引擎。 https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_003.png Create and use gamut masks to give your images a coherent feel. Creeu i useu màscares de gamma per donar a les imatges una aparença coherent. Creeu i useu màscares de gamma per donar a les imatges una aparença coherent. Create and use gamut masks to give your images a coherent feel. Cree y use gamas para proporcionar a sus imágenes un aspecto coherente. Crea e usa máscaras de gama para dar ás túas imaxes un aspecto coherente. Ciptakan dan gunakan masker gamut untuk memberikan gambarmu sebuah suasana koheren. Crea e utilizza maschere gamut per dare alle tue immagini un aspetto coerente. 색역 마스크를 만들고 사용할 수 있습니다. Maak en gebruik gamut-maskers om uw afbeeldingen een coherent gevoel te geven. Bruk fargeområde-masker for å gje bileta eit heilsleg uttrykk. Stwórz i używaj masek gamut, aby nadać swoim obrazom spójny wygląd. Crie e use máscaras de gamute para dar às suas imagens uma aparência coerente. Crie e use máscaras de gama para dar um senso de coerência às suas imagens. Vytvorte a používajte gamutové masky, aby vašim obrázkom poskytli ucelený pocit. Att skapa och använda färgomfångsmasker ger bilder en sammanhängande känsla. Створюйте маски палітри і користуйтеся ними для надання вашим малюнкам однорідного вигляду. xxCreate and use gamut masks to give your images a coherent feel.xx 创建并使用色域蒙版可以为你的图像带来更加一致的观感。 https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_004.png Into animation? Krita provides everything you need for traditional, hand-drawn animation. Esteu amb animacions? El Krita proporciona tot el que cal per a l'animació a mà tradicional. Esteu amb animacions? El Krita proporciona tol el que cal per a l'animació a mà tradicional. Into animation? Krita provides everything you need for traditional, hand-drawn animation. ¿Trabaja con animación? Krita proporciona todo lo necesario para la animación manual tradicional. Gusta das animacións? Krita fornece todo o necesario para animacións tradicionais debuxadas a man. Soal animasi? krita menyediakan apa pun yang kamu perlukan untuk animasi gambar-tangan, tradisional. Per le animazioni? Krita fornisce tutto ciò che ti server per l'animazione tradizionale, disegnata a mano. 애니메이션을 만들 계획이 있으신가요? Krita를 통해서 수작업 애니메이션을 작업할 수 있습니다. Naar animatie? Krita biedt alles wat u nodig hebt voor traditionele, met de hand getekende animatie. Interessert i animasjon? Krita har alt du treng for tradisjonelle, handteikna animasjonar. Zajmujesz się animacjami? Krita zapewnia wszystko czego potrzebujesz do tworzenia tradycyjnych, ręcznie rysowanych animacji. Gosta de animação? O Krita oferece tudo o que precisa para o desenho animado tradicional e desenhado à mão. Curte animação? O Krita fornece tudo necessário para você poder trabalhar com animação tradicional ou feita à mão. Ste do animácie? Krita poskytuje všetko, čo potrebujete pre tradičné ručne kreslené animácie. Gillar du animering? Krita tillhandahåller allt som behövs för traditionella, handritade animeringar. Працюєте із анімацією? У Krita ви знайдете усе, що потрібно для створення традиційної, намальованої вручну анімації. xxInto animation? Krita provides everything you need for traditional, hand-drawn animation.xx 喜欢制作动画吗?Krita 提供了制作传统手绘动画的全套工具。 想做動畫?Krita 提供您在傳統、手繪動畫所需的任何東西。 https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_005.png If you're new to digital painting, or want to know more about Krita's possibilities, there's an extensive, up-to-date manual. Si sou nou a la pintura digital, o voleu conèixer més quant a les possibilitats del Krita, hi ha un ampli manual actualitzat. Si sou nou a la pintura digital, o voleu conèixer més quant a les possibilitats del Krita, hi ha un ampli manual actualitzat. If you're new to digital painting, or want to know more about Krita's possibilities, there's an extensive, up-to-date manual. Si está empezando con el dibujo digital o si quiere saber más sobre la posibilidades de Krita, dispone de un extenso y actualizado manual. Se está a empezar co debuxo dixital, ou quere saber máis sobre as posibilidades de Krita, existe un manual exhaustivo e actualizado. Jika kamu baru dalam pelukisan digital, atau ingin mengetahui selebihnya tentang Krita, di situ ada manual yang update dan luas. Se sei nuovo del disegno digitale, o vuoi saperne di più sulle possibilità di Krita, è disponibile un manuale completo e aggiornato. 디지털 페인팅을 처음 시작하시거나, Krita의 기능을 더 알아 보려면 사용 설명서를 참조하십시오. Als u nieuw bent in digitaal schilderen of u wilt meer weten over de mogelijkheden van Krita, dan is er een uitgebreide, bijgewerkte handleiding. Viss du er nybegynnar innan digital teikning, eller ønskjer å veta meir om kva som er mogleg med Krita, finst det ei omfattande og oppdatert brukarhandbok. Jeśli cyfrowe malowanie to dla ciebie nowość, lub jeśli chcesz dowiedzieć się więcej o możliwościach Krity, to dostępny jest wyczerpująca i aktualna instrukcja obsługi. Se é novo na pintura digital, ou deseja saber mais sobre as possibilidades do Krita, existe um manual extenso e actualizado. Se você for iniciante em pintura digital ou gostaria de saber mais sobre as possibilidades que o Krita oferece, há um extenso e atualizado manual para isso. Ak ste v oblasti digitálnej maľby nováčikom alebo sa chcete dozvedieť viac o možnostiach programu Krita, existuje o tom rozsiahla a aktuálna príručka. Om digital målning är nytt för dig, eller om du vill veta mer om Kritas möjligheter, finns en omfattande, aktuell handbok. Якщо ви не маєте достатнього досвіду у цифровому малюванні або хочете дізнатися більше про можливості Krita, скористайтеся нашим докладним і актуальним підручником. xxIf you're new to digital painting, or want to know more about Krita's possibilities, there's an extensive, up-to-date manual.xx 不管你是数字绘画的新手,还是想发现 Krita 更多的用法,你都可以在我们详尽并持续更新的使用手册中找到答案。 https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_006.png Graphics 2DGraphics RasterGraphics KDE krita org.kde.krita.desktop + + https://www.microsoft.com/store/apps/9n6x57zgrw96 +
diff --git a/krita/pics/tools/SVG/16/dark_tool_magnetic_selection.svg b/krita/pics/tools/SVG/16/dark_tool_magnetic_selection.svg new file mode 100644 index 0000000000..07f84e0bd5 --- /dev/null +++ b/krita/pics/tools/SVG/16/dark_tool_magnetic_selection.svg @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + Andrei Rudenko + + + + + + + + + + + diff --git a/krita/pics/tools/SVG/16/light_tool_magnetic_selection.svg b/krita/pics/tools/SVG/16/light_tool_magnetic_selection.svg new file mode 100644 index 0000000000..10f9a0401f --- /dev/null +++ b/krita/pics/tools/SVG/16/light_tool_magnetic_selection.svg @@ -0,0 +1,169 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + Andrei Rudenko + + + + + + + + + + + + diff --git a/krita/pics/tools/SVG/16/tools-svg-16-icons.qrc b/krita/pics/tools/SVG/16/tools-svg-16-icons.qrc index d760fb0128..e831473360 100644 --- a/krita/pics/tools/SVG/16/tools-svg-16-icons.qrc +++ b/krita/pics/tools/SVG/16/tools-svg-16-icons.qrc @@ -1,82 +1,84 @@ dark_calligraphy.svg dark_draw-text.svg dark_format-fill-color.svg dark_krita_draw_path.svg dark_krita_tool_color_fill.svg dark_krita_tool_color_picker.svg dark_krita_tool_dyna.svg dark_krita_tool_ellipse.svg dark_krita_tool_freehand.svg dark_krita_tool_freehandvector.svg dark_krita_tool_gradient.svg dark_krita_tool_grid.svg dark_krita_tool_line.svg dark_krita_tool_measure.svg dark_krita_tool_move.svg dark_krita_tool_multihand.svg dark_krita_tool_polygon.svg dark_krita_tool_rectangle.svg dark_krita_tool_transform.svg dark_pattern.svg dark_polyline.svg dark_select.svg dark_tool_contiguous_selection.svg dark_tool_crop.svg dark_tool_elliptical_selection.svg + dark_tool_magnetic_selection.svg dark_tool_outline_selection.svg dark_tool_pan.svg dark_tool_path_selection.svg dark_tool_perspectivegrid.svg dark_tool_polygonal_selection.svg dark_tool_rect_selection.svg dark_tool_similar_selection.svg dark_tool_zoom.svg light_calligraphy.svg light_draw-text.svg light_format-fill-color.svg light_krita_draw_path.svg light_krita_tool_color_fill.svg light_krita_tool_color_picker.svg light_krita_tool_dyna.svg light_krita_tool_ellipse.svg light_krita_tool_freehand.svg light_krita_tool_freehandvector.svg light_krita_tool_gradient.svg light_krita_tool_grid.svg light_krita_tool_line.svg light_krita_tool_measure.svg light_krita_tool_move.svg light_krita_tool_multihand.svg light_krita_tool_polygon.svg light_krita_tool_rectangle.svg light_krita_tool_transform.svg light_pattern.svg light_polyline.svg light_select.svg light_tool_contiguous_selection.svg light_tool_crop.svg light_tool_elliptical_selection.svg light_tool_outline_selection.svg + light_tool_magnetic_selection.svg light_tool_pan.svg light_tool_path_selection.svg light_tool_perspectivegrid.svg light_tool_polygonal_selection.svg light_tool_rect_selection.svg light_tool_similar_selection.svg light_tool_zoom.svg dark_shape_handling.svg dark_artistic_text.svg light_artistic_text.svg light_shape_handling.svg dark_krita_tool_lazybrush.svg light_krita_tool_lazybrush.svg dark_krita_tool_smart_patch.svg light_krita_tool_smart_patch.svg light_krita_tool_assistant.svg dark_krita_tool_assistant.svg dark_krita_tool_reference_images.svg light_krita_tool_reference_images.svg diff --git a/libs/basicflakes/CMakeLists.txt b/libs/basicflakes/CMakeLists.txt index edf42265a1..4ef11cea1f 100644 --- a/libs/basicflakes/CMakeLists.txt +++ b/libs/basicflakes/CMakeLists.txt @@ -1,38 +1,36 @@ -add_subdirectory(plugin) - include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/tools ${CMAKE_CURRENT_SOURCE_DIR}/plugin ) set(kritabasicflakes_LIB_SRCS tools/KoCreatePathTool.cpp tools/KoPencilTool.cpp ) ki18n_wrap_ui( kritabasicflakes_LIB_SRCS ) add_library(kritabasicflakes SHARED ${kritabasicflakes_LIB_SRCS}) generate_export_header(kritabasicflakes) target_include_directories(kritabasicflakes PUBLIC $ $ ) target_link_libraries(kritabasicflakes PUBLIC kritaui kritawidgets kritaflake kritapigment ) set_target_properties(kritabasicflakes PROPERTIES VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION} ) install(TARGETS kritabasicflakes ${INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/libs/basicflakes/plugin/CMakeLists.txt b/libs/basicflakes/plugin/CMakeLists.txt deleted file mode 100644 index 181df785a2..0000000000 --- a/libs/basicflakes/plugin/CMakeLists.txt +++ /dev/null @@ -1,16 +0,0 @@ -project( basicflakesplugin ) - -########### Basickritaflakes plugin library ############### - -set ( basicflakesplugin_SRCS - Plugin.cpp - KoCreatePathToolFactory.cpp - KoPencilToolFactory.cpp -) - -ki18n_wrap_ui(basicflakesplugin_SRCS -) - -add_library(krita_tool_basicflakes MODULE ${basicflakesplugin_SRCS}) -target_link_libraries(krita_tool_basicflakes kritaflake kritabasicflakes) -install(TARGETS krita_tool_basicflakes DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) diff --git a/libs/basicflakes/plugin/KoCreatePathToolFactory.cpp b/libs/basicflakes/plugin/KoCreatePathToolFactory.cpp deleted file mode 100644 index e9b9f8e11d..0000000000 --- a/libs/basicflakes/plugin/KoCreatePathToolFactory.cpp +++ /dev/null @@ -1,44 +0,0 @@ -/* This file is part of the KDE project - * - * Copyright (C) 2006 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 "KoCreatePathToolFactory.h" -#include "KoCreatePathTool.h" - -#include -#include - -KoCreatePathToolFactory::KoCreatePathToolFactory() - : KoToolFactoryBase(KoCreatePathTool_ID) -{ - setToolTip(i18n("Draw path")); - setSection(mainToolType()); - setPriority(4); - setIconName(koIconNameCStr("createpath")); - setActivationShapeId("flake/edit"); -} - -KoCreatePathToolFactory::~KoCreatePathToolFactory() -{ -} - -KoToolBase* KoCreatePathToolFactory::createTool(KoCanvasBase *canvas) -{ - return new KoCreatePathTool(canvas); -} diff --git a/libs/basicflakes/plugin/KoCreatePathToolFactory.h b/libs/basicflakes/plugin/KoCreatePathToolFactory.h deleted file mode 100644 index 0de423789c..0000000000 --- a/libs/basicflakes/plugin/KoCreatePathToolFactory.h +++ /dev/null @@ -1,37 +0,0 @@ -/* This file is part of the KDE project - * - * Copyright (C) 2006 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 KOCREATEPATHTOOLFACTORY_H -#define KOCREATEPATHTOOLFACTORY_H - -#include "KoToolFactoryBase.h" - -/// The factory for the KoCreatePathTool -class KoCreatePathToolFactory : public KoToolFactoryBase -{ -public: - /// Constructor - KoCreatePathToolFactory(); - /// Destructor - ~KoCreatePathToolFactory() override; - - KoToolBase* createTool(KoCanvasBase *canvas) override; -}; -#endif diff --git a/libs/basicflakes/plugin/KoPencilToolFactory.cpp b/libs/basicflakes/plugin/KoPencilToolFactory.cpp deleted file mode 100644 index 059c0f21ec..0000000000 --- a/libs/basicflakes/plugin/KoPencilToolFactory.cpp +++ /dev/null @@ -1,44 +0,0 @@ -/* This file is part of the KDE project - * Copyright (C) 2007 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 "KoPencilToolFactory.h" -#include "KoPencilTool.h" - -#include -#include - -KoPencilToolFactory::KoPencilToolFactory() - : KoToolFactoryBase("KoPencilTool") -{ - setToolTip(i18n("Freehand path")); - setSection("karbon, krita"); - setIconName(koIconNameCStr("draw-freehand")); - setPriority(3); - setActivationShapeId("flake/edit"); -} - -KoPencilToolFactory::~KoPencilToolFactory() -{ -} - -KoToolBase * KoPencilToolFactory::createTool(KoCanvasBase *canvas) -{ - return new KoPencilTool(canvas); -} - diff --git a/libs/basicflakes/plugin/KoPencilToolFactory.h b/libs/basicflakes/plugin/KoPencilToolFactory.h deleted file mode 100644 index 6f7416c0be..0000000000 --- a/libs/basicflakes/plugin/KoPencilToolFactory.h +++ /dev/null @@ -1,34 +0,0 @@ -/* This file is part of the KDE project - * Copyright (C) 2007 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. - */ - -#ifndef _KOPENCILTOOLFACTORY_H_ -#define _KOPENCILTOOLFACTORY_H_ - -#include - -class KoPencilToolFactory : public KoToolFactoryBase -{ -public: - KoPencilToolFactory(); - ~KoPencilToolFactory() override; - - KoToolBase * createTool(KoCanvasBase *canvas) override; -}; - -#endif // _KOPENCILTOOLFACTORY_H_ diff --git a/libs/basicflakes/plugin/Plugin.cpp b/libs/basicflakes/plugin/Plugin.cpp deleted file mode 100644 index f1663caa5e..0000000000 --- a/libs/basicflakes/plugin/Plugin.cpp +++ /dev/null @@ -1,37 +0,0 @@ -/* This file is part of the KDE project - * Copyright (C) 2007 Thomas Zander - * Copyright (C) 2012 C. Boemann - * - * 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 "Plugin.h" - -#include -#include -#include - -#include - -K_PLUGIN_FACTORY_WITH_JSON(PluginFactory, "krita_tool_basicflakes.json", registerPlugin();) - -Plugin::Plugin(QObject * parent, const QVariantList &) - : QObject(parent) -{ - KoToolRegistry::instance()->add(new KoCreatePathToolFactory()); - KoToolRegistry::instance()->add(new KoPencilToolFactory()); -} - -#include diff --git a/libs/basicflakes/plugin/Plugin.h b/libs/basicflakes/plugin/Plugin.h deleted file mode 100644 index c38223f7dc..0000000000 --- a/libs/basicflakes/plugin/Plugin.h +++ /dev/null @@ -1,33 +0,0 @@ -/* This file is part of the KDE project - * Copyright (C) 2007 Thomas Zander - * - * 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 PLUGIN_H -#define PLUGIN_H - -#include -#include - -class Plugin : public QObject -{ - Q_OBJECT - -public: - Plugin(QObject * parent, const QVariantList &); - ~Plugin() override {} -}; -#endif diff --git a/libs/basicflakes/plugin/krita_tool_basicflakes.json b/libs/basicflakes/plugin/krita_tool_basicflakes.json deleted file mode 100644 index 4e370f1059..0000000000 --- a/libs/basicflakes/plugin/krita_tool_basicflakes.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "Id": "Basic Flake Shapes", - "Type": "Service", - "X-Flake-MinVersion": "28", - "X-Flake-PluginVersion": "28", - "X-KDE-Library": "calligra_tool_basicflakes", - "X-KDE-PluginInfo-Name": "basicflakesplugin", - "X-KDE-ServiceTypes": [ - "Calligra/Tool" - ] -} diff --git a/libs/brush/kis_auto_brush.cpp b/libs/brush/kis_auto_brush.cpp index 72c445afa1..7fa8804edf 100644 --- a/libs/brush/kis_auto_brush.cpp +++ b/libs/brush/kis_auto_brush.cpp @@ -1,398 +1,398 @@ /* * 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 //MSVC requires that Vc come first #include "kis_auto_brush.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(_WIN32) || defined(_WIN64) #include #define srand48 srand inline double drand48() { return double(rand()) / RAND_MAX; } #endif struct KisAutoBrush::Private { Private() : randomness(0), density(1.0), idealThreadCountCached(1) {} Private(const Private &rhs) : shape(rhs.shape->clone()), randomness(rhs.randomness), density(rhs.density), idealThreadCountCached(rhs.idealThreadCountCached) { } QScopedPointer shape; qreal randomness; qreal density; int idealThreadCountCached; }; KisAutoBrush::KisAutoBrush(KisMaskGenerator* as, qreal angle, qreal randomness, qreal density) : KisBrush(), d(new Private) { d->shape.reset(as); d->randomness = randomness; d->density = density; d->idealThreadCountCached = QThread::idealThreadCount(); setBrushType(MASK); setWidth(qMax(qreal(1.0), d->shape->width())); setHeight(qMax(qreal(1.0), d->shape->height())); QImage image = createBrushPreview(); setBrushTipImage(image); // Set angle here so brush tip image is generated unrotated setAngle(angle); image = createBrushPreview(); setImage(image); } KisAutoBrush::~KisAutoBrush() { } qreal KisAutoBrush::userEffectiveSize() const { return d->shape->diameter(); } void KisAutoBrush::setUserEffectiveSize(qreal value) { d->shape->setDiameter(value); } KisAutoBrush::KisAutoBrush(const KisAutoBrush& rhs) : KisBrush(rhs), d(new Private(*rhs.d)) { } KisBrush* KisAutoBrush::clone() const { return new KisAutoBrush(*this); } -/* It's difficult to predict the mask height when exaclty when there are +/* It's difficult to predict the mask height when exactly when there are * more than 2 spikes, so we return an upperbound instead. */ static KisDabShape lieAboutDabShape(KisDabShape const& shape) { return KisDabShape(shape.scale(), 1.0, shape.rotation()); } qint32 KisAutoBrush::maskHeight(KisDabShape const& shape, qreal subPixelX, qreal subPixelY, const KisPaintInformation& info) const { return KisBrush::maskHeight( lieAboutDabShape(shape), subPixelX, subPixelY, info); } qint32 KisAutoBrush::maskWidth(KisDabShape const& shape, qreal subPixelX, qreal subPixelY, const KisPaintInformation& info) const { return KisBrush::maskWidth( lieAboutDabShape(shape), subPixelX, subPixelY, info); } QSizeF KisAutoBrush::characteristicSize(KisDabShape const& shape) const { return KisBrush::characteristicSize(lieAboutDabShape(shape)); } inline void fillPixelOptimized_4bytes(quint8 *color, quint8 *buf, int size) { /** * This version of filling uses low granularity of data transfers * (32-bit chunks) and internal processor's parallelism. It reaches * 25% better performance in KisStrokeBenchmark in comparison to * per-pixel memcpy version (tested on Sandy Bridge). */ int block1 = size / 8; int block2 = size % 8; quint32 *src = reinterpret_cast(color); quint32 *dst = reinterpret_cast(buf); // check whether all buffers are 4 bytes aligned // (uncomment if experience some problems) // Q_ASSERT(((qint64)src & 3) == 0); // Q_ASSERT(((qint64)dst & 3) == 0); for (int i = 0; i < block1; i++) { *dst = *src; *(dst + 1) = *src; *(dst + 2) = *src; *(dst + 3) = *src; *(dst + 4) = *src; *(dst + 5) = *src; *(dst + 6) = *src; *(dst + 7) = *src; dst += 8; } for (int i = 0; i < block2; i++) { *dst = *src; dst++; } } inline void fillPixelOptimized_general(quint8 *color, quint8 *buf, int size, int pixelSize) { /** * This version uses internal processor's parallelism and gives * 20% better performance in KisStrokeBenchmark in comparison to * per-pixel memcpy version (tested on Sandy Bridge (+20%) and * on Merom (+10%)). */ int block1 = size / 8; int block2 = size % 8; for (int i = 0; i < block1; i++) { quint8 *d1 = buf; quint8 *d2 = buf + pixelSize; quint8 *d3 = buf + 2 * pixelSize; quint8 *d4 = buf + 3 * pixelSize; quint8 *d5 = buf + 4 * pixelSize; quint8 *d6 = buf + 5 * pixelSize; quint8 *d7 = buf + 6 * pixelSize; quint8 *d8 = buf + 7 * pixelSize; for (int j = 0; j < pixelSize; j++) { *(d1 + j) = color[j]; *(d2 + j) = color[j]; *(d3 + j) = color[j]; *(d4 + j) = color[j]; *(d5 + j) = color[j]; *(d6 + j) = color[j]; *(d7 + j) = color[j]; *(d8 + j) = color[j]; } buf += 8 * pixelSize; } for (int i = 0; i < block2; i++) { memcpy(buf, color, pixelSize); buf += pixelSize; } } void KisAutoBrush::generateMaskAndApplyMaskOrCreateDab(KisFixedPaintDeviceSP dst, KisBrush::ColoringInformation* coloringInformation, KisDabShape const& shape, const KisPaintInformation& info, double subPixelX , double subPixelY, qreal softnessFactor) const { Q_UNUSED(info); // Generate the paint device from the mask const KoColorSpace* cs = dst->colorSpace(); quint32 pixelSize = cs->pixelSize(); // mask dimension methods already includes KisBrush::angle() int dstWidth = maskWidth(shape, subPixelX, subPixelY, info); int dstHeight = maskHeight(shape, subPixelX, subPixelY, info); QPointF hotSpot = this->hotSpot(shape, info); // mask size and hotSpot function take the KisBrush rotation into account qreal angle = shape.rotation() + KisBrush::angle(); // if there's coloring information, we merely change the alpha: in that case, // the dab should be big enough! if (coloringInformation) { // new bounds. we don't care if there is some extra memory occcupied. dst->setRect(QRect(0, 0, dstWidth, dstHeight)); dst->lazyGrowBufferWithoutInitialization(); } else { KIS_SAFE_ASSERT_RECOVER_RETURN(dst->bounds().width() >= dstWidth && dst->bounds().height() >= dstHeight); } quint8* dabPointer = dst->data(); quint8* color = 0; if (coloringInformation) { if (dynamic_cast(coloringInformation)) { color = const_cast(coloringInformation->color()); } } double centerX = hotSpot.x() - 0.5 + subPixelX; double centerY = hotSpot.y() - 0.5 + subPixelY; d->shape->setSoftness(softnessFactor); // softness must be set first d->shape->setScale(shape.scaleX(), shape.scaleY()); if (coloringInformation) { if (color && pixelSize == 4) { fillPixelOptimized_4bytes(color, dabPointer, dstWidth * dstHeight); } else if (color) { fillPixelOptimized_general(color, dabPointer, dstWidth * dstHeight, pixelSize); } else { for (int y = 0; y < dstHeight; y++) { for (int x = 0; x < dstWidth; x++) { memcpy(dabPointer, coloringInformation->color(), pixelSize); coloringInformation->nextColumn(); dabPointer += pixelSize; } coloringInformation->nextRow(); } } } MaskProcessingData data(dst, cs, d->randomness, d->density, centerX, centerY, angle); KisBrushMaskApplicatorBase *applicator = d->shape->applicator(); applicator->initializeData(&data); int jobs = d->idealThreadCountCached; if (threadingAllowed() && dstHeight > 100 && jobs >= 4) { int splitter = dstHeight / jobs; QVector rects; for (int i = 0; i < jobs - 1; i++) { rects << QRect(0, i * splitter, dstWidth, splitter); } rects << QRect(0, (jobs - 1)*splitter, dstWidth, dstHeight - (jobs - 1)*splitter); OperatorWrapper wrapper(applicator); QtConcurrent::blockingMap(rects, wrapper); } else { QRect rect(0, 0, dstWidth, dstHeight); applicator->process(rect); } } void KisAutoBrush::toXML(QDomDocument& doc, QDomElement& e) const { QDomElement shapeElt = doc.createElement("MaskGenerator"); d->shape->toXML(doc, shapeElt); e.appendChild(shapeElt); e.setAttribute("type", "auto_brush"); e.setAttribute("spacing", QString::number(spacing())); e.setAttribute("useAutoSpacing", QString::number(autoSpacingActive())); e.setAttribute("autoSpacingCoeff", QString::number(autoSpacingCoeff())); e.setAttribute("angle", QString::number(KisBrush::angle())); e.setAttribute("randomness", QString::number(d->randomness)); e.setAttribute("density", QString::number(d->density)); KisBrush::toXML(doc, e); } QImage KisAutoBrush::createBrushPreview() { int width = maskWidth(KisDabShape(), 0.0, 0.0, KisPaintInformation()); int height = maskHeight(KisDabShape(), 0.0, 0.0, KisPaintInformation()); KisPaintInformation info(QPointF(width * 0.5, height * 0.5), 0.5, 0, 0, angle(), 0, 0, 0, 0); KisFixedPaintDeviceSP fdev = new KisFixedPaintDevice(KoColorSpaceRegistry::instance()->rgb8()); fdev->setRect(QRect(0, 0, width, height)); fdev->initialize(); mask(fdev, KoColor(Qt::black, fdev->colorSpace()), KisDabShape(), info); return fdev->convertToQImage(0); } const KisMaskGenerator* KisAutoBrush::maskGenerator() const { return d->shape.data(); } qreal KisAutoBrush::density() const { return d->density; } qreal KisAutoBrush::randomness() const { return d->randomness; } QPainterPath KisAutoBrush::outline() const { bool simpleOutline = (d->density < 1.0); if (simpleOutline) { QPainterPath path; QRectF brushBoundingbox(0, 0, width(), height()); if (maskGenerator()->type() == KisMaskGenerator::CIRCLE) { path.addEllipse(brushBoundingbox); } else { // if (maskGenerator()->type() == KisMaskGenerator::RECTANGLE) path.addRect(brushBoundingbox); } return path; } return KisBrush::boundary()->path(); } void KisAutoBrush::lodLimitations(KisPaintopLodLimitations *l) const { KisBrush::lodLimitations(l); if (!qFuzzyCompare(density(), 1.0)) { l->limitations << KoID("auto-brush-density", i18nc("PaintOp instant preview limitation", "Brush Density recommended value 100.0")); } if (!qFuzzyCompare(randomness(), 0.0)) { l->limitations << KoID("auto-brush-randomness", i18nc("PaintOp instant preview limitation", "Brush Randomness recommended value 0.0")); } } diff --git a/libs/flake/CMakeLists.txt b/libs/flake/CMakeLists.txt index a3b3ec6bb0..1926026a53 100644 --- a/libs/flake/CMakeLists.txt +++ b/libs/flake/CMakeLists.txt @@ -1,252 +1,249 @@ project(kritaflake) include_directories( ${CMAKE_SOURCE_DIR}/libs/flake/commands ${CMAKE_SOURCE_DIR}/libs/flake/tools ${CMAKE_SOURCE_DIR}/libs/flake/svg ${CMAKE_SOURCE_DIR}/libs/flake/text ${CMAKE_BINARY_DIR}/libs/flake ) add_subdirectory(styles) add_subdirectory(tests) add_subdirectory(resources/tests) set(kritaflake_SRCS KoGradientHelper.cpp KoFlake.cpp KoCanvasBase.cpp KoResourceManager_p.cpp KoDerivedResourceConverter.cpp KoResourceUpdateMediator.cpp KoCanvasResourceProvider.cpp KoDocumentResourceManager.cpp KoCanvasObserverBase.cpp KoCanvasSupervisor.cpp KoDockFactoryBase.cpp KoDockRegistry.cpp KoDataCenterBase.cpp KoInsets.cpp KoPathShape.cpp KoPathPoint.cpp KoPathSegment.cpp KoSelection.cpp KoSelectedShapesProxy.cpp KoSelectedShapesProxySimple.cpp KoShape.cpp KoShapeAnchor.cpp KoShapeControllerBase.cpp KoShapeApplicationData.cpp KoShapeContainer.cpp KoShapeContainerModel.cpp KoShapeGroup.cpp KoShapeManager.cpp KoShapePaintingContext.cpp KoFrameShape.cpp KoMarker.cpp KoMarkerCollection.cpp KoToolBase.cpp KoCanvasController.cpp KoCanvasControllerWidget.cpp KoCanvasControllerWidgetViewport_p.cpp KoShapeRegistry.cpp KoDeferredShapeFactoryBase.cpp KoToolFactoryBase.cpp KoPathShapeFactory.cpp KoShapeFactoryBase.cpp KoShapeUserData.cpp KoParameterShape.cpp KoPointerEvent.cpp KoShapeController.cpp KoToolSelection.cpp KoShapeLayer.cpp KoPostscriptPaintDevice.cpp KoInputDevice.cpp KoToolManager_p.cpp KoToolManager.cpp KoToolRegistry.cpp KoToolProxy.cpp KoShapeSavingContext.cpp KoShapeLoadingContext.cpp KoLoadingShapeUpdater.cpp KoPathShapeLoader.cpp KoShapeStrokeModel.cpp KoShapeStroke.cpp KoShapeBackground.cpp KoColorBackground.cpp KoGradientBackground.cpp KoOdfGradientBackground.cpp KoHatchBackground.cpp KoPatternBackground.cpp KoVectorPatternBackground.cpp KoShapeFillWrapper.cpp KoShapeFillResourceConnector.cpp KoShapeConfigWidgetBase.cpp KoDrag.cpp KoSvgPaste.cpp KoDragOdfSaveHelper.cpp KoShapeOdfSaveHelper.cpp KoConnectionPoint.cpp KoConnectionShape.cpp KoConnectionShapeLoadingUpdater.cpp KoConnectionShapeFactory.cpp KoConnectionShapeConfigWidget.cpp KoSnapGuide.cpp KoSnapProxy.cpp KoSnapStrategy.cpp KoSnapData.cpp KoShapeShadow.cpp KoSharedLoadingData.cpp KoSharedSavingData.cpp KoViewConverter.cpp KoInputDeviceHandler.cpp KoInputDeviceHandlerEvent.cpp KoInputDeviceHandlerRegistry.cpp KoImageData.cpp KoImageData_p.cpp KoImageCollection.cpp KoOdfWorkaround.cpp KoFilterEffect.cpp KoFilterEffectStack.cpp KoFilterEffectFactoryBase.cpp KoFilterEffectRegistry.cpp KoFilterEffectConfigWidgetBase.cpp KoFilterEffectRenderContext.cpp KoFilterEffectLoadingContext.cpp KoTextShapeDataBase.cpp KoTosContainer.cpp KoTosContainerModel.cpp KoClipPath.cpp KoClipMask.cpp KoClipMaskPainter.cpp KoCurveFit.cpp KisGamutMaskViewConverter.cpp commands/KoShapeGroupCommand.cpp commands/KoShapeAlignCommand.cpp commands/KoShapeBackgroundCommand.cpp commands/KoShapeCreateCommand.cpp commands/KoShapeDeleteCommand.cpp commands/KoShapeDistributeCommand.cpp commands/KoShapeLockCommand.cpp commands/KoShapeMoveCommand.cpp commands/KoShapeResizeCommand.cpp commands/KoShapeShearCommand.cpp commands/KoShapeSizeCommand.cpp commands/KoShapeStrokeCommand.cpp commands/KoShapeUngroupCommand.cpp commands/KoShapeReorderCommand.cpp commands/KoShapeKeepAspectRatioCommand.cpp commands/KoPathBaseCommand.cpp commands/KoPathPointMoveCommand.cpp commands/KoPathControlPointMoveCommand.cpp commands/KoPathPointTypeCommand.cpp commands/KoPathPointRemoveCommand.cpp commands/KoPathPointInsertCommand.cpp commands/KoPathSegmentBreakCommand.cpp commands/KoPathBreakAtPointCommand.cpp commands/KoPathSegmentTypeCommand.cpp commands/KoPathCombineCommand.cpp commands/KoSubpathRemoveCommand.cpp commands/KoSubpathJoinCommand.cpp commands/KoParameterHandleMoveCommand.cpp commands/KoParameterToPathCommand.cpp commands/KoShapeTransformCommand.cpp commands/KoPathFillRuleCommand.cpp commands/KoConnectionShapeTypeCommand.cpp commands/KoShapeShadowCommand.cpp commands/KoPathReverseCommand.cpp commands/KoShapeRenameCommand.cpp commands/KoShapeRunAroundCommand.cpp commands/KoPathPointMergeCommand.cpp commands/KoShapeTransparencyCommand.cpp commands/KoShapeClipCommand.cpp commands/KoShapeUnclipCommand.cpp commands/KoPathShapeMarkerCommand.cpp commands/KoShapeConnectionChangeCommand.cpp commands/KoMultiPathPointMergeCommand.cpp commands/KoMultiPathPointJoinCommand.cpp commands/KoKeepShapesSelectedCommand.cpp commands/KoPathMergeUtils.cpp html/HtmlSavingContext.cpp html/HtmlWriter.cpp - tools/KoCreateShapeStrategy.cpp tools/KoPathToolFactory.cpp tools/KoPathTool.cpp tools/KoPathToolSelection.cpp tools/KoPathToolHandle.cpp tools/PathToolOptionWidget.cpp tools/KoPathPointRubberSelectStrategy.cpp tools/KoPathPointMoveStrategy.cpp tools/KoPathConnectionPointStrategy.cpp tools/KoPathControlPointMoveStrategy.cpp tools/KoParameterChangeStrategy.cpp tools/KoZoomTool.cpp tools/KoZoomToolFactory.cpp tools/KoZoomToolWidget.cpp tools/KoZoomStrategy.cpp tools/KoInteractionTool.cpp tools/KoInteractionStrategy.cpp tools/KoInteractionStrategyFactory.cpp - tools/KoCreateShapesTool.cpp - tools/KoCreateShapesToolFactory.cpp tools/KoShapeRubberSelectStrategy.cpp tools/KoPathSegmentChangeStrategy.cpp svg/KoShapePainter.cpp svg/SvgUtil.cpp svg/SvgGraphicContext.cpp svg/SvgSavingContext.cpp svg/SvgWriter.cpp svg/SvgStyleWriter.cpp svg/SvgShape.cpp svg/SvgParser.cpp svg/SvgStyleParser.cpp svg/SvgGradientHelper.cpp svg/SvgFilterHelper.cpp svg/SvgCssHelper.cpp svg/SvgClipPathHelper.cpp svg/SvgLoadingContext.cpp svg/SvgShapeFactory.cpp svg/parsers/SvgTransformParser.cpp text/KoSvgText.cpp text/KoSvgTextProperties.cpp text/KoSvgTextChunkShape.cpp text/KoSvgTextShape.cpp text/KoSvgTextShapeMarkupConverter.cpp resources/KoSvgSymbolCollectionResource.cpp resources/KoGamutMask.cpp FlakeDebug.cpp tests/MockShapes.cpp ) ki18n_wrap_ui(kritaflake_SRCS tools/PathToolOptionWidgetBase.ui KoConnectionShapeConfigWidget.ui tools/KoZoomToolWidget.ui ) add_library(kritaflake SHARED ${kritaflake_SRCS}) generate_export_header(kritaflake BASE_NAME kritaflake) target_include_directories(kritaflake PUBLIC $ $ $ $ ) target_link_libraries(kritaflake kritapigment kritawidgetutils kritaodf kritacommand KF5::WidgetsAddons Qt5::Svg) set_target_properties(kritaflake PROPERTIES VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION} ) install(TARGETS kritaflake ${INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/libs/flake/KoParameterShape.h b/libs/flake/KoParameterShape.h index 6af45a81cb..bc58824a75 100644 --- a/libs/flake/KoParameterShape.h +++ b/libs/flake/KoParameterShape.h @@ -1,163 +1,163 @@ /* This file is part of the KDE project Copyright (C) 2006 Thorsten Zachmann Copyright (C) 2007 Thomas Zander 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 KOPARAMETERSHAPE_H #define KOPARAMETERSHAPE_H #include "KoPathShape.h" #include "kritaflake_export.h" class KoParameterShapePrivate; class KisHandlePainterHelper; /** * KoParameterShape is the base class for all parametric shapes * in flake. * Parametric shapes are those whose appearance can be completely * defined by a few numerical parameters. Rectangle, ellipse and star * are examples of parametric shapes. * In flake, these shape parameters can be manipulated visually by means * of control points. These control points can be moved with the mouse * on the canvas which changes the shapes parameter values and hence the * shapes appearance in realtime. * KoParameterShape is derived from the KoPathShape class that means * by changing the shape parameters, the underlying path is manipulated. * A parametric shape can be converted into a path shape by simply calling * the setModified method. This makes the path tool know that it can handle * the shape like a path shape, so that modifying the single path points * is possible. */ class KRITAFLAKE_EXPORT KoParameterShape : public KoPathShape { public: KoParameterShape(); ~KoParameterShape() override; /** * @brief Move handle to point * * This method calls moveHandleAction. Overload moveHandleAction to get the behaviour you want. * After that updatePath and a repaint is called. * * @param handleId the id of the handle to move * @param point the point to move the handle to in document coordinates * @param modifiers the keyboard modifiers used during moving the handle */ void moveHandle(int handleId, const QPointF &point, Qt::KeyboardModifiers modifiers = Qt::NoModifier); /** * @brief Get the id of the handle within the given rect * * @param rect the rect in shape coordinates * @return id of the found handle or -1 if none was found */ int handleIdAt(const QRectF &rect) const; /** * @brief Get the handle position * * @param handleId the id of the handle for which to get the position in shape coordinates */ QPointF handlePosition(int handleId) const; /** * @brief Paint the handles * * @param handlesHelper the helper of the handles used for painting * @sa KisHandlePainterHelper */ void paintHandles(KisHandlePainterHelper &handlesHelper); /** * @brief Paint the given handles * * @param handlesHelper the helper of the handle used for painting * @param handleId of the handle which should be repainted */ void paintHandle(KisHandlePainterHelper &handlesHelper, int handleId); /// reimplemented from KoShape void setSize(const QSizeF &size) override; /** * @brief Check if object is a parametric shape * * It is no longer a parametric shape when the path was manipulated * - * @return true if it is a parametic shape, false otherwise + * @return true if it is a parametric shape, false otherwise */ bool isParametricShape() const; /** * @brief Set if the shape can be modified using parameters * * After the state is set to false it is no longer possible to work * with parameters on this shape. * * @param parametric the new state * @see isParametricShape */ void setParametricShape(bool parametric); QPointF normalize() override; /// return the number of handles set on the shape int handleCount() const; protected: /** * Get the handle positions for manipulating the parameters. * @see setHandles, handleCount() */ QList handles() const; /** * Set the new handle positions which are used by the user to manipulate the parameters. * @see handles(), handleCount() */ void setHandles(const QList &handles); /// constructor KoParameterShape(const KoParameterShape &rhs); /** * @brief Updates the internal state of a KoParameterShape. * * This method is called from moveHandle. * * @param handleId of the handle * @param point to move the handle to in shape coordinates * @param modifiers used during move to point */ virtual void moveHandleAction(int handleId, const QPointF & point, Qt::KeyboardModifiers modifiers = Qt::NoModifier) = 0; /** * @brief Update the path of the parameter shape * * @param size of the shape */ virtual void updatePath(const QSizeF &size) = 0; private: class Private; QSharedDataPointer d; }; #endif /* KOPARAMETERSHAPE_H */ diff --git a/libs/flake/KoPathShape.cpp b/libs/flake/KoPathShape.cpp index fb6731bf91..bb955d7e9d 100644 --- a/libs/flake/KoPathShape.cpp +++ b/libs/flake/KoPathShape.cpp @@ -1,1654 +1,1656 @@ /* This file is part of the KDE project Copyright (C) 2006-2008, 2010-2011 Thorsten Zachmann Copyright (C) 2006-2011 Jan Hambrecht Copyright (C) 2007-2009 Thomas Zander Copyright (C) 2011 Jean-Nicolas Artaud 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 "KoPathShape.h" #include "KoPathShape_p.h" #include "KoPathSegment.h" #include "KoOdfWorkaround.h" #include "KoPathPoint.h" #include "KoShapeStrokeModel.h" #include "KoViewConverter.h" #include "KoPathShapeLoader.h" #include "KoShapeSavingContext.h" #include "KoShapeLoadingContext.h" #include "KoShapeShadow.h" #include "KoShapeBackground.h" #include "KoShapeContainer.h" #include "KoFilterEffectStack.h" #include "KoMarker.h" #include "KoShapeStroke.h" #include "KoInsets.h" #include #include #include #include #include #include #include #include "KisQPainterStateSaver.h" #include #include #include "kis_global.h" #include // for qIsNaN static bool qIsNaNPoint(const QPointF &p) { return qIsNaN(p.x()) || qIsNaN(p.y()); } KoPathShape::Private::Private() : QSharedData() , fillRule(Qt::OddEvenFill) , autoFillMarkers(false) { } KoPathShape::Private::Private(const Private &rhs) : QSharedData() , fillRule(rhs.fillRule) , markersNew(rhs.markersNew) , autoFillMarkers(rhs.autoFillMarkers) { } QRectF KoPathShape::Private::handleRect(const QPointF &p, qreal radius) const { return QRectF(p.x() - radius, p.y() - radius, 2*radius, 2*radius); } void KoPathShape::Private::applyViewboxTransformation(const KoXmlElement &element) { // apply viewbox transformation const QRect viewBox = KoPathShape::loadOdfViewbox(element); if (! viewBox.isEmpty()) { // load the desired size QSizeF size; size.setWidth(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "width", QString()))); size.setHeight(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "height", QString()))); // load the desired position QPointF pos; pos.setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x", QString()))); pos.setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y", QString()))); // create matrix to transform original path data into desired size and position QTransform viewMatrix; viewMatrix.translate(-viewBox.left(), -viewBox.top()); viewMatrix.scale(size.width() / viewBox.width(), size.height() / viewBox.height()); viewMatrix.translate(pos.x(), pos.y()); // transform the path data map(viewMatrix); } } KoPathShape::KoPathShape() : KoTosContainer() , d(new Private) { } KoPathShape::KoPathShape(const KoPathShape &rhs) : KoTosContainer(rhs) , d(rhs.d) { KoSubpathList subpaths; Q_FOREACH (KoSubpath *subPath, rhs.d->subpaths) { KoSubpath *clonedSubPath = new KoSubpath(); Q_FOREACH (KoPathPoint *point, *subPath) { *clonedSubPath << new KoPathPoint(*point, this); } subpaths << clonedSubPath; } d->subpaths = subpaths; } KoPathShape::~KoPathShape() { clear(); } KoShape *KoPathShape::cloneShape() const { return new KoPathShape(*this); } void KoPathShape::saveContourOdf(KoShapeSavingContext &context, const QSizeF &scaleFactor) const { if (d->subpaths.length() <= 1) { QTransform matrix; matrix.scale(scaleFactor.width(), scaleFactor.height()); QString points; KoSubpath *subPath = d->subpaths.first(); KoSubpath::const_iterator pointIt(subPath->constBegin()); KoPathPoint *currPoint= 0; // iterate over all points for (; pointIt != subPath->constEnd(); ++pointIt) { currPoint = *pointIt; if (currPoint->activeControlPoint1() || currPoint->activeControlPoint2()) { break; } const QPointF p = matrix.map(currPoint->point()); points += QString("%1,%2 ").arg(qRound(1000*p.x())).arg(qRound(1000*p.y())); } if (currPoint && !(currPoint->activeControlPoint1() || currPoint->activeControlPoint2())) { context.xmlWriter().startElement("draw:contour-polygon"); context.xmlWriter().addAttribute("svg:width", size().width()); context.xmlWriter().addAttribute("svg:height", size().height()); const QSizeF s(size()); QString viewBox = QString("0 0 %1 %2").arg(qRound(1000*s.width())).arg(qRound(1000*s.height())); context.xmlWriter().addAttribute("svg:viewBox", viewBox); context.xmlWriter().addAttribute("draw:points", points); context.xmlWriter().addAttribute("draw:recreate-on-edit", "true"); context.xmlWriter().endElement(); return; } } // if we get here we couldn't save as polygon - let-s try contour-path context.xmlWriter().startElement("draw:contour-path"); saveOdfAttributes(context, OdfViewbox); context.xmlWriter().addAttribute("svg:d", toString()); context.xmlWriter().addAttribute("calligra:nodeTypes", d->nodeTypes()); context.xmlWriter().addAttribute("draw:recreate-on-edit", "true"); context.xmlWriter().endElement(); } void KoPathShape::saveOdf(KoShapeSavingContext & context) const { context.xmlWriter().startElement("draw:path"); saveOdfAttributes(context, OdfAllAttributes | OdfViewbox); context.xmlWriter().addAttribute("svg:d", toString()); context.xmlWriter().addAttribute("calligra:nodeTypes", d->nodeTypes()); saveOdfCommonChildElements(context); saveText(context); context.xmlWriter().endElement(); } bool KoPathShape::loadContourOdf(const KoXmlElement &element, KoShapeLoadingContext &, const QSizeF &scaleFactor) { // first clear the path data from the default path clear(); if (element.localName() == "contour-polygon") { QString points = element.attributeNS(KoXmlNS::draw, "points").simplified(); points.replace(',', ' '); points.remove('\r'); points.remove('\n'); bool firstPoint = true; const QStringList coordinateList = points.split(' '); for (QStringList::ConstIterator it = coordinateList.constBegin(); it != coordinateList.constEnd(); ++it) { QPointF point; point.setX((*it).toDouble()); ++it; point.setY((*it).toDouble()); if (firstPoint) { moveTo(point); firstPoint = false; } else lineTo(point); } close(); } else if (element.localName() == "contour-path") { KoPathShapeLoader loader(this); loader.parseSvg(element.attributeNS(KoXmlNS::svg, "d"), true); d->loadNodeTypes(element); } // apply viewbox transformation const QRect viewBox = KoPathShape::loadOdfViewbox(element); if (! viewBox.isEmpty()) { QSizeF size; size.setWidth(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "width", QString()))); size.setHeight(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "height", QString()))); // create matrix to transform original path data into desired size and position QTransform viewMatrix; viewMatrix.translate(-viewBox.left(), -viewBox.top()); viewMatrix.scale(scaleFactor.width(), scaleFactor.height()); viewMatrix.scale(size.width() / viewBox.width(), size.height() / viewBox.height()); // transform the path data d->map(viewMatrix); } setTransformation(QTransform()); return true; } bool KoPathShape::loadOdf(const KoXmlElement & element, KoShapeLoadingContext &context) { loadOdfAttributes(element, context, OdfMandatories | OdfAdditionalAttributes | OdfCommonChildElements); // first clear the path data from the default path clear(); if (element.localName() == "line") { QPointF start; start.setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x1", ""))); start.setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y1", ""))); QPointF end; end.setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x2", ""))); end.setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y2", ""))); moveTo(start); lineTo(end); } else if (element.localName() == "polyline" || element.localName() == "polygon") { QString points = element.attributeNS(KoXmlNS::draw, "points").simplified(); points.replace(',', ' '); points.remove('\r'); points.remove('\n'); bool firstPoint = true; const QStringList coordinateList = points.split(' '); for (QStringList::ConstIterator it = coordinateList.constBegin(); it != coordinateList.constEnd(); ++it) { QPointF point; point.setX((*it).toDouble()); ++it; point.setY((*it).toDouble()); if (firstPoint) { moveTo(point); firstPoint = false; } else lineTo(point); } if (element.localName() == "polygon") close(); } else { // path loading KoPathShapeLoader loader(this); loader.parseSvg(element.attributeNS(KoXmlNS::svg, "d"), true); d->loadNodeTypes(element); } d->applyViewboxTransformation(element); QPointF pos = normalize(); setTransformation(QTransform()); if (element.hasAttributeNS(KoXmlNS::svg, "x") || element.hasAttributeNS(KoXmlNS::svg, "y")) { pos.setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x", QString()))); pos.setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y", QString()))); } setPosition(pos); loadOdfAttributes(element, context, OdfTransformation); // now that the correct transformation is set up // apply that matrix to the path geometry so that // we don't transform the stroke d->map(transformation()); setTransformation(QTransform()); normalize(); loadText(element, context); return true; } QString KoPathShape::saveStyle(KoGenStyle &style, KoShapeSavingContext &context) const { style.addProperty("svg:fill-rule", d->fillRule == Qt::OddEvenFill ? "evenodd" : "nonzero"); QSharedPointer lineBorder = qSharedPointerDynamicCast(stroke()); qreal lineWidth = 0; if (lineBorder) { lineWidth = lineBorder->lineWidth(); } Q_UNUSED(lineWidth) return KoTosContainer::saveStyle(style, context); } void KoPathShape::loadStyle(const KoXmlElement & element, KoShapeLoadingContext &context) { KoTosContainer::loadStyle(element, context); KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); styleStack.setTypeProperties("graphic"); if (styleStack.hasProperty(KoXmlNS::svg, "fill-rule")) { QString rule = styleStack.property(KoXmlNS::svg, "fill-rule"); d->fillRule = (rule == "nonzero") ? Qt::WindingFill : Qt::OddEvenFill; } else { d->fillRule = Qt::WindingFill; #ifndef NWORKAROUND_ODF_BUGS KoOdfWorkaround::fixMissingFillRule(d->fillRule, context); #endif } QSharedPointer lineBorder = qSharedPointerDynamicCast(stroke()); qreal lineWidth = 0; if (lineBorder) { lineWidth = lineBorder->lineWidth(); } Q_UNUSED(lineWidth); } QRect KoPathShape::loadOdfViewbox(const KoXmlElement & element) { QRect viewbox; QString data = element.attributeNS(KoXmlNS::svg, QLatin1String("viewBox")); if (! data.isEmpty()) { data.replace(QLatin1Char(','), QLatin1Char(' ')); const QStringList coordinates = data.simplified().split(QLatin1Char(' '), QString::SkipEmptyParts); if (coordinates.count() == 4) { viewbox.setRect(coordinates.at(0).toInt(), coordinates.at(1).toInt(), coordinates.at(2).toInt(), coordinates.at(3).toInt()); } } return viewbox; } void KoPathShape::clear() { Q_FOREACH (KoSubpath *subpath, d->subpaths) { Q_FOREACH (KoPathPoint *point, *subpath) delete point; delete subpath; } d->subpaths.clear(); notifyPointsChanged(); } void KoPathShape::paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) { KisQPainterStateSaver saver(&painter); applyConversion(painter, converter); QPainterPath path(outline()); path.setFillRule(d->fillRule); if (background()) { background()->paint(painter, converter, paintContext, path); } //d->paintDebug(painter); } #ifndef NDEBUG void KoPathShape::Private::paintDebug(QPainter &painter) { KoSubpathList::const_iterator pathIt(subpaths.constBegin()); int i = 0; QPen pen(Qt::black, 0); painter.save(); painter.setPen(pen); for (; pathIt != subpaths.constEnd(); ++pathIt) { KoSubpath::const_iterator it((*pathIt)->constBegin()); for (; it != (*pathIt)->constEnd(); ++it) { ++i; KoPathPoint *point = (*it); QRectF r(point->point(), QSizeF(5, 5)); r.translate(-2.5, -2.5); QPen pen(Qt::black, 0); painter.setPen(pen); if (point->activeControlPoint1() && point->activeControlPoint2()) { QBrush b(Qt::red); painter.setBrush(b); } else if (point->activeControlPoint1()) { QBrush b(Qt::yellow); painter.setBrush(b); } else if (point->activeControlPoint2()) { QBrush b(Qt::darkYellow); painter.setBrush(b); } painter.drawEllipse(r); } } painter.restore(); debugFlake << "nop =" << i; } void KoPathShape::Private::debugPath() const { KoSubpathList::const_iterator pathIt(subpaths.constBegin()); for (; pathIt != subpaths.constEnd(); ++pathIt) { KoSubpath::const_iterator it((*pathIt)->constBegin()); for (; it != (*pathIt)->constEnd(); ++it) { debugFlake << "p:" << (*pathIt) << "," << *it << "," << (*it)->point() << "," << (*it)->properties(); } } } #endif void KoPathShape::paintPoints(KisHandlePainterHelper &handlesHelper) { KoSubpathList::const_iterator pathIt(d->subpaths.constBegin()); for (; pathIt != d->subpaths.constEnd(); ++pathIt) { KoSubpath::const_iterator it((*pathIt)->constBegin()); for (; it != (*pathIt)->constEnd(); ++it) (*it)->paint(handlesHelper, KoPathPoint::Node); } } QRectF KoPathShape::outlineRect() const { return outline().boundingRect(); } QPainterPath KoPathShape::outline() const { QPainterPath path; - Q_FOREACH (KoSubpath * subpath, d->subpaths) { - KoPathPoint * lastPoint = subpath->first(); + for (auto subpathIt = d->subpaths.constBegin(); subpathIt != d->subpaths.constEnd(); ++subpathIt) { + const KoSubpath * subpath = *subpathIt; + const KoPathPoint * lastPoint = subpath->constFirst(); bool activeCP = false; - Q_FOREACH (KoPathPoint * currPoint, *subpath) { + for (auto pointIt = subpath->constBegin(); pointIt != subpath->constEnd(); ++pointIt) { + const KoPathPoint * currPoint = *pointIt; KoPathPoint::PointProperties currProperties = currPoint->properties(); - if (currPoint == subpath->first()) { + if (currPoint == subpath->constFirst()) { if (currProperties & KoPathPoint::StartSubpath) { Q_ASSERT(!qIsNaNPoint(currPoint->point())); path.moveTo(currPoint->point()); } } else if (activeCP && currPoint->activeControlPoint1()) { Q_ASSERT(!qIsNaNPoint(lastPoint->controlPoint2())); Q_ASSERT(!qIsNaNPoint(currPoint->controlPoint1())); Q_ASSERT(!qIsNaNPoint(currPoint->point())); path.cubicTo( lastPoint->controlPoint2(), currPoint->controlPoint1(), currPoint->point()); } else if (activeCP || currPoint->activeControlPoint1()) { Q_ASSERT(!qIsNaNPoint(lastPoint->controlPoint2())); Q_ASSERT(!qIsNaNPoint(currPoint->controlPoint1())); path.quadTo( activeCP ? lastPoint->controlPoint2() : currPoint->controlPoint1(), currPoint->point()); } else { Q_ASSERT(!qIsNaNPoint(currPoint->point())); path.lineTo(currPoint->point()); } if (currProperties & KoPathPoint::CloseSubpath && currProperties & KoPathPoint::StopSubpath) { // add curve when there is a curve on the way to the first point KoPathPoint * firstPoint = subpath->first(); Q_ASSERT(!qIsNaNPoint(firstPoint->point())); if (currPoint->activeControlPoint2() && firstPoint->activeControlPoint1()) { path.cubicTo( currPoint->controlPoint2(), firstPoint->controlPoint1(), firstPoint->point()); } else if (currPoint->activeControlPoint2() || firstPoint->activeControlPoint1()) { Q_ASSERT(!qIsNaNPoint(currPoint->point())); Q_ASSERT(!qIsNaNPoint(currPoint->controlPoint1())); path.quadTo( currPoint->activeControlPoint2() ? currPoint->controlPoint2() : firstPoint->controlPoint1(), firstPoint->point()); } path.closeSubpath(); } if (currPoint->activeControlPoint2()) { activeCP = true; } else { activeCP = false; } lastPoint = currPoint; } } return path; } QRectF KoPathShape::boundingRect() const { const QTransform transform = absoluteTransformation(0); /** * First we approximate the insets of the stroke by rendering a fat bezier curve * with width set to the maximum inset of miters and markers. The are swept by this * curve will be a good approximation of the real curve bounding rect. */ qreal outlineSweepWidth = 0; const QSharedPointer lineBorder = qSharedPointerDynamicCast(stroke()); if (lineBorder) { outlineSweepWidth = lineBorder->lineWidth(); } if (stroke()) { KoInsets inset; stroke()->strokeInsets(this, inset); const qreal maxInset = std::max({inset.left, inset.top, inset.right, inset.bottom}); // insets extend outside the shape, but width extends both inside and outside, // so we should multiply insets by 2.0 outlineSweepWidth = std::max({outlineSweepWidth, 2.0 * maxInset, 2.0 * stroke()->strokeMaxMarkersInset(this)}); } QPen pen(Qt::black, outlineSweepWidth); // select round joins and caps to ensure it sweeps exactly // 'outlineSweepWidth' pixels in every possible pen.setJoinStyle(Qt::RoundJoin); pen.setCapStyle(Qt::RoundCap); QRectF bb = transform.map(pathStroke(pen)).boundingRect(); if (shadow()) { KoInsets insets; shadow()->insets(insets); bb.adjust(-insets.left, -insets.top, insets.right, insets.bottom); } if (filterEffectStack()) { QRectF clipRect = filterEffectStack()->clipRectForBoundingRect(QRectF(QPointF(), size())); bb |= transform.mapRect(clipRect); } return bb; } QSizeF KoPathShape::size() const { // don't call boundingRect here as it uses absoluteTransformation // which itself uses size() -> leads to infinite recursion return outlineRect().size(); } void KoPathShape::setSize(const QSizeF &newSize) { QTransform matrix(resizeMatrix(newSize)); KoShape::setSize(newSize); d->map(matrix); } QTransform KoPathShape::resizeMatrix(const QSizeF & newSize) const { QSizeF oldSize = size(); if (oldSize.width() == 0.0) { oldSize.setWidth(0.000001); } if (oldSize.height() == 0.0) { oldSize.setHeight(0.000001); } QSizeF sizeNew(newSize); if (sizeNew.width() == 0.0) { sizeNew.setWidth(0.000001); } if (sizeNew.height() == 0.0) { sizeNew.setHeight(0.000001); } return QTransform(sizeNew.width() / oldSize.width(), 0, 0, sizeNew.height() / oldSize.height(), 0, 0); } KoPathPoint * KoPathShape::moveTo(const QPointF &p) { KoPathPoint * point = new KoPathPoint(this, p, KoPathPoint::StartSubpath | KoPathPoint::StopSubpath); KoSubpath * path = new KoSubpath; path->push_back(point); d->subpaths.push_back(path); notifyPointsChanged(); return point; } KoPathPoint * KoPathShape::lineTo(const QPointF &p) { if (d->subpaths.empty()) { moveTo(QPointF(0, 0)); } KoPathPoint * point = new KoPathPoint(this, p, KoPathPoint::StopSubpath); KoPathPoint * lastPoint = d->subpaths.last()->last(); updateLastPriv(&lastPoint); d->subpaths.last()->push_back(point); notifyPointsChanged(); return point; } KoPathPoint * KoPathShape::curveTo(const QPointF &c1, const QPointF &c2, const QPointF &p) { if (d->subpaths.empty()) { moveTo(QPointF(0, 0)); } KoPathPoint * lastPoint = d->subpaths.last()->last(); updateLastPriv(&lastPoint); lastPoint->setControlPoint2(c1); KoPathPoint * point = new KoPathPoint(this, p, KoPathPoint::StopSubpath); point->setControlPoint1(c2); d->subpaths.last()->push_back(point); notifyPointsChanged(); return point; } KoPathPoint * KoPathShape::curveTo(const QPointF &c, const QPointF &p) { if (d->subpaths.empty()) moveTo(QPointF(0, 0)); KoPathPoint * lastPoint = d->subpaths.last()->last(); updateLastPriv(&lastPoint); lastPoint->setControlPoint2(c); KoPathPoint * point = new KoPathPoint(this, p, KoPathPoint::StopSubpath); d->subpaths.last()->push_back(point); notifyPointsChanged(); return point; } KoPathPoint * KoPathShape::arcTo(qreal rx, qreal ry, qreal startAngle, qreal sweepAngle) { if (d->subpaths.empty()) { moveTo(QPointF(0, 0)); } KoPathPoint * lastPoint = d->subpaths.last()->last(); if (lastPoint->properties() & KoPathPoint::CloseSubpath) { lastPoint = d->subpaths.last()->first(); } QPointF startpoint(lastPoint->point()); KoPathPoint * newEndPoint = lastPoint; QPointF curvePoints[12]; int pointCnt = arcToCurve(rx, ry, startAngle, sweepAngle, startpoint, curvePoints); for (int i = 0; i < pointCnt; i += 3) { newEndPoint = curveTo(curvePoints[i], curvePoints[i+1], curvePoints[i+2]); } return newEndPoint; } int KoPathShape::arcToCurve(qreal rx, qreal ry, qreal startAngle, qreal sweepAngle, const QPointF & offset, QPointF * curvePoints) const { int pointCnt = 0; // check Parameters if (sweepAngle == 0.0) return pointCnt; sweepAngle = qBound(-360.0, sweepAngle, 360.0); if (rx == 0 || ry == 0) { //TODO } // split angles bigger than 90° so that it gives a good approximation to the circle qreal parts = ceil(qAbs(sweepAngle / 90.0)); qreal sa_rad = startAngle * M_PI / 180.0; qreal partangle = sweepAngle / parts; qreal endangle = startAngle + partangle; qreal se_rad = endangle * M_PI / 180.0; qreal sinsa = sin(sa_rad); qreal cossa = cos(sa_rad); qreal kappa = 4.0 / 3.0 * tan((se_rad - sa_rad) / 4); // startpoint is at the last point is the path but when it is closed // it is at the first point QPointF startpoint(offset); //center berechnen QPointF center(startpoint - QPointF(cossa * rx, -sinsa * ry)); //debugFlake <<"kappa" << kappa <<"parts" << parts; for (int part = 0; part < parts; ++part) { // start tangent curvePoints[pointCnt++] = QPointF(startpoint - QPointF(sinsa * rx * kappa, cossa * ry * kappa)); qreal sinse = sin(se_rad); qreal cosse = cos(se_rad); // end point QPointF endpoint(center + QPointF(cosse * rx, -sinse * ry)); // end tangent curvePoints[pointCnt++] = QPointF(endpoint - QPointF(-sinse * rx * kappa, -cosse * ry * kappa)); curvePoints[pointCnt++] = endpoint; // set the endpoint as next start point startpoint = endpoint; sinsa = sinse; cossa = cosse; endangle += partangle; se_rad = endangle * M_PI / 180.0; } return pointCnt; } void KoPathShape::close() { if (d->subpaths.empty()) { return; } closeSubpathPriv(d->subpaths.last()); } void KoPathShape::closeMerge() { if (d->subpaths.empty()) { return; } closeMergeSubpathPriv(d->subpaths.last()); } QPointF KoPathShape::normalize() { QPointF tl(outline().boundingRect().topLeft()); QTransform matrix; matrix.translate(-tl.x(), -tl.y()); d->map(matrix); // keep the top left point of the object applyTransformation(matrix.inverted()); shapeChangedPriv(ContentChanged); return tl; } void KoPathShape::Private::map(const QTransform &matrix) { KoSubpathList::const_iterator pathIt(subpaths.constBegin()); for (; pathIt != subpaths.constEnd(); ++pathIt) { KoSubpath::const_iterator it((*pathIt)->constBegin()); for (; it != (*pathIt)->constEnd(); ++it) { // It's possible there are null points in the map... if (*it) { (*it)->map(matrix); } } } } void KoPathShape::updateLastPriv(KoPathPoint **lastPoint) { // check if we are about to add a new point to a closed subpath if ((*lastPoint)->properties() & KoPathPoint::StopSubpath && (*lastPoint)->properties() & KoPathPoint::CloseSubpath) { // get the first point of the subpath KoPathPoint *subpathStart = d->subpaths.last()->first(); // clone the first point of the subpath... KoPathPoint * newLastPoint = new KoPathPoint(*subpathStart, this); // ... and make it a normal point newLastPoint->setProperties(KoPathPoint::Normal); // now start a new subpath with the cloned start point KoSubpath *path = new KoSubpath; path->push_back(newLastPoint); d->subpaths.push_back(path); *lastPoint = newLastPoint; } else { // the subpath was not closed so the formerly last point // of the subpath is no end point anymore (*lastPoint)->unsetProperty(KoPathPoint::StopSubpath); } (*lastPoint)->unsetProperty(KoPathPoint::CloseSubpath); } QList KoPathShape::pointsAt(const QRectF &r) const { QList result; KoSubpathList::const_iterator pathIt(d->subpaths.constBegin()); for (; pathIt != d->subpaths.constEnd(); ++pathIt) { KoSubpath::const_iterator it((*pathIt)->constBegin()); for (; it != (*pathIt)->constEnd(); ++it) { if (r.contains((*it)->point())) result.append(*it); else if ((*it)->activeControlPoint1() && r.contains((*it)->controlPoint1())) result.append(*it); else if ((*it)->activeControlPoint2() && r.contains((*it)->controlPoint2())) result.append(*it); } } return result; } QList KoPathShape::segmentsAt(const QRectF &r) const { QList segments; int subpathCount = d->subpaths.count(); for (int subpathIndex = 0; subpathIndex < subpathCount; ++subpathIndex) { KoSubpath * subpath = d->subpaths[subpathIndex]; int pointCount = subpath->count(); bool subpathClosed = isClosedSubpath(subpathIndex); for (int pointIndex = 0; pointIndex < pointCount; ++pointIndex) { if (pointIndex == (pointCount - 1) && ! subpathClosed) break; KoPathSegment s(subpath->at(pointIndex), subpath->at((pointIndex + 1) % pointCount)); QRectF controlRect = s.controlPointRect(); if (! r.intersects(controlRect) && ! controlRect.contains(r)) continue; QRectF bound = s.boundingRect(); if (! r.intersects(bound) && ! bound.contains(r)) continue; segments.append(s); } } return segments; } KoPathPointIndex KoPathShape::pathPointIndex(const KoPathPoint *point) const { for (int subpathIndex = 0; subpathIndex < d->subpaths.size(); ++subpathIndex) { KoSubpath * subpath = d->subpaths.at(subpathIndex); for (int pointPos = 0; pointPos < subpath->size(); ++pointPos) { if (subpath->at(pointPos) == point) { return KoPathPointIndex(subpathIndex, pointPos); } } } return KoPathPointIndex(-1, -1); } KoPathPoint * KoPathShape::pointByIndex(const KoPathPointIndex &pointIndex) const { KoSubpath *subpath = d->subPath(pointIndex.first); if (subpath == 0 || pointIndex.second < 0 || pointIndex.second >= subpath->size()) return 0; return subpath->at(pointIndex.second); } KoPathSegment KoPathShape::segmentByIndex(const KoPathPointIndex &pointIndex) const { KoPathSegment segment(0, 0); KoSubpath *subpath = d->subPath(pointIndex.first); if (subpath != 0 && pointIndex.second >= 0 && pointIndex.second < subpath->size()) { KoPathPoint * point = subpath->at(pointIndex.second); int index = pointIndex.second; // check if we have a (closing) segment starting from the last point if ((index == subpath->size() - 1) && point->properties() & KoPathPoint::CloseSubpath) index = 0; else ++index; if (index < subpath->size()) { segment = KoPathSegment(point, subpath->at(index)); } } return segment; } int KoPathShape::pointCount() const { int i = 0; KoSubpathList::const_iterator pathIt(d->subpaths.constBegin()); for (; pathIt != d->subpaths.constEnd(); ++pathIt) { i += (*pathIt)->size(); } return i; } int KoPathShape::subpathCount() const { return d->subpaths.count(); } int KoPathShape::subpathPointCount(int subpathIndex) const { KoSubpath *subpath = d->subPath(subpathIndex); if (subpath == 0) return -1; return subpath->size(); } bool KoPathShape::isClosedSubpath(int subpathIndex) const { KoSubpath *subpath = d->subPath(subpathIndex); if (subpath == 0) return false; const bool firstClosed = subpath->first()->properties() & KoPathPoint::CloseSubpath; const bool lastClosed = subpath->last()->properties() & KoPathPoint::CloseSubpath; return firstClosed && lastClosed; } bool KoPathShape::insertPoint(KoPathPoint* point, const KoPathPointIndex &pointIndex) { KoSubpath *subpath = d->subPath(pointIndex.first); if (subpath == 0 || pointIndex.second < 0 || pointIndex.second > subpath->size()) return false; KoPathPoint::PointProperties properties = point->properties(); properties &= ~KoPathPoint::StartSubpath; properties &= ~KoPathPoint::StopSubpath; properties &= ~KoPathPoint::CloseSubpath; // check if new point starts subpath if (pointIndex.second == 0) { properties |= KoPathPoint::StartSubpath; // subpath was closed if (subpath->last()->properties() & KoPathPoint::CloseSubpath) { // keep the path closed properties |= KoPathPoint::CloseSubpath; } // old first point does not start the subpath anymore subpath->first()->unsetProperty(KoPathPoint::StartSubpath); } // check if new point stops subpath else if (pointIndex.second == subpath->size()) { properties |= KoPathPoint::StopSubpath; // subpath was closed if (subpath->last()->properties() & KoPathPoint::CloseSubpath) { // keep the path closed properties = properties | KoPathPoint::CloseSubpath; } // old last point does not end subpath anymore subpath->last()->unsetProperty(KoPathPoint::StopSubpath); } point->setProperties(properties); point->setParent(this); subpath->insert(pointIndex.second , point); notifyPointsChanged(); return true; } KoPathPoint * KoPathShape::removePoint(const KoPathPointIndex &pointIndex) { KoSubpath *subpath = d->subPath(pointIndex.first); if (subpath == 0 || pointIndex.second < 0 || pointIndex.second >= subpath->size()) return 0; KoPathPoint * point = subpath->takeAt(pointIndex.second); point->setParent(0); //don't do anything (not even crash), if there was only one point if (pointCount()==0) { return point; } // check if we removed the first point else if (pointIndex.second == 0) { // first point removed, set new StartSubpath subpath->first()->setProperty(KoPathPoint::StartSubpath); // check if path was closed if (subpath->last()->properties() & KoPathPoint::CloseSubpath) { // keep path closed subpath->first()->setProperty(KoPathPoint::CloseSubpath); } } // check if we removed the last point else if (pointIndex.second == subpath->size()) { // use size as point is already removed // last point removed, set new StopSubpath subpath->last()->setProperty(KoPathPoint::StopSubpath); // check if path was closed if (point->properties() & KoPathPoint::CloseSubpath) { // keep path closed subpath->last()->setProperty(KoPathPoint::CloseSubpath); } } notifyPointsChanged(); return point; } bool KoPathShape::breakAfter(const KoPathPointIndex &pointIndex) { KoSubpath *subpath = d->subPath(pointIndex.first); if (!subpath || pointIndex.second < 0 || pointIndex.second > subpath->size() - 2 || isClosedSubpath(pointIndex.first)) return false; KoSubpath * newSubpath = new KoSubpath; int size = subpath->size(); for (int i = pointIndex.second + 1; i < size; ++i) { newSubpath->append(subpath->takeAt(pointIndex.second + 1)); } // now make the first point of the new subpath a starting node newSubpath->first()->setProperty(KoPathPoint::StartSubpath); // the last point of the old subpath is now an ending node subpath->last()->setProperty(KoPathPoint::StopSubpath); // insert the new subpath after the broken one d->subpaths.insert(pointIndex.first + 1, newSubpath); notifyPointsChanged(); return true; } bool KoPathShape::join(int subpathIndex) { KoSubpath *subpath = d->subPath(subpathIndex); KoSubpath *nextSubpath = d->subPath(subpathIndex + 1); if (!subpath || !nextSubpath || isClosedSubpath(subpathIndex) || isClosedSubpath(subpathIndex+1)) return false; // the last point of the subpath does not end the subpath anymore subpath->last()->unsetProperty(KoPathPoint::StopSubpath); // the first point of the next subpath does not start a subpath anymore nextSubpath->first()->unsetProperty(KoPathPoint::StartSubpath); // append the second subpath to the first Q_FOREACH (KoPathPoint * p, *nextSubpath) subpath->append(p); // remove the nextSubpath from path d->subpaths.removeAt(subpathIndex + 1); // delete it as it is no longer possible to use it delete nextSubpath; notifyPointsChanged(); return true; } bool KoPathShape::moveSubpath(int oldSubpathIndex, int newSubpathIndex) { KoSubpath *subpath = d->subPath(oldSubpathIndex); if (subpath == 0 || newSubpathIndex >= d->subpaths.size()) return false; if (oldSubpathIndex == newSubpathIndex) return true; d->subpaths.removeAt(oldSubpathIndex); d->subpaths.insert(newSubpathIndex, subpath); notifyPointsChanged(); return true; } KoPathPointIndex KoPathShape::openSubpath(const KoPathPointIndex &pointIndex) { KoSubpath *subpath = d->subPath(pointIndex.first); if (!subpath || pointIndex.second < 0 || pointIndex.second >= subpath->size() || !isClosedSubpath(pointIndex.first)) return KoPathPointIndex(-1, -1); KoPathPoint * oldStartPoint = subpath->first(); // the old starting node no longer starts the subpath oldStartPoint->unsetProperty(KoPathPoint::StartSubpath); // the old end node no longer closes the subpath subpath->last()->unsetProperty(KoPathPoint::StopSubpath); // reorder the subpath for (int i = 0; i < pointIndex.second; ++i) { subpath->append(subpath->takeFirst()); } // make the first point a start node subpath->first()->setProperty(KoPathPoint::StartSubpath); // make the last point an end node subpath->last()->setProperty(KoPathPoint::StopSubpath); notifyPointsChanged(); return pathPointIndex(oldStartPoint); } KoPathPointIndex KoPathShape::closeSubpath(const KoPathPointIndex &pointIndex) { KoSubpath *subpath = d->subPath(pointIndex.first); if (!subpath || pointIndex.second < 0 || pointIndex.second >= subpath->size() || isClosedSubpath(pointIndex.first)) return KoPathPointIndex(-1, -1); KoPathPoint * oldStartPoint = subpath->first(); // the old starting node no longer starts the subpath oldStartPoint->unsetProperty(KoPathPoint::StartSubpath); // the old end node no longer ends the subpath subpath->last()->unsetProperty(KoPathPoint::StopSubpath); // reorder the subpath for (int i = 0; i < pointIndex.second; ++i) { subpath->append(subpath->takeFirst()); } subpath->first()->setProperty(KoPathPoint::StartSubpath); subpath->last()->setProperty(KoPathPoint::StopSubpath); closeSubpathPriv(subpath); notifyPointsChanged(); return pathPointIndex(oldStartPoint); } bool KoPathShape::reverseSubpath(int subpathIndex) { KoSubpath *subpath = d->subPath(subpathIndex); if (subpath == 0) return false; int size = subpath->size(); for (int i = 0; i < size; ++i) { KoPathPoint *p = subpath->takeAt(i); p->reverse(); subpath->prepend(p); } // adjust the position dependent properties KoPathPoint *first = subpath->first(); KoPathPoint *last = subpath->last(); KoPathPoint::PointProperties firstProps = first->properties(); KoPathPoint::PointProperties lastProps = last->properties(); firstProps |= KoPathPoint::StartSubpath; firstProps &= ~KoPathPoint::StopSubpath; lastProps |= KoPathPoint::StopSubpath; lastProps &= ~KoPathPoint::StartSubpath; if (firstProps & KoPathPoint::CloseSubpath) { firstProps |= KoPathPoint::CloseSubpath; lastProps |= KoPathPoint::CloseSubpath; } first->setProperties(firstProps); last->setProperties(lastProps); notifyPointsChanged(); return true; } KoSubpath * KoPathShape::removeSubpath(int subpathIndex) { KoSubpath *subpath = d->subPath(subpathIndex); if (subpath != 0) { Q_FOREACH (KoPathPoint* point, *subpath) { point->setParent(this); } d->subpaths.removeAt(subpathIndex); } notifyPointsChanged(); return subpath; } bool KoPathShape::addSubpath(KoSubpath * subpath, int subpathIndex) { if (subpathIndex < 0 || subpathIndex > d->subpaths.size()) return false; Q_FOREACH (KoPathPoint* point, *subpath) { point->setParent(this); } d->subpaths.insert(subpathIndex, subpath); notifyPointsChanged(); return true; } int KoPathShape::combine(KoPathShape *path) { int insertSegmentPosition = -1; if (!path) return insertSegmentPosition; QTransform pathMatrix = path->absoluteTransformation(0); QTransform myMatrix = absoluteTransformation(0).inverted(); Q_FOREACH (KoSubpath* subpath, path->d->subpaths) { KoSubpath *newSubpath = new KoSubpath(); Q_FOREACH (KoPathPoint* point, *subpath) { KoPathPoint *newPoint = new KoPathPoint(*point, this); newPoint->map(pathMatrix); newPoint->map(myMatrix); newSubpath->append(newPoint); } d->subpaths.append(newSubpath); if (insertSegmentPosition < 0) { insertSegmentPosition = d->subpaths.size() - 1; } } normalize(); notifyPointsChanged(); return insertSegmentPosition; } bool KoPathShape::separate(QList & separatedPaths) { if (! d->subpaths.size()) return false; QTransform myMatrix = absoluteTransformation(0); Q_FOREACH (KoSubpath* subpath, d->subpaths) { KoPathShape *shape = new KoPathShape(); shape->setStroke(stroke()); shape->setBackground(background()); shape->setShapeId(shapeId()); shape->setZIndex(zIndex()); KoSubpath *newSubpath = new KoSubpath(); Q_FOREACH (KoPathPoint* point, *subpath) { KoPathPoint *newPoint = new KoPathPoint(*point, shape); newPoint->map(myMatrix); newSubpath->append(newPoint); } shape->d->subpaths.append(newSubpath); shape->normalize(); // NOTE: shape cannot have any listeners yet, so no notification about // points modification is needed separatedPaths.append(shape); } return true; } void KoPathShape::closeSubpathPriv(KoSubpath *subpath) { if (! subpath) return; subpath->last()->setProperty(KoPathPoint::CloseSubpath); subpath->first()->setProperty(KoPathPoint::CloseSubpath); notifyPointsChanged(); } void KoPathShape::closeMergeSubpathPriv(KoSubpath *subpath) { if (! subpath || subpath->size() < 2) return; KoPathPoint * lastPoint = subpath->last(); KoPathPoint * firstPoint = subpath->first(); // check if first and last points are coincident if (lastPoint->point() == firstPoint->point()) { // we are removing the current last point and // reuse its first control point if active firstPoint->setProperty(KoPathPoint::StartSubpath); firstPoint->setProperty(KoPathPoint::CloseSubpath); if (lastPoint->activeControlPoint1()) firstPoint->setControlPoint1(lastPoint->controlPoint1()); // remove last point delete subpath->takeLast(); // the new last point closes the subpath now lastPoint = subpath->last(); lastPoint->setProperty(KoPathPoint::StopSubpath); lastPoint->setProperty(KoPathPoint::CloseSubpath); notifyPointsChanged(); } else { closeSubpathPriv(subpath); } } const KoSubpathList &KoPathShape::subpaths() const { return d->subpaths; } KoSubpathList &KoPathShape::subpaths() { return d->subpaths; } void KoPathShape::map(const QTransform &matrix) { return d->map(matrix); } KoSubpath *KoPathShape::Private::subPath(int subpathIndex) const { if (subpathIndex < 0 || subpathIndex >= subpaths.size()) return 0; return subpaths.at(subpathIndex); } QString KoPathShape::pathShapeId() const { return KoPathShapeId; } QString KoPathShape::toString(const QTransform &matrix) const { QString pathString; // iterate over all subpaths KoSubpathList::const_iterator pathIt(d->subpaths.constBegin()); for (; pathIt != d->subpaths.constEnd(); ++pathIt) { KoSubpath::const_iterator pointIt((*pathIt)->constBegin()); // keep a pointer to the first point of the subpath KoPathPoint *firstPoint(*pointIt); // keep a pointer to the previous point of the subpath KoPathPoint *lastPoint = firstPoint; // keep track if the previous point has an active control point 2 bool activeControlPoint2 = false; // iterate over all points of the current subpath for (; pointIt != (*pathIt)->constEnd(); ++pointIt) { KoPathPoint *currPoint(*pointIt); if (!currPoint) { qWarning() << "Found a zero point in the shape's path!"; continue; } // first point of subpath ? if (currPoint == firstPoint) { // are we starting a subpath ? if (currPoint->properties() & KoPathPoint::StartSubpath) { const QPointF p = matrix.map(currPoint->point()); pathString += QString("M%1 %2").arg(p.x()).arg(p.y()); } } // end point of curve segment ? else if (activeControlPoint2 || currPoint->activeControlPoint1()) { // check if we have a cubic or quadratic curve const bool isCubic = activeControlPoint2 && currPoint->activeControlPoint1(); KoPathSegment cubicSeg = isCubic ? KoPathSegment(lastPoint, currPoint) : KoPathSegment(lastPoint, currPoint).toCubic(); const QPointF cp1 = matrix.map(cubicSeg.first()->controlPoint2()); const QPointF cp2 = matrix.map(cubicSeg.second()->controlPoint1()); const QPointF p = matrix.map(cubicSeg.second()->point()); pathString += QString("C%1 %2 %3 %4 %5 %6") .arg(cp1.x()).arg(cp1.y()) .arg(cp2.x()).arg(cp2.y()) .arg(p.x()).arg(p.y()); } // end point of line segment! else { const QPointF p = matrix.map(currPoint->point()); pathString += QString("L%1 %2").arg(p.x()).arg(p.y()); } // last point closes subpath ? if (currPoint->properties() & KoPathPoint::StopSubpath && currPoint->properties() & KoPathPoint::CloseSubpath) { // add curve when there is a curve on the way to the first point if (currPoint->activeControlPoint2() || firstPoint->activeControlPoint1()) { // check if we have a cubic or quadratic curve const bool isCubic = currPoint->activeControlPoint2() && firstPoint->activeControlPoint1(); KoPathSegment cubicSeg = isCubic ? KoPathSegment(currPoint, firstPoint) : KoPathSegment(currPoint, firstPoint).toCubic(); const QPointF cp1 = matrix.map(cubicSeg.first()->controlPoint2()); const QPointF cp2 = matrix.map(cubicSeg.second()->controlPoint1()); const QPointF p = matrix.map(cubicSeg.second()->point()); pathString += QString("C%1 %2 %3 %4 %5 %6") .arg(cp1.x()).arg(cp1.y()) .arg(cp2.x()).arg(cp2.y()) .arg(p.x()).arg(p.y()); } pathString += QString("Z"); } activeControlPoint2 = currPoint->activeControlPoint2(); lastPoint = currPoint; } } return pathString; } char nodeType(const KoPathPoint * point) { if (point->properties() & KoPathPoint::IsSmooth) { return 's'; } else if (point->properties() & KoPathPoint::IsSymmetric) { return 'z'; } else { return 'c'; } } QString KoPathShape::Private::nodeTypes() const { QString types; KoSubpathList::const_iterator pathIt(subpaths.constBegin()); for (; pathIt != subpaths.constEnd(); ++pathIt) { KoSubpath::const_iterator it((*pathIt)->constBegin()); for (; it != (*pathIt)->constEnd(); ++it) { if (it == (*pathIt)->constBegin()) { types.append('c'); } else { types.append(nodeType(*it)); } if ((*it)->properties() & KoPathPoint::StopSubpath && (*it)->properties() & KoPathPoint::CloseSubpath) { KoPathPoint * firstPoint = (*pathIt)->first(); types.append(nodeType(firstPoint)); } } } return types; } void updateNodeType(KoPathPoint * point, const QChar & nodeType) { if (nodeType == 's') { point->setProperty(KoPathPoint::IsSmooth); } else if (nodeType == 'z') { point->setProperty(KoPathPoint::IsSymmetric); } } void KoPathShape::Private::loadNodeTypes(const KoXmlElement &element) { if (element.hasAttributeNS(KoXmlNS::calligra, "nodeTypes")) { QString nodeTypes = element.attributeNS(KoXmlNS::calligra, "nodeTypes"); QString::const_iterator nIt(nodeTypes.constBegin()); KoSubpathList::const_iterator pathIt(subpaths.constBegin()); for (; pathIt != subpaths.constEnd(); ++pathIt) { KoSubpath::const_iterator it((*pathIt)->constBegin()); for (; it != (*pathIt)->constEnd(); ++it, nIt++) { // be sure not to crash if there are not enough nodes in nodeTypes if (nIt == nodeTypes.constEnd()) { warnFlake << "not enough nodes in calligra:nodeTypes"; return; } // the first node is always of type 'c' if (it != (*pathIt)->constBegin()) { updateNodeType(*it, *nIt); } if ((*it)->properties() & KoPathPoint::StopSubpath && (*it)->properties() & KoPathPoint::CloseSubpath) { ++nIt; updateNodeType((*pathIt)->first(), *nIt); } } } } } Qt::FillRule KoPathShape::fillRule() const { return d->fillRule; } void KoPathShape::setFillRule(Qt::FillRule fillRule) { d->fillRule = fillRule; } KoPathShape * KoPathShape::createShapeFromPainterPath(const QPainterPath &path) { KoPathShape * shape = new KoPathShape(); int elementCount = path.elementCount(); for (int i = 0; i < elementCount; i++) { QPainterPath::Element element = path.elementAt(i); switch (element.type) { case QPainterPath::MoveToElement: shape->moveTo(QPointF(element.x, element.y)); break; case QPainterPath::LineToElement: shape->lineTo(QPointF(element.x, element.y)); break; case QPainterPath::CurveToElement: shape->curveTo(QPointF(element.x, element.y), QPointF(path.elementAt(i + 1).x, path.elementAt(i + 1).y), QPointF(path.elementAt(i + 2).x, path.elementAt(i + 2).y)); break; default: continue; } } shape->setShapeId(KoPathShapeId); //shape->normalize(); return shape; } bool KoPathShape::hitTest(const QPointF &position) const { if (parent() && parent()->isClipped(this) && ! parent()->hitTest(position)) return false; QPointF point = absoluteTransformation(0).inverted().map(position); const QPainterPath outlinePath = outline(); if (stroke()) { KoInsets insets; stroke()->strokeInsets(this, insets); QRectF roi(QPointF(-insets.left, -insets.top), QPointF(insets.right, insets.bottom)); roi.moveCenter(point); if (outlinePath.intersects(roi) || outlinePath.contains(roi)) return true; } else { if (outlinePath.contains(point)) return true; } // if there is no shadow we can as well just leave if (! shadow()) return false; // the shadow has an offset to the shape, so we simply // check if the position minus the shadow offset hits the shape point = absoluteTransformation(0).inverted().map(position - shadow()->offset()); return outlinePath.contains(point); } void KoPathShape::setMarker(KoMarker *marker, KoFlake::MarkerPosition pos) { if (!marker && d->markersNew.contains(pos)) { d->markersNew.remove(pos); } else { d->markersNew[pos] = marker; } } KoMarker *KoPathShape::marker(KoFlake::MarkerPosition pos) const { return d->markersNew[pos].data(); } bool KoPathShape::hasMarkers() const { return !d->markersNew.isEmpty(); } bool KoPathShape::autoFillMarkers() const { return d->autoFillMarkers; } void KoPathShape::setAutoFillMarkers(bool value) { d->autoFillMarkers = value; } void KoPathShape::recommendPointSelectionChange(const QList &newSelection) { Q_FOREACH (KoShape::ShapeChangeListener *listener, listeners()) { PointSelectionChangeListener *pointListener = dynamic_cast(listener); if (pointListener) { pointListener->recommendPointSelectionChange(this, newSelection); } } } void KoPathShape::notifyPointsChanged() { Q_FOREACH (KoShape::ShapeChangeListener *listener, listeners()) { PointSelectionChangeListener *pointListener = dynamic_cast(listener); if (pointListener) { pointListener->notifyPathPointsChanged(this); } } } QPainterPath KoPathShape::pathStroke(const QPen &pen) const { if (d->subpaths.isEmpty()) { return QPainterPath(); } QPainterPath pathOutline; QPainterPathStroker stroker; stroker.setWidth(0); stroker.setJoinStyle(Qt::MiterJoin); stroker.setWidth(pen.widthF()); stroker.setJoinStyle(pen.joinStyle()); stroker.setMiterLimit(pen.miterLimit()); stroker.setCapStyle(pen.capStyle()); stroker.setDashOffset(pen.dashOffset()); stroker.setDashPattern(pen.dashPattern()); QPainterPath path = stroker.createStroke(outline()); pathOutline.addPath(path); pathOutline.setFillRule(Qt::WindingFill); return pathOutline; } void KoPathShape::PointSelectionChangeListener::notifyShapeChanged(KoShape::ChangeType type, KoShape *shape) { Q_UNUSED(type); Q_UNUSED(shape); } diff --git a/libs/flake/KoShapeManager.cpp b/libs/flake/KoShapeManager.cpp index 5d19a5bdda..b7abf6c89e 100644 --- a/libs/flake/KoShapeManager.cpp +++ b/libs/flake/KoShapeManager.cpp @@ -1,729 +1,741 @@ /* This file is part of the KDE project Copyright (C) 2006-2008 Thorsten Zachmann Copyright (C) 2006-2010 Thomas Zander Copyright (C) 2009-2010 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 "KoShapeManager.h" #include "KoShapeManager_p.h" #include "KoSelection.h" #include "KoToolManager.h" #include "KoPointerEvent.h" #include "KoShape.h" #include "KoShape_p.h" #include "KoCanvasBase.h" #include "KoShapeContainer.h" #include "KoShapeStrokeModel.h" #include "KoShapeGroup.h" #include "KoToolProxy.h" #include "KoShapeShadow.h" #include "KoShapeLayer.h" #include "KoFilterEffect.h" #include "KoFilterEffectStack.h" #include "KoFilterEffectRenderContext.h" #include "KoShapeBackground.h" #include #include "KoClipPath.h" #include "KoClipMaskPainter.h" #include "KoShapePaintingContext.h" #include "KoViewConverter.h" #include "KisQPainterStateSaver.h" #include "KoSvgTextChunkShape.h" #include "KoSvgTextShape.h" #include #include #include #include #include "kis_painting_tweaks.h" bool KoShapeManager::Private::shapeUsedInRenderingTree(KoShape *shape) { // FIXME: make more general! return !dynamic_cast(shape) && !dynamic_cast(shape) && !(dynamic_cast(shape) && !dynamic_cast(shape)); } void KoShapeManager::Private::updateTree() { QMutexLocker l(&this->treeMutex); // for detecting collisions between shapes. DetectCollision detector; bool selectionModified = false; bool anyModified = false; Q_FOREACH (KoShape *shape, aggregate4update) { if (shapeIndexesBeforeUpdate.contains(shape)) detector.detect(tree, shape, shapeIndexesBeforeUpdate[shape]); selectionModified = selectionModified || selection->isSelected(shape); anyModified = true; } foreach (KoShape *shape, aggregate4update) { if (!shapeUsedInRenderingTree(shape)) continue; tree.remove(shape); QRectF br(shape->boundingRect()); tree.insert(br, shape); } // do it again to see which shapes we intersect with _after_ moving. foreach (KoShape *shape, aggregate4update) { detector.detect(tree, shape, shapeIndexesBeforeUpdate[shape]); } aggregate4update.clear(); shapeIndexesBeforeUpdate.clear(); detector.fireSignals(); if (selectionModified) { emit q->selectionContentChanged(); } if (anyModified) { emit q->contentChanged(); } } void KoShapeManager::Private::forwardCompressedUdpate() { bool shouldUpdateDecorations = false; QRectF scheduledUpdate; { QMutexLocker l(&shapesMutex); if (!compressedUpdate.isEmpty()) { scheduledUpdate = compressedUpdate; compressedUpdate = QRect(); } Q_FOREACH (const KoShape *shape, compressedUpdatedShapes) { if (selection->isSelected(shape)) { shouldUpdateDecorations = true; break; } } compressedUpdatedShapes.clear(); } if (shouldUpdateDecorations && canvas->toolProxy()) { canvas->toolProxy()->repaintDecorations(); } canvas->updateCanvas(scheduledUpdate); } void KoShapeManager::Private::paintGroup(KoShapeGroup *group, QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) { QList shapes = group->shapes(); std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); Q_FOREACH (KoShape *child, shapes) { // we paint recursively here, so we do not have to check recursively for visibility if (!child->isVisible(false)) continue; KoShapeGroup *childGroup = dynamic_cast(child); if (childGroup) { paintGroup(childGroup, painter, converter, paintContext); } else { painter.save(); KoShapeManager::renderSingleShape(child, painter, converter, paintContext); painter.restore(); } } } KoShapeManager::KoShapeManager(KoCanvasBase *canvas, const QList &shapes) : d(new Private(this, canvas)) { Q_ASSERT(d->canvas); // not optional. connect(d->selection, SIGNAL(selectionChanged()), this, SIGNAL(selectionChanged())); setShapes(shapes); /** * Shape manager uses signal compressors with timers, therefore * it might handle queued signals, therefore it should belong * to the GUI thread. */ this->moveToThread(qApp->thread()); connect(&d->updateCompressor, SIGNAL(timeout()), this, SLOT(forwardCompressedUdpate())); } KoShapeManager::KoShapeManager(KoCanvasBase *canvas) : d(new Private(this, canvas)) { Q_ASSERT(d->canvas); // not optional. connect(d->selection, SIGNAL(selectionChanged()), this, SIGNAL(selectionChanged())); // see a comment in another constructor this->moveToThread(qApp->thread()); connect(&d->updateCompressor, SIGNAL(timeout()), this, SLOT(forwardCompressedUdpate())); } void KoShapeManager::Private::unlinkFromShapesRecursively(const QList &shapes) { Q_FOREACH (KoShape *shape, shapes) { shape->removeShapeManager(q); KoShapeContainer *container = dynamic_cast(shape); if (container) { unlinkFromShapesRecursively(container->shapes()); } } } KoShapeManager::~KoShapeManager() { d->unlinkFromShapesRecursively(d->shapes); d->shapes.clear(); delete d; } void KoShapeManager::setShapes(const QList &shapes, Repaint repaint) { { QMutexLocker l1(&d->shapesMutex); QMutexLocker l2(&d->treeMutex); //clear selection d->selection->deselectAll(); d->unlinkFromShapesRecursively(d->shapes); d->compressedUpdate = QRect(); d->compressedUpdatedShapes.clear(); d->aggregate4update.clear(); d->shapeIndexesBeforeUpdate.clear(); d->tree.clear(); d->shapes.clear(); } Q_FOREACH (KoShape *shape, shapes) { addShape(shape, repaint); } } void KoShapeManager::addShape(KoShape *shape, Repaint repaint) { { QMutexLocker l1(&d->shapesMutex); if (d->shapes.contains(shape)) return; shape->addShapeManager(this); d->shapes.append(shape); if (d->shapeUsedInRenderingTree(shape)) { QMutexLocker l2(&d->treeMutex); QRectF br(shape->boundingRect()); d->tree.insert(br, shape); } } if (repaint == PaintShapeOnAdd) { shape->update(); } // add the children of a KoShapeContainer KoShapeContainer *container = dynamic_cast(shape); if (container) { foreach (KoShape *containerShape, container->shapes()) { addShape(containerShape, repaint); } } { QMutexLocker l(&d->treeMutex); Private::DetectCollision detector; detector.detect(d->tree, shape, shape->zIndex()); detector.fireSignals(); } } void KoShapeManager::remove(KoShape *shape) { QRectF dirtyRect; { QMutexLocker l1(&d->shapesMutex); QMutexLocker l2(&d->treeMutex); Private::DetectCollision detector; detector.detect(d->tree, shape, shape->zIndex()); detector.fireSignals(); dirtyRect = shape->absoluteOutlineRect(); shape->removeShapeManager(this); d->selection->deselect(shape); d->aggregate4update.remove(shape); d->compressedUpdatedShapes.remove(shape); if (d->shapeUsedInRenderingTree(shape)) { d->tree.remove(shape); } d->shapes.removeAll(shape); } if (!dirtyRect.isEmpty()) { d->canvas->updateCanvas(dirtyRect); } // remove the children of a KoShapeContainer KoShapeContainer *container = dynamic_cast(shape); if (container) { foreach (KoShape *containerShape, container->shapes()) { remove(containerShape); } } } KoShapeManager::ShapeInterface::ShapeInterface(KoShapeManager *_q) : q(_q) { } void KoShapeManager::ShapeInterface::notifyShapeDestructed(KoShape *shape) { QMutexLocker l1(&q->d->shapesMutex); QMutexLocker l2(&q->d->treeMutex); q->d->selection->deselect(shape); q->d->aggregate4update.remove(shape); q->d->compressedUpdatedShapes.remove(shape); // we cannot access RTTI of the semi-destructed shape, so just // unlink it lazily if (q->d->tree.contains(shape)) { q->d->tree.remove(shape); } q->d->shapes.removeAll(shape); } KoShapeManager::ShapeInterface *KoShapeManager::shapeInterface() { return &d->shapeInterface; } void KoShapeManager::paint(QPainter &painter, const KoViewConverter &converter, bool forPrint) { QMutexLocker l1(&d->shapesMutex); d->updateTree(); painter.setPen(Qt::NoPen); // painters by default have a black stroke, lets turn that off. painter.setBrush(Qt::NoBrush); QList unsortedShapes; if (painter.hasClipping()) { QMutexLocker l(&d->treeMutex); QRectF rect = converter.viewToDocument(KisPaintingTweaks::safeClipBoundingRect(painter)); unsortedShapes = d->tree.intersects(rect); } else { unsortedShapes = d->shapes; warnFlake << "KoShapeManager::paint Painting with a painter that has no clipping will lead to too much being painted!"; } // filter all hidden shapes from the list // also filter shapes with a parent which has filter effects applied QList sortedShapes; foreach (KoShape *shape, unsortedShapes) { if (!shape->isVisible()) continue; bool addShapeToList = true; // check if one of the shapes ancestors have filter effects KoShapeContainer *parent = shape->parent(); while (parent) { // parent must be part of the shape manager to be taken into account if (!d->shapes.contains(parent)) break; if (parent->filterEffectStack() && !parent->filterEffectStack()->isEmpty()) { addShapeToList = false; break; } parent = parent->parent(); } if (addShapeToList) { sortedShapes.append(shape); } else if (parent) { sortedShapes.append(parent); } } std::sort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex); KoShapePaintingContext paintContext(d->canvas, forPrint); //FIXME foreach (KoShape *shape, sortedShapes) { renderSingleShape(shape, painter, converter, paintContext); } #ifdef CALLIGRA_RTREE_DEBUG // paint tree qreal zx = 0; qreal zy = 0; converter.zoom(&zx, &zy); painter.save(); painter.scale(zx, zy); d->tree.paint(painter); painter.restore(); #endif if (! forPrint) { KoShapePaintingContext paintContext(d->canvas, forPrint); //FIXME d->selection->paint(painter, converter, paintContext); } } void KoShapeManager::renderSingleShape(KoShape *shape, QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) { KisQPainterStateSaver saver(&painter); // apply shape clipping KoClipPath::applyClipping(shape, painter, converter); // apply transformation painter.setTransform(shape->absoluteTransformation(&converter) * painter.transform()); // paint the shape paintShape(shape, painter, converter, paintContext); } void KoShapeManager::paintShape(KoShape *shape, QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) { qreal transparency = shape->transparency(true); if (transparency > 0.0) { painter.setOpacity(1.0-transparency); } if (shape->shadow()) { painter.save(); shape->shadow()->paint(shape, painter, converter); painter.restore(); } if (!shape->filterEffectStack() || shape->filterEffectStack()->isEmpty()) { QScopedPointer clipMaskPainter; QPainter *shapePainter = &painter; KoClipMask *clipMask = shape->clipMask(); if (clipMask) { clipMaskPainter.reset(new KoClipMaskPainter(&painter, shape->boundingRect())); shapePainter = clipMaskPainter->shapePainter(); } /** * We expect the shape to save/restore the painter's state itself. Such design was not * not always here, so we need a period of sanity checks to ensure all the shapes are * ported correctly. */ const QTransform sanityCheckTransformSaved = shapePainter->transform(); shape->paint(*shapePainter, converter, paintContext); shape->paintStroke(*shapePainter, converter, paintContext); KIS_SAFE_ASSERT_RECOVER(shapePainter->transform() == sanityCheckTransformSaved) { shapePainter->setTransform(sanityCheckTransformSaved); } if (clipMask) { shape->clipMask()->drawMask(clipMaskPainter->maskPainter(), shape); clipMaskPainter->renderOnGlobalPainter(); } } else { // TODO: clipping mask is not implemented for this case! // There are filter effects, then we need to prerender the shape on an image, to filter it QRectF shapeBound(QPointF(), shape->size()); // First step, compute the rectangle used for the image QRectF clipRegion = shape->filterEffectStack()->clipRectForBoundingRect(shapeBound); // convert clip region to view coordinates QRectF zoomedClipRegion = converter.documentToView(clipRegion); // determine the offset of the clipping rect from the shapes origin QPointF clippingOffset = zoomedClipRegion.topLeft(); // Initialize the buffer image QImage sourceGraphic(zoomedClipRegion.size().toSize(), QImage::Format_ARGB32_Premultiplied); sourceGraphic.fill(qRgba(0,0,0,0)); QHash imageBuffers; QSet requiredStdInputs = shape->filterEffectStack()->requiredStandarsInputs(); if (requiredStdInputs.contains("SourceGraphic") || requiredStdInputs.contains("SourceAlpha")) { // Init the buffer painter QPainter imagePainter(&sourceGraphic); imagePainter.translate(-1.0f*clippingOffset); imagePainter.setPen(Qt::NoPen); imagePainter.setBrush(Qt::NoBrush); imagePainter.setRenderHint(QPainter::Antialiasing, painter.testRenderHint(QPainter::Antialiasing)); // Paint the shape on the image KoShapeGroup *group = dynamic_cast(shape); if (group) { // the childrens matrix contains the groups matrix as well // so we have to compensate for that before painting the children imagePainter.setTransform(group->absoluteTransformation(&converter).inverted(), true); Private::paintGroup(group, imagePainter, converter, paintContext); } else { imagePainter.save(); shape->paint(imagePainter, converter, paintContext); shape->paintStroke(imagePainter, converter, paintContext); imagePainter.restore(); imagePainter.end(); } } if (requiredStdInputs.contains("SourceAlpha")) { QImage sourceAlpha = sourceGraphic; sourceAlpha.fill(qRgba(0,0,0,255)); sourceAlpha.setAlphaChannel(sourceGraphic.alphaChannel()); imageBuffers.insert("SourceAlpha", sourceAlpha); } if (requiredStdInputs.contains("FillPaint")) { QImage fillPaint = sourceGraphic; if (shape->background()) { QPainter fillPainter(&fillPaint); QPainterPath fillPath; fillPath.addRect(fillPaint.rect().adjusted(-1,-1,1,1)); shape->background()->paint(fillPainter, converter, paintContext, fillPath); } else { fillPaint.fill(qRgba(0,0,0,0)); } imageBuffers.insert("FillPaint", fillPaint); } imageBuffers.insert("SourceGraphic", sourceGraphic); imageBuffers.insert(QString(), sourceGraphic); KoFilterEffectRenderContext renderContext(converter); renderContext.setShapeBoundingBox(shapeBound); QImage result; QList filterEffects = shape->filterEffectStack()->filterEffects(); // Filter foreach (KoFilterEffect *filterEffect, filterEffects) { QRectF filterRegion = filterEffect->filterRectForBoundingRect(shapeBound); filterRegion = converter.documentToView(filterRegion); QRect subRegion = filterRegion.translated(-clippingOffset).toRect(); // set current filter region renderContext.setFilterRegion(subRegion & sourceGraphic.rect()); if (filterEffect->maximalInputCount() <= 1) { QList inputs = filterEffect->inputs(); QString input = inputs.count() ? inputs.first() : QString(); // get input image from image buffers and apply the filter effect QImage image = imageBuffers.value(input); if (!image.isNull()) { result = filterEffect->processImage(imageBuffers.value(input), renderContext); } } else { QList inputImages; Q_FOREACH (const QString &input, filterEffect->inputs()) { QImage image = imageBuffers.value(input); if (!image.isNull()) inputImages.append(imageBuffers.value(input)); } // apply the filter effect if (filterEffect->inputs().count() == inputImages.count()) result = filterEffect->processImages(inputImages, renderContext); } // store result of effect imageBuffers.insert(filterEffect->output(), result); } KoFilterEffect *lastEffect = filterEffects.last(); // Paint the result painter.save(); painter.drawImage(clippingOffset, imageBuffers.value(lastEffect->output())); painter.restore(); } } KoShape *KoShapeManager::shapeAt(const QPointF &position, KoFlake::ShapeSelection selection, bool omitHiddenShapes) { QMutexLocker l(&d->shapesMutex); d->updateTree(); QList sortedShapes; { QMutexLocker l(&d->treeMutex); sortedShapes = d->tree.contains(position); } std::sort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex); KoShape *firstUnselectedShape = 0; for (int count = sortedShapes.count() - 1; count >= 0; count--) { KoShape *shape = sortedShapes.at(count); if (omitHiddenShapes && ! shape->isVisible()) continue; if (! shape->hitTest(position)) continue; switch (selection) { case KoFlake::ShapeOnTop: if (shape->isSelectable()) return shape; break; case KoFlake::Selected: if (d->selection->isSelected(shape)) return shape; break; case KoFlake::Unselected: if (! d->selection->isSelected(shape)) return shape; break; case KoFlake::NextUnselected: // we want an unselected shape if (d->selection->isSelected(shape)) continue; // memorize the first unselected shape if (! firstUnselectedShape) firstUnselectedShape = shape; // check if the shape above is selected if (count + 1 < sortedShapes.count() && d->selection->isSelected(sortedShapes.at(count + 1))) return shape; break; } } // if we want the next unselected below a selected but there was none selected, // return the first found unselected shape if (selection == KoFlake::NextUnselected && firstUnselectedShape) return firstUnselectedShape; if (d->selection->hitTest(position)) return d->selection; return 0; // missed everything } QList KoShapeManager::shapesAt(const QRectF &rect, bool omitHiddenShapes, bool containedMode) { QMutexLocker l(&d->shapesMutex); d->updateTree(); QList shapes; { QMutexLocker l(&d->treeMutex); shapes = containedMode ? d->tree.contained(rect) : d->tree.intersects(rect); } for (int count = shapes.count() - 1; count >= 0; count--) { KoShape *shape = shapes.at(count); if (omitHiddenShapes && !shape->isVisible()) { shapes.removeAt(count); } else { const QPainterPath outline = shape->absoluteTransformation(0).map(shape->outline()); if (!containedMode && !outline.intersects(rect) && !outline.contains(rect)) { shapes.removeAt(count); } else if (containedMode) { QPainterPath containingPath; containingPath.addRect(rect); if (!containingPath.contains(outline)) { shapes.removeAt(count); } } } } return shapes; } void KoShapeManager::update(const QRectF &rect, const KoShape *shape, bool selectionHandles) { + if (d->updatesBlocked) return; + { QMutexLocker l(&d->shapesMutex); d->compressedUpdate |= rect; if (selectionHandles) { d->compressedUpdatedShapes.insert(shape); } } d->updateCompressor.start(); } + +void KoShapeManager::setUpdatesBlocked(bool value) +{ + d->updatesBlocked = value; +} + +bool KoShapeManager::updatesBlocked() const +{ + return d->updatesBlocked; +} void KoShapeManager::notifyShapeChanged(KoShape *shape) { { QMutexLocker l(&d->treeMutex); Q_ASSERT(shape); if (d->aggregate4update.contains(shape)) { return; } d->aggregate4update.insert(shape); d->shapeIndexesBeforeUpdate.insert(shape, shape->zIndex()); } KoShapeContainer *container = dynamic_cast(shape); if (container) { Q_FOREACH (KoShape *child, container->shapes()) notifyShapeChanged(child); } } QList KoShapeManager::shapes() const { QMutexLocker l(&d->shapesMutex); return d->shapes; } QList KoShapeManager::topLevelShapes() const { QMutexLocker l(&d->shapesMutex); QList shapes; // get all toplevel shapes Q_FOREACH (KoShape *shape, d->shapes) { if (!shape->parent() || dynamic_cast(shape->parent())) { shapes.append(shape); } } return shapes; } KoSelection *KoShapeManager::selection() const { return d->selection; } KoCanvasBase *KoShapeManager::canvas() { return d->canvas; } //have to include this because of Q_PRIVATE_SLOT #include "moc_KoShapeManager.cpp" diff --git a/libs/flake/KoShapeManager.h b/libs/flake/KoShapeManager.h index dd5f1f030f..ac747b7265 100644 --- a/libs/flake/KoShapeManager.h +++ b/libs/flake/KoShapeManager.h @@ -1,215 +1,226 @@ /* This file is part of the KDE project Copyright (C) 2006-2008 Thorsten Zachmann Copyright (C) 2007, 2009 Thomas Zander 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 KOSHAPEMANAGER_H #define KOSHAPEMANAGER_H #include #include #include #include "KoFlake.h" #include "kritaflake_export.h" class KoShape; class KoSelection; class KoViewConverter; class KoCanvasBase; class KoPointerEvent; class KoShapePaintingContext; class QPainter; class QPointF; class QRectF; /** * The shape manager hold a list of all shape which are in scope. * There is one shape manager per canvas. This makes the shape manager * different from QGraphicsScene, which contains the datamodel for all * graphics items: KoShapeManager only contains the subset of shapes * that are shown in its canvas. * * The selection in the different views can be different. */ class KRITAFLAKE_EXPORT KoShapeManager : public QObject { Q_OBJECT public: /// enum for add() enum Repaint { PaintShapeOnAdd, ///< Causes each shapes 'update()' to be called after being added to the shapeManager AddWithoutRepaint ///< Avoids each shapes 'update()' to be called for faster addition when its possible. }; /** * Constructor. */ explicit KoShapeManager(KoCanvasBase *canvas); /** * Constructor that takes a list of shapes, convenience version. * @param shapes the shapes to start out with, see also setShapes() * @param canvas the canvas this shape manager is working on. */ KoShapeManager(KoCanvasBase *canvas, const QList &shapes); ~KoShapeManager() override; /** * Remove all previously owned shapes and make the argument list the new shapes * to be managed by this manager. * @param shapes the new shapes to manage. * @param repaint if true it will trigger a repaint of the shapes */ void setShapes(const QList &shapes, Repaint repaint = PaintShapeOnAdd); /// returns the list of maintained shapes QList shapes() const; /** * Get a list of all shapes that don't have a parent. */ QList topLevelShapes() const; public Q_SLOTS: /** * Add a KoShape to be displayed and managed by this manager. * This will trigger a repaint of the shape. * @param shape the shape to add * @param repaint if true it will trigger a repaint of the shape */ void addShape(KoShape *shape, KoShapeManager::Repaint repaint = PaintShapeOnAdd); /** * Remove a KoShape from this manager * @param shape the shape to remove */ void remove(KoShape *shape); public: /// return the selection shapes for this shapeManager KoSelection *selection() const; /** * Paint all shapes and their selection handles etc. * @param painter the painter to paint to. * @param forPrint if true, make sure only actual content is drawn and no decorations. * @param converter to convert between document and view coordinates. */ void paint(QPainter &painter, const KoViewConverter &converter, bool forPrint); /** * Returns the shape located at a specific point in the document. * If more than one shape is located at the specific point, the given selection type * controls which of them is returned. * @param position the position in the document coordinate system. * @param selection controls which shape is returned when more than one shape is at the specific point * @param omitHiddenShapes if true, only visible shapes are considered */ KoShape *shapeAt(const QPointF &position, KoFlake::ShapeSelection selection = KoFlake::ShapeOnTop, bool omitHiddenShapes = true); /** * Returns the shapes which intersects the specific rect in the document. * @param rect the rectangle in the document coordinate system. * @param omitHiddenShapes if @c true, only visible shapes are considered * @param containedMode if @c true use contained mode */ QList shapesAt(const QRectF &rect, bool omitHiddenShapes = true, bool containedMode = false); /** * Request a repaint to be queued. * The repaint will be restricted to the parameters rectangle, which is expected to be * in points (the document coordinates system of KoShape) and it is expected to be * normalized and based in the global coordinates, not any local coordinates. *

This method will return immediately and only request a repaint. Successive calls * will be merged into an appropriate repaint action. * @param rect the rectangle (in pt) to queue for repaint. * @param shape the shape that is going to be redrawn; only needed when selectionHandles=true * @param selectionHandles if true; find out if the shape is selected and repaint its * selection handles at the same time. */ void update(const QRectF &rect, const KoShape *shape = 0, bool selectionHandles = false); + /** + * Block all updates initiated with update() call. The incoming updates will + * be dropped completely. + */ + void setUpdatesBlocked(bool value); + + /** + * \see setUpdatesBlocked() + */ + bool updatesBlocked() const; + /** * Update the tree for finding the shapes. * This will remove the shape from the tree and will reinsert it again. * The update to the tree will be posponed until it is needed so that successive calls * will be merged into one. * @param shape the shape to updated its position in the tree. */ void notifyShapeChanged(KoShape *shape); /** * Paint a shape * * @param shape the shape to paint * @param painter the painter to paint to. * @param converter to convert between document and view coordinates. * @param paintContext the painting context */ static void paintShape(KoShape *shape, QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext); /** * @brief renderSingleShape renders a shape on \p painter. This method includes all the * needed steps for painting a single shape: setting transformations, clipping and masking. */ static void renderSingleShape(KoShape *shape, QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext); /** * A special interface for KoShape to use during shape destruction. Don't use this * interface directly unless you are KoShape. */ struct ShapeInterface { ShapeInterface(KoShapeManager *_q); /** * Called by a shape when it is destructed. Please note that you cannot access * any shape's method type or information during this call because the shape might be * semi-destroyed. */ void notifyShapeDestructed(KoShape *shape); protected: KoShapeManager *q; }; ShapeInterface* shapeInterface(); Q_SIGNALS: /// emitted when the selection is changed void selectionChanged(); /// emitted when an object in the selection is changed (moved/rotated etc) void selectionContentChanged(); /// emitted when any object changed (moved/rotated etc) void contentChanged(); private: KoCanvasBase *canvas(); class Private; Private * const d; Q_PRIVATE_SLOT(d, void updateTree()) Q_PRIVATE_SLOT(d, void forwardCompressedUdpate()) }; #endif diff --git a/libs/flake/KoShapeManager_p.h b/libs/flake/KoShapeManager_p.h index 8180a94f34..5371a8d074 100644 --- a/libs/flake/KoShapeManager_p.h +++ b/libs/flake/KoShapeManager_p.h @@ -1,130 +1,132 @@ /* This file is part of the KDE project Copyright (C) 2006-2008 Thorsten Zachmann Copyright (C) 2006-2010 Thomas Zander Copyright (C) 2009-2010 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. */ #ifndef KoShapeManager_p_h #define KoShapeManager_p_h #include "KoSelection.h" #include "KoShape.h" #include "KoShape_p.h" #include "KoShapeContainer.h" #include "KoShapeManager.h" #include #include #include "kis_thread_safe_signal_compressor.h" class KoCanvasBase; class KoShapeGroup; class KoShapePaintingContext; class QPainter; class Q_DECL_HIDDEN KoShapeManager::Private { public: Private(KoShapeManager *shapeManager, KoCanvasBase *c) : selection(new KoSelection(shapeManager)), canvas(c), tree(4, 2), q(shapeManager), shapeInterface(shapeManager), updateCompressor(100, KisSignalCompressor::FIRST_ACTIVE) { } ~Private() { delete selection; } /** * Update the tree when there are shapes in m_aggregate4update. This is done so not all * updates to the tree are done when they are asked for but when they are needed. */ void updateTree(); void forwardCompressedUdpate(); /** * Returns whether the shape should be added to the RTree for collision and ROI * detection. */ bool shapeUsedInRenderingTree(KoShape *shape); /** * Recursively detach the shapes from this shape manager */ void unlinkFromShapesRecursively(const QList &shapes); /** * Recursively paints the given group shape to the specified painter * This is needed for filter effects on group shapes where the filter effect * applies to all the children of the group shape at once */ static void paintGroup(KoShapeGroup *group, QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext); class DetectCollision { public: DetectCollision() {} void detect(KoRTree &tree, KoShape *s, int prevZIndex) { Q_FOREACH (KoShape *shape, tree.intersects(s->boundingRect())) { bool isChild = false; KoShapeContainer *parent = s->parent(); while (parent && !isChild) { if (parent == shape) isChild = true; parent = parent->parent(); } if (isChild) continue; if (s->zIndex() <= shape->zIndex() && prevZIndex <= shape->zIndex()) // Moving a shape will only make it collide with shapes below it. continue; if (shape->collisionDetection() && !shapesWithCollisionDetection.contains(shape)) shapesWithCollisionDetection.append(shape); } } void fireSignals() { Q_FOREACH (KoShape *shape, shapesWithCollisionDetection) shape->shapeChangedPriv(KoShape::CollisionDetected); } private: QList shapesWithCollisionDetection; }; QList shapes; KoSelection *selection; KoCanvasBase *canvas; KoRTree tree; QSet aggregate4update; QHash shapeIndexesBeforeUpdate; KoShapeManager *q; KoShapeManager::ShapeInterface shapeInterface; QMutex shapesMutex; QMutex treeMutex; KisThreadSafeSignalCompressor updateCompressor; QRectF compressedUpdate; QSet compressedUpdatedShapes; + + bool updatesBlocked = false; }; #endif diff --git a/libs/flake/KoToolManager.cpp b/libs/flake/KoToolManager.cpp index 956caf2479..2932f06b8b 100644 --- a/libs/flake/KoToolManager.cpp +++ b/libs/flake/KoToolManager.cpp @@ -1,976 +1,953 @@ /* This file is part of the KDE project * * Copyright (c) 2005-2010 Boudewijn Rempt * Copyright (C) 2006-2008 Thomas Zander * Copyright (C) 2006 Thorsten Zachmann * Copyright (C) 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. */ // flake #include "KoToolManager.h" #include "KoToolManager_p.h" #include "KoToolRegistry.h" #include "KoToolProxy.h" #include "KoToolProxy_p.h" #include "KoSelection.h" #include "KoCanvasController.h" #include "KoCanvasControllerWidget.h" #include "KoShape.h" #include "KoShapeLayer.h" #include "KoShapeRegistry.h" #include "KoShapeManager.h" #include "KoSelectedShapesProxy.h" #include "KoCanvasBase.h" #include "KoInputDeviceHandlerRegistry.h" #include "KoInputDeviceHandlerEvent.h" #include "KoPointerEvent.h" -#include "tools/KoCreateShapesTool.h" #include "tools/KoZoomTool.h" #include "kis_action_registry.h" #include "KoToolFactoryBase.h" #include "kis_assert.h" #include // Qt + kde #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include Q_GLOBAL_STATIC(KoToolManager, s_instance) class CanvasData { public: CanvasData(KoCanvasController *cc, const KoInputDevice &id) : activeTool(0), canvas(cc), inputDevice(id), dummyToolWidget(0), dummyToolLabel(0) { } ~CanvasData() { // the dummy tool widget does not necessarily have a parent and we create it, so we delete it. delete dummyToolWidget; } void activateToolActions() { toolActions.clear(); disabledGlobalActions.clear(); KActionCollection *windowActionCollection = canvas->actionCollection(); if (!windowActionCollection) { qWarning() << "We haven't got an action collection"; return; } QStringList globalActions; QMap shortcutMap; //qDebug() << "................... activating tool" << activeToolId; Q_FOREACH(QAction *action, windowActionCollection->actions()) { //qDebug() << "Action" << action->objectName() << "shortcuts" << action->shortcuts(); if (action->property("tool_action").isValid()) { QStringList tools = action->property("tool_action").toStringList(); //qDebug() << "\tassociated with" << tools; if (tools.contains(activeToolId)) { //qDebug() << "\t\tenabling"; action->setEnabled(true); toolActions << action->objectName(); } else { action->setDisabled(true); } } else { globalActions << action->objectName(); } Q_FOREACH(QKeySequence keySequence, action->shortcuts()) { // After loading a custom shortcut profile, shortcuts can be defined as an empty string, which is not an empty shortcut if (keySequence.toString() != "") { if (shortcutMap.contains(keySequence)) { shortcutMap[keySequence].append(action->objectName()); } else { shortcutMap[keySequence] = QStringList() << action->objectName(); } } } } // Make sure the tool's actions override the global actions that aren't associated with the tool. Q_FOREACH(const QKeySequence &k, shortcutMap.keys()) { if (shortcutMap[k].size() > 1) { QStringList actions = shortcutMap[k]; //qDebug() << k << actions; bool toolActionFound = false; Q_FOREACH(const QString &action, actions) { if (toolActions.contains(action)) { toolActionFound = true; } } Q_FOREACH(const QString &action, actions) { if (toolActionFound && globalActions.contains(action)) { //qDebug() << "\tdisabling global action" << action; windowActionCollection->action(action)->setEnabled(false); disabledGlobalActions << action; } } //qDebug() << k << shortcutMap[k]; } } windowActionCollection->readSettings(); // The shortcuts might have been configured in the meantime. } void deactivateToolActions() { if (!activeTool) return; //qDebug() << "............... deactivating previous tool because activating" << activeToolId; KActionCollection *windowActionCollection = canvas->actionCollection(); Q_FOREACH(const QString &action, toolActions) { //qDebug() << "disabling" << action; windowActionCollection->action(action)->setDisabled(true); } Q_FOREACH(const QString &action, disabledGlobalActions) { //qDebug() << "enabling" << action; windowActionCollection->action(action)->setEnabled(true); } } KoToolBase *activeTool; // active Tool QString activeToolId; // the id of the active Tool QString activationShapeId; // the shape-type (KoShape::shapeId()) the activeTool 'belongs' to. QHash allTools; // all the tools that are created for this canvas. QStack stack; // stack of temporary tools KoCanvasController *const canvas; const KoInputDevice inputDevice; QWidget *dummyToolWidget; // the widget shown in the toolDocker. QLabel *dummyToolLabel; QStringList toolActions; QStringList disabledGlobalActions; }; // ******** KoToolManager ********** KoToolManager::KoToolManager() : QObject(), d(new Private(this)) { connect(QApplication::instance(), SIGNAL(focusChanged(QWidget*,QWidget*)), this, SLOT(movedFocus(QWidget*,QWidget*))); } KoToolManager::~KoToolManager() { delete d; } QList KoToolManager::toolActionList() const { QList answer; answer.reserve(d->tools.count()); Q_FOREACH (ToolHelper *tool, d->tools) { - if (tool->id() == KoCreateShapesTool_ID) - continue; // don't show this one. answer.append(tool->toolAction()); } return answer; } void KoToolManager::requestToolActivation(KoCanvasController * controller) { if (d->canvasses.contains(controller)) { QString activeToolId = d->canvasses.value(controller).first()->activeToolId; Q_FOREACH (ToolHelper * th, d->tools) { if (th->id() == activeToolId) { d->toolActivated(th); break; } } } } KoInputDevice KoToolManager::currentInputDevice() const { return d->inputDevice; } void KoToolManager::registerToolActions(KActionCollection *ac, KoCanvasController *controller) { Q_ASSERT(controller); Q_ASSERT(ac); d->setup(); if (!d->canvasses.contains(controller)) { return; } // Actions used to switch tools via shortcuts Q_FOREACH (ToolHelper * th, d->tools) { if (ac->action(th->id())) { continue; } ShortcutToolAction* action = th->createShortcutToolAction(ac); ac->addCategorizedAction(th->id(), action, "tool-shortcuts"); } } void KoToolManager::addController(KoCanvasController *controller) { Q_ASSERT(controller); if (d->canvasses.contains(controller)) return; d->setup(); d->attachCanvas(controller); connect(controller->proxyObject, SIGNAL(destroyed(QObject*)), this, SLOT(attemptCanvasControllerRemoval(QObject*))); connect(controller->proxyObject, SIGNAL(canvasRemoved(KoCanvasController*)), this, SLOT(detachCanvas(KoCanvasController*))); connect(controller->proxyObject, SIGNAL(canvasSet(KoCanvasController*)), this, SLOT(attachCanvas(KoCanvasController*))); } void KoToolManager::removeCanvasController(KoCanvasController *controller) { Q_ASSERT(controller); disconnect(controller->proxyObject, SIGNAL(canvasRemoved(KoCanvasController*)), this, SLOT(detachCanvas(KoCanvasController*))); disconnect(controller->proxyObject, SIGNAL(canvasSet(KoCanvasController*)), this, SLOT(attachCanvas(KoCanvasController*))); d->detachCanvas(controller); } void KoToolManager::attemptCanvasControllerRemoval(QObject* controller) { KoCanvasControllerProxyObject* controllerActual = qobject_cast(controller); if (controllerActual) { removeCanvasController(controllerActual->canvasController()); } } void KoToolManager::switchToolRequested(const QString & id) { Q_ASSERT(d->canvasData); if (!d->canvasData) return; while (!d->canvasData->stack.isEmpty()) // switching means to flush the stack d->canvasData->stack.pop(); d->switchTool(id, false); } void KoToolManager::switchInputDeviceRequested(const KoInputDevice &id) { if (!d->canvasData) return; d->switchInputDevice(id); } void KoToolManager::switchToolTemporaryRequested(const QString &id) { d->switchTool(id, true); } void KoToolManager::switchBackRequested() { if (!d->canvasData) return; if (d->canvasData->stack.isEmpty()) { // default to changing to the interactionTool d->switchTool(KoInteractionTool_ID, false); return; } d->switchTool(d->canvasData->stack.pop(), false); } -KoCreateShapesTool * KoToolManager::shapeCreatorTool(KoCanvasBase *canvas) const -{ - Q_ASSERT(canvas); - Q_FOREACH (KoCanvasController *controller, d->canvasses.keys()) { - if (controller->canvas() == canvas) { - KoCreateShapesTool *createTool = dynamic_cast - (d->canvasData->allTools.value(KoCreateShapesTool_ID)); - Q_ASSERT(createTool /* ID changed? */); - return createTool; - } - } - Q_ASSERT(0); // this should not happen - return 0; -} - KoToolBase *KoToolManager::toolById(KoCanvasBase *canvas, const QString &id) const { Q_ASSERT(canvas); Q_FOREACH (KoCanvasController *controller, d->canvasses.keys()) { if (controller->canvas() == canvas) return d->canvasData->allTools.value(id); } return 0; } KoCanvasController *KoToolManager::activeCanvasController() const { if (! d->canvasData) return 0; return d->canvasData->canvas; } QString KoToolManager::preferredToolForSelection(const QList &shapes) { QSet shapeTypes; Q_FOREACH (KoShape *shape, shapes) { shapeTypes << shape->shapeId(); } //KritaUtils::makeContainerUnique(types); QString toolType = KoInteractionTool_ID; int prio = INT_MAX; Q_FOREACH (ToolHelper *helper, d->tools) { - if (helper->id() == KoCreateShapesTool_ID) continue; if (helper->priority() >= prio) continue; bool toolWillWork = false; foreach (const QString &type, shapeTypes) { if (helper->activationShapeId().split(',').contains(type)) { toolWillWork = true; break; } } if (toolWillWork) { toolType = helper->id(); prio = helper->priority(); } } return toolType; } QPair KoToolManager::createTools(KoCanvasController *controller, ToolHelper *tool) { // XXX: maybe this method should go into the private class? QHash origHash; if (d->canvasses.contains(controller)) { origHash = d->canvasses.value(controller).first()->allTools; } if (origHash.contains(tool->id())) { return QPair(tool->id(), origHash.value(tool->id())); } debugFlake << "Creating tool" << tool->id() << ". Activated on:" << tool->activationShapeId() << ", prio:" << tool->priority(); KoToolBase *tl = tool->createTool(controller->canvas()); if (tl) { d->uniqueToolIds.insert(tl, tool->uniqueId()); tl->setObjectName(tool->id()); } KoZoomTool *zoomTool = dynamic_cast(tl); if (zoomTool) { zoomTool->setCanvasController(controller); } return QPair(tool->id(), tl); } void KoToolManager::initializeCurrentToolForCanvas() { d->postSwitchTool(false); } KoToolManager* KoToolManager::instance() { return s_instance; } QString KoToolManager::activeToolId() const { if (!d->canvasData) return QString(); return d->canvasData->activeToolId; } KoToolManager::Private *KoToolManager::priv() { return d; } /**** KoToolManager::Private ****/ KoToolManager::Private::Private(KoToolManager *qq) : q(qq), canvasData(0), layerExplicitlyDisabled(false) { } KoToolManager::Private::~Private() { qDeleteAll(tools); } // helper method. CanvasData *KoToolManager::Private::createCanvasData(KoCanvasController *controller, const KoInputDevice &device) { QHash toolsHash; Q_FOREACH (ToolHelper *tool, tools) { QPair toolPair = q->createTools(controller, tool); if (toolPair.second) { // only if a real tool was created toolsHash.insert(toolPair.first, toolPair.second); } } - KoCreateShapesTool *createShapesTool = dynamic_cast(toolsHash.value(KoCreateShapesTool_ID)); - KIS_ASSERT(createShapesTool); - QString id = KoShapeRegistry::instance()->keys()[0]; - createShapesTool->setShapeId(id); CanvasData *cd = new CanvasData(controller, device); cd->allTools = toolsHash; return cd; } void KoToolManager::Private::setup() { if (tools.size() > 0) return; KoShapeRegistry::instance(); KoToolRegistry *registry = KoToolRegistry::instance(); Q_FOREACH (const QString & id, registry->keys()) { ToolHelper *t = new ToolHelper(registry->value(id)); tools.append(t); } // connect to all tools so we can hear their button-clicks Q_FOREACH (ToolHelper *tool, tools) connect(tool, SIGNAL(toolActivated(ToolHelper*)), q, SLOT(toolActivated(ToolHelper*))); // load pluggable input devices KoInputDeviceHandlerRegistry::instance(); } void KoToolManager::Private::connectActiveTool() { if (canvasData->activeTool) { connect(canvasData->activeTool, SIGNAL(cursorChanged(QCursor)), q, SLOT(updateCursor(QCursor))); connect(canvasData->activeTool, SIGNAL(activateTool(QString)), q, SLOT(switchToolRequested(QString))); connect(canvasData->activeTool, SIGNAL(activateTemporary(QString)), q, SLOT(switchToolTemporaryRequested(QString))); connect(canvasData->activeTool, SIGNAL(done()), q, SLOT(switchBackRequested())); connect(canvasData->activeTool, SIGNAL(statusTextChanged(QString)), q, SIGNAL(changedStatusText(QString))); } // we expect the tool to emit a cursor on activation. updateCursor(Qt::ForbiddenCursor); } void KoToolManager::Private::disconnectActiveTool() { if (canvasData->activeTool) { canvasData->deactivateToolActions(); // repaint the decorations before we deactivate the tool as it might deleted // data needed for the repaint emit q->aboutToChangeTool(canvasData->canvas); canvasData->activeTool->deactivate(); disconnect(canvasData->activeTool, SIGNAL(cursorChanged(QCursor)), q, SLOT(updateCursor(QCursor))); disconnect(canvasData->activeTool, SIGNAL(activateTool(QString)), q, SLOT(switchToolRequested(QString))); disconnect(canvasData->activeTool, SIGNAL(activateTemporary(QString)), q, SLOT(switchToolTemporaryRequested(QString))); disconnect(canvasData->activeTool, SIGNAL(done()), q, SLOT(switchBackRequested())); disconnect(canvasData->activeTool, SIGNAL(statusTextChanged(QString)), q, SIGNAL(changedStatusText(QString))); } // emit a empty status text to clear status text from last active tool emit q->changedStatusText(QString()); } void KoToolManager::Private::switchTool(KoToolBase *tool, bool temporary) { Q_ASSERT(tool); if (canvasData == 0) return; if (canvasData->activeTool == tool && tool->toolId() != KoInteractionTool_ID) return; disconnectActiveTool(); canvasData->activeTool = tool; connectActiveTool(); postSwitchTool(temporary); } void KoToolManager::Private::switchTool(const QString &id, bool temporary) { Q_ASSERT(canvasData); if (!canvasData) return; if (canvasData->activeTool && temporary) canvasData->stack.push(canvasData->activeToolId); canvasData->activeToolId = id; KoToolBase *tool = canvasData->allTools.value(id); if (! tool) { return; } Q_FOREACH (ToolHelper *th, tools) { if (th->id() == id) { canvasData->activationShapeId = th->activationShapeId(); break; } } switchTool(tool, temporary); } void KoToolManager::Private::postSwitchTool(bool temporary) { #ifndef NDEBUG int canvasCount = 1; Q_FOREACH (QList list, canvasses) { bool first = true; Q_FOREACH (CanvasData *data, list) { if (first) { debugFlake << "Canvas" << canvasCount++; } debugFlake << " +- Tool:" << data->activeToolId << (data == canvasData ? " *" : ""); first = false; } } #endif Q_ASSERT(canvasData); if (!canvasData) return; KoToolBase::ToolActivation toolActivation; if (temporary) toolActivation = KoToolBase::TemporaryActivation; else toolActivation = KoToolBase::DefaultActivation; QSet shapesToOperateOn; if (canvasData->activeTool && canvasData->activeTool->canvas() && canvasData->activeTool->canvas()->shapeManager()) { KoSelection *selection = canvasData->activeTool->canvas()->shapeManager()->selection(); Q_ASSERT(selection); shapesToOperateOn = QSet::fromList(selection->selectedEditableShapesAndDelegates()); } if (canvasData->canvas->canvas()) { // Caller of postSwitchTool expect this to be called to update the selected tool updateToolForProxy(); canvasData->activeTool->activate(toolActivation, shapesToOperateOn); KoCanvasBase *canvas = canvasData->canvas->canvas(); canvas->updateInputMethodInfo(); } else { canvasData->activeTool->activate(toolActivation, shapesToOperateOn); } QList > optionWidgetList = canvasData->activeTool->optionWidgets(); if (optionWidgetList.empty()) { // no option widget. QWidget *toolWidget; QString title; Q_FOREACH (ToolHelper *tool, tools) { if (tool->id() == canvasData->activeTool->toolId()) { title = tool->toolTip(); break; } } toolWidget = canvasData->dummyToolWidget; if (toolWidget == 0) { toolWidget = new QWidget(); toolWidget->setObjectName("DummyToolWidget"); QVBoxLayout *layout = new QVBoxLayout(toolWidget); layout->setMargin(3); canvasData->dummyToolLabel = new QLabel(toolWidget); layout->addWidget(canvasData->dummyToolLabel); layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::Expanding)); toolWidget->setLayout(layout); canvasData->dummyToolWidget = toolWidget; } canvasData->dummyToolLabel->setText(i18n("Active tool: %1", title)); optionWidgetList.append(toolWidget); } // Activate the actions for the currently active tool canvasData->activateToolActions(); emit q->changedTool(canvasData->canvas, uniqueToolIds.value(canvasData->activeTool)); emit q->toolOptionWidgetsChanged(canvasData->canvas, optionWidgetList); } void KoToolManager::Private::switchCanvasData(CanvasData *cd) { Q_ASSERT(cd); KoCanvasBase *oldCanvas = 0; KoInputDevice oldInputDevice; if (canvasData) { oldCanvas = canvasData->canvas->canvas(); oldInputDevice = canvasData->inputDevice; if (canvasData->activeTool) { disconnectActiveTool(); } KoToolProxy *proxy = proxies.value(oldCanvas); Q_ASSERT(proxy); proxy->setActiveTool(0); } canvasData = cd; inputDevice = canvasData->inputDevice; if (canvasData->activeTool) { connectActiveTool(); postSwitchTool(false); } if (oldInputDevice != canvasData->inputDevice) { emit q->inputDeviceChanged(canvasData->inputDevice); } if (oldCanvas != canvasData->canvas->canvas()) { emit q->changedCanvas(canvasData->canvas->canvas()); } } void KoToolManager::Private::toolActivated(ToolHelper *tool) { Q_ASSERT(tool); Q_ASSERT(canvasData); if (!canvasData) return; KoToolBase *t = canvasData->allTools.value(tool->id()); Q_ASSERT(t); canvasData->activeToolId = tool->id(); canvasData->activationShapeId = tool->activationShapeId(); switchTool(t, false); } void KoToolManager::Private::detachCanvas(KoCanvasController *controller) { Q_ASSERT(controller); // check if we are removing the active canvas controller if (canvasData && canvasData->canvas == controller) { KoCanvasController *newCanvas = 0; // try to find another canvas controller beside the one we are removing Q_FOREACH (KoCanvasController* canvas, canvasses.keys()) { if (canvas != controller) { // yay found one newCanvas = canvas; break; } } if (newCanvas) { switchCanvasData(canvasses.value(newCanvas).first()); } else { disconnectActiveTool(); emit q->toolOptionWidgetsChanged(controller, QList >()); // as a last resort just set a blank one canvasData = 0; } } KoToolProxy *proxy = proxies.value(controller->canvas()); if (proxy) proxy->setActiveTool(0); QList tools; Q_FOREACH (CanvasData *canvasData, canvasses.value(controller)) { Q_FOREACH (KoToolBase *tool, canvasData->allTools) { if (! tools.contains(tool)) { tools.append(tool); } } delete canvasData; } Q_FOREACH (KoToolBase *tool, tools) { uniqueToolIds.remove(tool); delete tool; } canvasses.remove(controller); emit q->changedCanvas(canvasData ? canvasData->canvas->canvas() : 0); } void KoToolManager::Private::attachCanvas(KoCanvasController *controller) { Q_ASSERT(controller); CanvasData *cd = createCanvasData(controller, KoInputDevice::mouse()); // switch to new canvas as the active one. switchCanvasData(cd); inputDevice = cd->inputDevice; QList canvasses_; canvasses_.append(cd); canvasses[controller] = canvasses_; KoToolProxy *tp = proxies[controller->canvas()]; if (tp) tp->priv()->setCanvasController(controller); if (cd->activeTool == 0) { // no active tool, so we activate the highest priority main tool int highestPriority = INT_MAX; ToolHelper * helper = 0; Q_FOREACH (ToolHelper * th, tools) { if (th->section() == KoToolFactoryBase::mainToolType()) { if (th->priority() < highestPriority) { highestPriority = qMin(highestPriority, th->priority()); helper = th; } } } if (helper) toolActivated(helper); } Connector *connector = new Connector(controller->canvas()->shapeManager()); connect(connector, SIGNAL(selectionChanged(QList)), q, SLOT(selectionChanged(QList))); connect(controller->canvas()->selectedShapesProxy(), SIGNAL(currentLayerChanged(const KoShapeLayer*)), q, SLOT(currentLayerChanged(const KoShapeLayer*))); emit q->changedCanvas(canvasData ? canvasData->canvas->canvas() : 0); } void KoToolManager::Private::movedFocus(QWidget *from, QWidget *to) { Q_UNUSED(from); // no canvas anyway or no focus set anyway? if (!canvasData || to == 0) { return; } // Check if this app is about QWidget-based KoCanvasControllerWidget canvasses // XXX: Focus handling for non-qwidget based canvases! KoCanvasControllerWidget *canvasControllerWidget = dynamic_cast(canvasData->canvas); if (!canvasControllerWidget) { return; } // canvasWidget is set as focusproxy for KoCanvasControllerWidget, // so all focus checks are to be done against canvasWidget objects // focus returned to current canvas? if (to == canvasData->canvas->canvas()->canvasWidget()) { // nothing to do return; } // if the 'to' is one of our canvasWidgets, then switch. // for code simplicity the current canvas will be checked again, // but would have been caught already in the lines above, so no issue KoCanvasController *newCanvas = 0; Q_FOREACH (KoCanvasController* canvas, canvasses.keys()) { if (canvas->canvas()->canvasWidget() == to) { newCanvas = canvas; break; } } // none of our canvasWidgets got focus? if (newCanvas == 0) { return; } // switch to canvasdata matching inputdevice used last with this app instance Q_FOREACH (CanvasData *data, canvasses.value(newCanvas)) { if (data->inputDevice == inputDevice) { switchCanvasData(data); return; } } // if no such inputDevice for this canvas, then simply fallback to first one switchCanvasData(canvasses.value(newCanvas).first()); } void KoToolManager::Private::updateCursor(const QCursor &cursor) { Q_ASSERT(canvasData); Q_ASSERT(canvasData->canvas); Q_ASSERT(canvasData->canvas->canvas()); canvasData->canvas->canvas()->setCursor(cursor); } void KoToolManager::Private::selectionChanged(const QList &shapes) { QList types; Q_FOREACH (KoShape *shape, shapes) { QSet delegates = shape->toolDelegates(); if (delegates.isEmpty()) { // no delegates, just the orig shape delegates << shape; } foreach (KoShape *shape2, delegates) { Q_ASSERT(shape2); if (! types.contains(shape2->shapeId())) { types.append(shape2->shapeId()); } } } // check if there is still a shape selected the active tool can work on // there needs to be at least one shape for a tool without an activationShapeId // to work // if not change the current tool to the default tool const QStringList activationShapeIds = canvasData->activationShapeId.split(','); if (!(canvasData->activationShapeId.isNull() && shapes.size() > 0) && !activationShapeIds.contains("flake/always") && !activationShapeIds.contains("flake/edit")) { bool currentToolWorks = false; foreach (const QString &type, types) { if (activationShapeIds.contains(type)) { currentToolWorks = true; break; } } if (!currentToolWorks) { switchTool(KoInteractionTool_ID, false); } } emit q->toolCodesSelected(types); } void KoToolManager::Private::currentLayerChanged(const KoShapeLayer *layer) { emit q->currentLayerChanged(canvasData->canvas, layer); layerExplicitlyDisabled = layer && !layer->isShapeEditable(); updateToolForProxy(); debugFlake << "Layer changed to" << layer << "explicitly disabled:" << layerExplicitlyDisabled; } void KoToolManager::Private::updateToolForProxy() { KoToolProxy *proxy = proxies.value(canvasData->canvas->canvas()); if(!proxy) return; bool canUseTool = !layerExplicitlyDisabled || canvasData->activationShapeId.endsWith(QLatin1String("/always")); proxy->setActiveTool(canUseTool ? canvasData->activeTool : 0); } void KoToolManager::Private::switchInputDevice(const KoInputDevice &device) { Q_ASSERT(canvasData); if (!canvasData) return; if (inputDevice == device) return; if (inputDevice.isMouse() && device.isMouse()) return; if (device.isMouse() && !inputDevice.isMouse()) { // we never switch back to mouse from a tablet input device, so the user can use the // mouse to edit the settings for a tool activated by a tablet. See bugs // https://bugs.kde.org/show_bug.cgi?id=283130 and https://bugs.kde.org/show_bug.cgi?id=285501. // We do continue to switch between tablet devices, thought. return; } QList items = canvasses[canvasData->canvas]; // search for a canvasdata object for the current input device Q_FOREACH (CanvasData *cd, items) { if (cd->inputDevice == device) { switchCanvasData(cd); if (!canvasData->activeTool) { switchTool(KoInteractionTool_ID, false); } return; } } // still here? That means we need to create a new CanvasData instance with the current InputDevice. CanvasData *cd = createCanvasData(canvasData->canvas, device); // switch to new canvas as the active one. QString oldTool = canvasData->activeToolId; items.append(cd); canvasses[cd->canvas] = items; switchCanvasData(cd); q->switchToolRequested(oldTool); } void KoToolManager::Private::registerToolProxy(KoToolProxy *proxy, KoCanvasBase *canvas) { proxies.insert(canvas, proxy); Q_FOREACH (KoCanvasController *controller, canvasses.keys()) { if (controller->canvas() == canvas) { proxy->priv()->setCanvasController(controller); break; } } } //have to include this because of Q_PRIVATE_SLOT #include "moc_KoToolManager.cpp" diff --git a/libs/flake/KoToolManager.h b/libs/flake/KoToolManager.h index 77dbe4edd1..5504755171 100644 --- a/libs/flake/KoToolManager.h +++ b/libs/flake/KoToolManager.h @@ -1,335 +1,326 @@ /* This file is part of the KDE project * Copyright (c) 2005-2006 Boudewijn Rempt * Copyright (C) 2006, 2008 Thomas Zander * Copyright (C) 2006 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 KO_TOOL_MANAGER #define KO_TOOL_MANAGER #include "KoInputDevice.h" #include "kritaflake_export.h" #include #include class KoCanvasController; class KoShapeControllerBase; class KoToolFactoryBase; class KoCanvasBase; class KoToolBase; class KoCreateShapesTool; class KActionCollection; class KoShape; class KoInputDeviceHandlerEvent; class KoShapeLayer; class ToolHelper; class QKeySequence; class QCursor; /** * This class serves as a QAction-like control object for activation of a tool. * * It allows to implement a custom UI to control the activation of tools. * See KoToolBox & KoModeBox in the kowidgets library. * * KoToolAction objects are indirectly owned by the KoToolManager singleton * and live until the end of its lifetime. */ class KRITAFLAKE_EXPORT KoToolAction : public QObject { Q_OBJECT public: // toolHelper takes over ownership, and those live till the end of KoToolManager. explicit KoToolAction(ToolHelper *toolHelper); ~KoToolAction() override; public: QString id() const; ///< The id of the tool QString iconText() const; ///< The icontext of the tool QString toolTip() const; ///< The tooltip of the tool QString iconName() const; ///< The icon name of the tool QKeySequence shortcut() const; ///< The shortcut to activate the tool QString section() const; ///< The section the tool wants to be in. int priority() const; ///< Lower number (higher priority) means coming first in the section. int buttonGroupId() const; ///< A unique ID for this tool as passed by changedTool(), >= 0 QString visibilityCode() const; ///< This tool should become visible when we emit this string in toolCodesSelected() public Q_SLOTS: void trigger(); ///< Request the activation of the tool Q_SIGNALS: void changed(); ///< Emitted when a property changes (shortcut ATM) private: friend class ToolHelper; class Private; Private *const d; }; /** * This class manages the activation and deactivation of tools for * each input device. * * Managing the active tool and switching tool based on various variables. * * The state of the toolbox will be the same for all views in the process so practically * you can say we have one toolbox per application instance (process). Implementation * does not allow one widget to be in more then one view, so we just make sure the toolbox * is hidden in not-in-focus views. * * The ToolManager is a singleton and will manage all views in all applications that * are loaded in this process. This means you will have to register and unregister your view. * When creating your new view you should use a KoCanvasController() and register that * with the ToolManager like this: @code MyGuiWidget::MyGuiWidget() { m_canvasController = new KoCanvasController(this); m_canvasController->setCanvas(m_canvas); KoToolManager::instance()->addControllers(m_canvasController)); } MyGuiWidget::~MyGuiWidget() { KoToolManager::instance()->removeCanvasController(m_canvasController); } @endcode * * For a new view that extends KoView all you need to do is implement KoView::createToolBox() * * KoToolManager also keeps track of the current tool based on a complex set of conditions and heuristics: - there is one active tool per KoCanvasController (and there is one KoCanvasController per view, because this is a class with scrollbars and a zoomlevel and so on) - for every pointing device (determined by the unique id of tablet, or 0 for mice -- you may have more than one mouse attached, but Qt cannot distinguish between them, there is an associated tool. - depending on things like tablet leave/enter proximity, incoming mouse or tablet events and a little timer (that gets stopped when we know what is what), the active pointing device is determined, and the active tool is set accordingly. Nota bene: if you use KoToolManager and register your canvases with it you no longer have to manually implement methods to route mouse, tablet, key or wheel events to the active tool. In fact, it's no longer interesting to you which tool is active; you can safely route the paint event through KoToolProxy::paint(). (The reason the input events are handled completely by the toolmanager and the paint events not is that, generally speaking, it's okay if the tools get the input events first, but you want to paint your shapes or other canvas stuff first and only then paint the tool stuff.) */ class KRITAFLAKE_EXPORT KoToolManager : public QObject { Q_OBJECT public: KoToolManager(); /// Return the toolmanager singleton static KoToolManager* instance(); ~KoToolManager() override; /** * Register actions for switching to tools at the actionCollection parameter. * The actions will have the text / shortcut as stated by the toolFactory. * If the application calls this in their KoView extending class they will have all the benefits * from allowing this in the menus and to allow the use to configure the shortcuts used. * @param ac the actionCollection that will be the parent of the actions. * @param controller tools registered with this controller will have all their actions added as well. */ void registerToolActions(KActionCollection *ac, KoCanvasController *controller); /** * Register a new canvas controller * @param controller the view controller that this toolmanager will manage the tools for */ void addController(KoCanvasController *controller); /** * Remove a set of controllers * When the controller is no longer used it should be removed so all tools can be * deleted and stop eating memory. * @param controller the controller that is removed */ void removeCanvasController(KoCanvasController *controller); /** * Attempt to remove a controller. * This is automatically called when a controller's proxy object is deleted, and * it ensures that the controller is, in fact, removed, even if the creator forgot * to do so. * @param controller the proxy object of the controller to be removed */ Q_SLOT void attemptCanvasControllerRemoval(QObject *controller); /// @return the active canvas controller KoCanvasController *activeCanvasController() const; - /** - * Return the tool that is able to create shapes for this param canvas. - * This is typically used by the KoShapeSelector to set which shape to create next. - * @param canvas the canvas that is a child of a previously registered controller - * who's tool you want. - * @see addController() - */ - KoCreateShapesTool *shapeCreatorTool(KoCanvasBase *canvas) const; - /** * Returns the tool for the given tool id. The tool may be 0 * @param canvas the canvas that is a child of a previously registered controller * who's tool you want. * @param id the tool identifier * @see addController() */ KoToolBase *toolById(KoCanvasBase *canvas, const QString &id) const; /// @return the currently active pointing device KoInputDevice currentInputDevice() const; /** * For the list of shapes find out which tool is the highest priority tool that can handle it. * @returns the toolId for the shapes. * @param shapes a list of shapes, a selection for example, that is used to look for the tool. */ QString preferredToolForSelection(const QList &shapes); /** * Returns the list of toolActions for the current tools. * @returns lists of toolActions for the current tools. */ QList toolActionList() const; /// Request tool activation for the given canvas controller void requestToolActivation(KoCanvasController *controller); /// Returns the toolId of the currently active tool QString activeToolId() const; void initializeCurrentToolForCanvas(); class Private; /** * \internal return the private object for the toolmanager. */ KoToolManager::Private *priv(); public Q_SLOTS: /** * Request switching tool * @param id the id of the tool */ void switchToolRequested(const QString &id); /** * Request change input device * @param id the id of the input device */ void switchInputDeviceRequested(const KoInputDevice &id); /** * Request for temporary switching the tools. * This switch can be later reverted with switchBackRequested(). * @param id the id of the tool * * @see switchBackRequested() */ void switchToolTemporaryRequested(const QString &id); /** * Switches back to the original tool after the temporary switch * has been done. It the user changed the tool manually on the way, * then it switches to the interaction tool */ void switchBackRequested(); Q_SIGNALS: /** * Emitted when a new tool is going to override the current tool * @param canvas the currently active canvas. */ void aboutToChangeTool(KoCanvasController *canvas); /** * Emitted when a new tool was selected or became active. * @param canvas the currently active canvas. * @param uniqueToolId a random but unique code for the new tool. */ void changedTool(KoCanvasController *canvas, int uniqueToolId); /** * Emitted after the selection changed to state which unique shape-types are now * in the selection. * @param types a list of string that are the shape types of the selected objects. */ void toolCodesSelected(const QList &types); /** * Emitted after the current layer changed either its properties or to a new layer. * @param canvas the currently active canvas. * @param layer the layer that is selected. */ void currentLayerChanged(const KoCanvasController *canvas, const KoShapeLayer *layer); /** * Every time a new input device gets used by a tool, this event is emitted. * @param device the new input device that the user picked up. */ void inputDeviceChanged(const KoInputDevice &device); /** * Emitted whenever the active canvas changed. * @param canvas the new activated canvas (might be 0) */ void changedCanvas(const KoCanvasBase *canvas); /** * Emitted whenever the active tool changes the status text. * @param statusText the new status text */ void changedStatusText(const QString &statusText); /** * emitted whenever a new tool is dynamically added for the given canvas */ void addedTool(KoToolAction *toolAction, KoCanvasController *canvas); /** * Emit the new tool option widgets to be used with this canvas. */ void toolOptionWidgetsChanged(KoCanvasController *controller, const QList > &widgets); private: KoToolManager(const KoToolManager&); KoToolManager operator=(const KoToolManager&); Q_PRIVATE_SLOT(d, void toolActivated(ToolHelper *tool)) Q_PRIVATE_SLOT(d, void detachCanvas(KoCanvasController *controller)) Q_PRIVATE_SLOT(d, void attachCanvas(KoCanvasController *controller)) Q_PRIVATE_SLOT(d, void movedFocus(QWidget *from, QWidget *to)) Q_PRIVATE_SLOT(d, void updateCursor(const QCursor &cursor)) Q_PRIVATE_SLOT(d, void selectionChanged(const QList &shapes)) Q_PRIVATE_SLOT(d, void currentLayerChanged(const KoShapeLayer *layer)) QPair createTools(KoCanvasController *controller, ToolHelper *tool); Private *const d; }; #endif diff --git a/libs/flake/KoToolRegistry.cpp b/libs/flake/KoToolRegistry.cpp index 2e7b2bcb1e..f893b339be 100644 --- a/libs/flake/KoToolRegistry.cpp +++ b/libs/flake/KoToolRegistry.cpp @@ -1,79 +1,76 @@ /* This file is part of the KDE project * Copyright (C) 2006-2007 Thomas Zander * Copyright (c) 2004 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 "KoToolRegistry.h" #include #include #include -#include "tools/KoCreateShapesToolFactory.h" -#include "tools/KoCreateShapesTool.h" #include "tools/KoPathToolFactory.h" #include "tools/KoZoomTool.h" #include "tools/KoZoomToolFactory.h" #include "KoToolManager.h" #include #include Q_GLOBAL_STATIC(KoToolRegistry, s_instance) KoToolRegistry::KoToolRegistry() : d(0) { } void KoToolRegistry::init() { KoPluginLoader::PluginsConfig config; config.group = "calligra"; config.whiteList = "ToolPlugins"; config.blacklist = "ToolPluginsDisabled"; KoPluginLoader::instance()->load(QString::fromLatin1("Calligra/Tool"), QString::fromLatin1("[X-Flake-PluginVersion] == 28"), config); // register generic tools - add(new KoCreateShapesToolFactory()); add(new KoPathToolFactory()); add(new KoZoomToolFactory()); KConfigGroup cfg = KSharedConfig::openConfig()->group("calligra"); QStringList toolsBlacklist = cfg.readEntry("ToolsBlacklist", QStringList()); foreach (const QString& toolID, toolsBlacklist) { delete value(toolID); remove(toolID); } } KoToolRegistry::~KoToolRegistry() { qDeleteAll(doubleEntries()); qDeleteAll(values()); } KoToolRegistry* KoToolRegistry::instance() { if (!s_instance.exists()) { s_instance->init(); } return s_instance; } diff --git a/libs/flake/commands/KoShapeResizeCommand.cpp b/libs/flake/commands/KoShapeResizeCommand.cpp index 727c0ddac0..454a539f19 100644 --- a/libs/flake/commands/KoShapeResizeCommand.cpp +++ b/libs/flake/commands/KoShapeResizeCommand.cpp @@ -1,127 +1,173 @@ /* * 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 "KoShapeResizeCommand.h" #include #include "kis_command_ids.h" +#include "kis_assert.h" struct Q_DECL_HIDDEN KoShapeResizeCommand::Private { QList shapes; qreal scaleX; qreal scaleY; QPointF absoluteStillPoint; bool useGlobalMode; bool usePostScaling; QTransform postScalingCoveringTransform; QList oldSizes; QList oldTransforms; }; KoShapeResizeCommand::KoShapeResizeCommand(const QList &shapes, qreal scaleX, qreal scaleY, const QPointF &absoluteStillPoint, bool useGLobalMode, bool usePostScaling, const QTransform &postScalingCoveringTransform, KUndo2Command *parent) : SkipFirstRedoBase(false, kundo2_i18n("Resize"), parent), m_d(new Private) { m_d->shapes = shapes; m_d->scaleX = scaleX; m_d->scaleY = scaleY; m_d->absoluteStillPoint = absoluteStillPoint; m_d->useGlobalMode = useGLobalMode; m_d->usePostScaling = usePostScaling; m_d->postScalingCoveringTransform = postScalingCoveringTransform; Q_FOREACH (KoShape *shape, m_d->shapes) { m_d->oldSizes << shape->size(); m_d->oldTransforms << shape->transformation(); } } KoShapeResizeCommand::~KoShapeResizeCommand() { } void KoShapeResizeCommand::redoImpl() { + QMap updates = redoNoUpdate(); + + for (auto it = updates.begin(); it != updates.end(); ++it) { + it.key()->updateAbsolute(it.value()); + } +} + +void KoShapeResizeCommand::undoImpl() +{ + QMap updates = undoNoUpdate(); + + for (auto it = updates.begin(); it != updates.end(); ++it) { + it.key()->updateAbsolute(it.value()); + } +} + +QMap KoShapeResizeCommand::redoNoUpdate() +{ + QMap updates; + Q_FOREACH (KoShape *shape, m_d->shapes) { const QRectF oldDirtyRect = shape->boundingRect(); KoFlake::resizeShape(shape, m_d->scaleX, m_d->scaleY, m_d->absoluteStillPoint, m_d->useGlobalMode, m_d->usePostScaling, m_d->postScalingCoveringTransform); - shape->updateAbsolute(oldDirtyRect | shape->boundingRect()); + updates[shape] = oldDirtyRect | shape->boundingRect(); } + + return updates; } -void KoShapeResizeCommand::undoImpl() +QMap KoShapeResizeCommand::undoNoUpdate() { + QMap updates; + for (int i = 0; i < m_d->shapes.size(); i++) { KoShape *shape = m_d->shapes[i]; const QRectF oldDirtyRect = shape->boundingRect(); shape->setSize(m_d->oldSizes[i]); shape->setTransformation(m_d->oldTransforms[i]); - shape->updateAbsolute(oldDirtyRect | shape->boundingRect()); + + updates[shape] = oldDirtyRect | shape->boundingRect(); } + + return updates; } int KoShapeResizeCommand::id() const { return KisCommandUtils::ResizeShapeId; } bool KoShapeResizeCommand::mergeWith(const KUndo2Command *command) { const KoShapeResizeCommand *other = dynamic_cast(command); if (!other || other->m_d->absoluteStillPoint != m_d->absoluteStillPoint || other->m_d->shapes != m_d->shapes || other->m_d->useGlobalMode != m_d->useGlobalMode || other->m_d->usePostScaling != m_d->usePostScaling) { return false; } // check if the significant orientations coincide if (m_d->useGlobalMode && !m_d->usePostScaling) { Qt::Orientation our = KoFlake::significantScaleOrientation(m_d->scaleX, m_d->scaleY); Qt::Orientation their = KoFlake::significantScaleOrientation(other->m_d->scaleX, other->m_d->scaleY); if (our != their) { return false; } } m_d->scaleX *= other->m_d->scaleX; m_d->scaleY *= other->m_d->scaleY; return true; } + +void KoShapeResizeCommand::replaceResizeAction(qreal scaleX, qreal scaleY, const QPointF &absoluteStillPoint) +{ + const QMap undoUpdates = undoNoUpdate(); + + m_d->scaleX = scaleX; + m_d->scaleY = scaleY; + m_d->absoluteStillPoint = absoluteStillPoint; + + const QMap redoUpdates = redoNoUpdate(); + + KIS_SAFE_ASSERT_RECOVER_NOOP(undoUpdates.size() == redoUpdates.size()); + + for (auto it = undoUpdates.begin(); it != undoUpdates.end(); ++it) { + KIS_SAFE_ASSERT_RECOVER_NOOP(redoUpdates.contains(it.key())); + it.key()->updateAbsolute(it.value() | redoUpdates[it.key()]); + } +} diff --git a/libs/flake/commands/KoShapeResizeCommand.h b/libs/flake/commands/KoShapeResizeCommand.h index 8bb322c80c..8aad9046fa 100644 --- a/libs/flake/commands/KoShapeResizeCommand.h +++ b/libs/flake/commands/KoShapeResizeCommand.h @@ -1,57 +1,63 @@ /* * 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. */ #ifndef KOSHAPERESIZECOMMAND_H #define KOSHAPERESIZECOMMAND_H #include "kritaflake_export.h" #include "kundo2command.h" #include "kis_command_utils.h" #include #include #include #include class KoShape; class KRITAFLAKE_EXPORT KoShapeResizeCommand : public KisCommandUtils::SkipFirstRedoBase { public: KoShapeResizeCommand(const QList &shapes, qreal scaleX, qreal scaleY, const QPointF &absoluteStillPoint, bool useGLobalMode, bool usePostScaling, const QTransform &postScalingCoveringTransform, KUndo2Command *parent = 0); ~KoShapeResizeCommand() override; void redoImpl() override; void undoImpl() override; + QMap redoNoUpdate(); + QMap undoNoUpdate(); + int id() const override; bool mergeWith(const KUndo2Command *command) override; + void replaceResizeAction(qreal scaleX, qreal scaleY, + const QPointF &absoluteStillPoint); + private: struct Private; QScopedPointer const m_d; }; #endif // KOSHAPERESIZECOMMAND_H diff --git a/libs/flake/tools/KoCreateShapeStrategy.cpp b/libs/flake/tools/KoCreateShapeStrategy.cpp deleted file mode 100644 index 83fbc4424e..0000000000 --- a/libs/flake/tools/KoCreateShapeStrategy.cpp +++ /dev/null @@ -1,134 +0,0 @@ -/* This file is part of the KDE project - * Copyright (C) 2006 Thomas Zander - * Copyright (C) 2006 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 "KoCreateShapeStrategy.h" -#include "KoShapeRubberSelectStrategy_p.h" -#include "KoCreateShapesTool.h" -#include "KoShape.h" -#include "KoShapeRegistry.h" -#include "KoShapeManager.h" -#include "KoCanvasBase.h" -#include "KoSelection.h" -#include "KoShapeFactoryBase.h" -#include "KoShapeController.h" -#include "KoViewConverter.h" - -#include - -#include - -KoCreateShapeStrategy::KoCreateShapeStrategy(KoCreateShapesTool *tool, const QPointF &clicked) - : KoShapeRubberSelectStrategy(tool, clicked, tool->canvas()->snapToGrid()) -{ - KoCreateShapesTool *parent = static_cast(d_ptr->tool); - KoShapeFactoryBase *factory = KoShapeRegistry::instance()->value(parent->shapeId()); - if (factory) { - const KoProperties *props = parent->shapeProperties(); - KoShape *shape; - if (props) { - shape = factory->createShape(props); - } else { - shape = factory->createDefaultShape(); - } - - m_outline = shape->outline(); - m_outlineBoundingRect = m_outline.boundingRect(); - delete shape; - } -} - -KUndo2Command* KoCreateShapeStrategy::createCommand() -{ - Q_D(KoShapeRubberSelectStrategy); - KoCreateShapesTool *parent = static_cast(d_ptr->tool); - KoShapeFactoryBase *factory = KoShapeRegistry::instance()->value(parent->shapeId()); - if (! factory) { - warnFlake << "Application requested a shape that is not registered" << parent->shapeId(); - return 0; - } - - const KoProperties *props = parent->shapeProperties(); - KoShape *shape; - if (props) - shape = factory->createShape(props, parent->canvas()->shapeController()->resourceManager()); - else - shape = factory->createDefaultShape(parent->canvas()->shapeController()->resourceManager()); - if (shape->shapeId().isEmpty()) - shape->setShapeId(factory->id()); - QRectF rect = d->selectedRect(); - shape->setPosition(rect.topLeft()); - QSizeF newSize = rect.size(); - // if the user has dragged when creating the shape, - // resize the shape to the dragged size - if (newSize.width() > 1.0 && newSize.height() > 1.0) - shape->setSize(newSize); - - KUndo2Command * cmd = parent->canvas()->shapeController()->addShape(shape, 0); - if (cmd) { - KoSelection *selection = parent->canvas()->shapeManager()->selection(); - selection->deselectAll(); - selection->select(shape); - } - return cmd; -} - -void KoCreateShapeStrategy::finishInteraction(Qt::KeyboardModifiers modifiers) -{ - Q_UNUSED(modifiers); - Q_D(KoShapeRubberSelectStrategy); - d->tool->canvas()->updateCanvas(d->selectedRect()); -} - -void KoCreateShapeStrategy::paint(QPainter &painter, const KoViewConverter &converter) -{ - Q_D(KoShapeRubberSelectStrategy); - if (m_outline.isEmpty()) - KoShapeRubberSelectStrategy::paint(painter, converter); - else { - painter.save(); - painter.setRenderHint(QPainter::Antialiasing, false); - - QColor selectColor(Qt::blue); // TODO make configurable - selectColor.setAlphaF(0.5); - QBrush sb(selectColor, Qt::SolidPattern); - painter.setPen(QPen(sb, 0)); - painter.setBrush(sb); - QRectF paintRect = converter.documentToView(d->selectedRect()); - - qreal xscale = paintRect.width() / m_outlineBoundingRect.width(); - qreal yscale = paintRect.height() / m_outlineBoundingRect.height(); - QTransform matrix; - matrix.translate(-m_outlineBoundingRect.left(), -m_outlineBoundingRect.top()); - matrix.scale(xscale, yscale); - painter.translate(paintRect.left(), paintRect.top()); - - painter.setTransform(matrix, true); - painter.drawPath(m_outline); - painter.restore(); - } -} - -void KoCreateShapeStrategy::handleMouseMove(const QPointF &point, Qt::KeyboardModifiers modifiers) -{ - Q_D(KoShapeRubberSelectStrategy); - KoShapeRubberSelectStrategy::handleMouseMove(point, modifiers); - if (! m_outline.isEmpty()) - d->tool->canvas()->updateCanvas(d->selectedRect()); -} diff --git a/libs/flake/tools/KoCreateShapeStrategy.h b/libs/flake/tools/KoCreateShapeStrategy.h deleted file mode 100644 index f6971d16ae..0000000000 --- a/libs/flake/tools/KoCreateShapeStrategy.h +++ /dev/null @@ -1,57 +0,0 @@ -/* This file is part of the KDE project - * Copyright (C) 2006 Thomas Zander - * Copyright (C) 2006 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 KOCREATESHAPESTRATEGY_H -#define KOCREATESHAPESTRATEGY_H - -#include "KoShapeRubberSelectStrategy.h" - -#include -#include - -class KoCreateShapesTool; - -/** - * A strategy for the KoCreateShapesTool. - */ -class KoCreateShapeStrategy : public KoShapeRubberSelectStrategy -{ -public: - /** - * Constructor that starts to create a new shape. - * @param tool the parent tool which controls this strategy - * @param clicked the initial point that the user depressed (in pt). - */ - KoCreateShapeStrategy(KoCreateShapesTool *tool, const QPointF &clicked); - ~KoCreateShapeStrategy() override {} - - void finishInteraction(Qt::KeyboardModifiers modifiers) override; - KUndo2Command* createCommand() override; - void paint(QPainter &painter, const KoViewConverter &converter) override; - void handleMouseMove(const QPointF &mouseLocation, Qt::KeyboardModifiers modifiers) override; - -private: - QPainterPath m_outline; - QRectF m_outlineBoundingRect; - Q_DECLARE_PRIVATE(KoShapeRubberSelectStrategy) -}; - -#endif - diff --git a/libs/flake/tools/KoCreateShapesTool.cpp b/libs/flake/tools/KoCreateShapesTool.cpp deleted file mode 100644 index 5f811f1eae..0000000000 --- a/libs/flake/tools/KoCreateShapesTool.cpp +++ /dev/null @@ -1,98 +0,0 @@ -/* This file is part of the KDE project - * - * Copyright (C) 2006-2007 Thomas Zander - * Copyright (C) 2006 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 "KoCreateShapesTool.h" -#include "KoInteractionTool_p.h" -#include "KoPointerEvent.h" -#include "KoInteractionStrategy.h" -#include "KoCreateShapeStrategy.h" - -#include -#include - -class KoCreateShapesToolPrivate : public KoInteractionToolPrivate -{ -public: - KoCreateShapesToolPrivate(KoToolBase *qq, KoCanvasBase *canvas) - : KoInteractionToolPrivate(qq, canvas), - newShapeProperties(0) - { - } - - QString shapeId; - const KoProperties *newShapeProperties; -}; - -KoCreateShapesTool::KoCreateShapesTool(KoCanvasBase *canvas) - : KoInteractionTool(*(new KoCreateShapesToolPrivate(this, canvas))) -{ -} - -KoCreateShapesTool::~KoCreateShapesTool() -{ -} - -void KoCreateShapesTool::paint(QPainter &painter, const KoViewConverter &converter) -{ - if (currentStrategy()) - currentStrategy()->paint(painter, converter); -} - -void KoCreateShapesTool::mouseReleaseEvent(KoPointerEvent *event) -{ - KoInteractionTool::mouseReleaseEvent(event); - emit KoToolBase::done(); -} - -void KoCreateShapesTool::activate(ToolActivation, const QSet &) -{ - useCursor(Qt::ArrowCursor); -} - -void KoCreateShapesTool::setShapeId(const QString &id) -{ - Q_D(KoCreateShapesTool); - d->shapeId = id; -} - -QString KoCreateShapesTool::shapeId() const -{ - Q_D(const KoCreateShapesTool); - return d->shapeId; -} - -void KoCreateShapesTool::setShapeProperties(const KoProperties *properties) -{ - Q_D(KoCreateShapesTool); - d->newShapeProperties = properties; -} - -const KoProperties * KoCreateShapesTool::shapeProperties() -{ - Q_D(KoCreateShapesTool); - return d->newShapeProperties; -} - -KoInteractionStrategy *KoCreateShapesTool::createStrategy(KoPointerEvent *event) -{ - return new KoCreateShapeStrategy(this, event->point); -} - diff --git a/libs/flake/tools/KoCreateShapesTool.h b/libs/flake/tools/KoCreateShapesTool.h deleted file mode 100644 index 89fd409b57..0000000000 --- a/libs/flake/tools/KoCreateShapesTool.h +++ /dev/null @@ -1,87 +0,0 @@ -/* This file is part of the KDE project - * - * Copyright (C) 2006-2007 Thomas Zander - * Copyright (C) 2006 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 KOCREATESHAPESTOOL_H -#define KOCREATESHAPESTOOL_H - -#include "KoInteractionTool.h" - -#include "kritaflake_export.h" - -#include - -class KoCanvasBase; -class KoProperties; -class KoCreateShapesToolPrivate; - -#define KoCreateShapesTool_ID "CreateShapesTool" - -/** - * A tool to create shapes with. - */ -class KRITAFLAKE_EXPORT KoCreateShapesTool : public KoInteractionTool -{ -public: - /** - * Create a new tool; typically not called by applications, only by the KoToolManager - * @param canvas the canvas this tool works for. - */ - explicit KoCreateShapesTool(KoCanvasBase *canvas); - /// destructor - ~KoCreateShapesTool() override; - void mouseReleaseEvent(KoPointerEvent *event) override; - void activate(ToolActivation toolActivation, const QSet &shapes) override; - - void paint(QPainter &painter, const KoViewConverter &converter) override; - - /** - * Each shape-type has an Id; as found in KoShapeFactoryBase::id().id(), to choose which - * shape this controller should actually create; set the id before the user starts to - * create the new shape. - * @param id the SHAPEID of the to be generated shape - */ - void setShapeId(const QString &id); - /** - * return the shape Id that is to be created. - * @return the shape Id that is to be created. - */ - QString shapeId() const; - - /** - * Set the shape properties that the create controller will use for the next shape it will - * create. The tool does not take ownership of the object. - * @param properties the properties or 0 if the default shape should be created. - */ - void setShapeProperties(const KoProperties *properties); - /** - * return the properties to be used for creating the next shape - * @return the properties to be used for creating the next shape - */ - const KoProperties *shapeProperties(); - -protected: - KoInteractionStrategy *createStrategy(KoPointerEvent *event) override; - -private: - Q_DECLARE_PRIVATE(KoCreateShapesTool) -}; - -#endif diff --git a/libs/flake/tools/KoCreateShapesToolFactory.cpp b/libs/flake/tools/KoCreateShapesToolFactory.cpp deleted file mode 100644 index c2a2aec4eb..0000000000 --- a/libs/flake/tools/KoCreateShapesToolFactory.cpp +++ /dev/null @@ -1,41 +0,0 @@ -/* This file is part of the KDE project - * - * Copyright (C) 2006 Thomas Zander - * - * 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 "KoCreateShapesToolFactory.h" -#include "KoCreateShapesTool.h" - -#include - -KoCreateShapesToolFactory::KoCreateShapesToolFactory() - : KoToolFactoryBase(KoCreateShapesTool_ID) -{ - setToolTip(i18n("Create object")); - setSection(mainToolType()); - setPriority(3); -} - -KoCreateShapesToolFactory::~KoCreateShapesToolFactory() -{ -} - -KoToolBase* KoCreateShapesToolFactory::createTool(KoCanvasBase *canvas) -{ - return new KoCreateShapesTool(canvas); -} diff --git a/libs/flake/tools/KoCreateShapesToolFactory.h b/libs/flake/tools/KoCreateShapesToolFactory.h deleted file mode 100644 index 51ab3cea93..0000000000 --- a/libs/flake/tools/KoCreateShapesToolFactory.h +++ /dev/null @@ -1,37 +0,0 @@ -/* This file is part of the KDE project - * - * Copyright (C) 2006 Thomas Zander - * - * 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 KOCREATESHAPESTOOLFACTORY_H -#define KOCREATESHAPESTOOLFACTORY_H - -#include "KoToolFactoryBase.h" - -/// The factory for the KoCreateShapesTool -class KoCreateShapesToolFactory : public KoToolFactoryBase -{ -public: - /// Constructor - KoCreateShapesToolFactory(); - /// Destructor - ~KoCreateShapesToolFactory() override; - - KoToolBase* createTool(KoCanvasBase *canvas) override; -}; -#endif diff --git a/libs/global/KisSignalMapper.cpp b/libs/global/KisSignalMapper.cpp index e20b8cbd6f..5de4d23f7f 100644 --- a/libs/global/KisSignalMapper.cpp +++ b/libs/global/KisSignalMapper.cpp @@ -1,304 +1,304 @@ /**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtCore module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "KisSignalMapper.h" #include "qhash.h" class KisSignalMapper::Private { public: Private(KisSignalMapper *_q) :q(_q) {} void _q_senderDestroyed() { q->removeMappings(q->sender()); } QHash intHash; QHash stringHash; QHash widgetHash; QHash objectHash; KisSignalMapper *q; }; /*! \class KisSignalMapper \inmodule QtCore \obsolete The recommended solution is connecting the signal to a lambda. \brief The KisSignalMapper class bundles signals from identifiable senders. \ingroup objectmodel This class collects a set of parameterless signals, and re-emits them with integer, string or widget parameters corresponding to the object that sent the signal. The class supports the mapping of particular strings or integers with particular objects using setMapping(). The objects' signals can then be connected to the map() slot which will emit the mapped() signal with the string or integer associated with the - original signalling object. Mappings can be removed later using + original signaling object. Mappings can be removed later using removeMappings(). Example: Suppose we want to create a custom widget that contains a group of buttons (like a tool palette). One approach is to connect each button's \c clicked() signal to its own custom slot; but in this example we want to connect all the buttons to a single slot and parameterize the slot by the button that was clicked. Here's the definition of a simple custom widget that has a single signal, \c clicked(), which is emitted with the text of the button that was clicked: \snippet KisSignalMapper/buttonwidget.h 0 \snippet KisSignalMapper/buttonwidget.h 1 The only function that we need to implement is the constructor: \snippet KisSignalMapper/buttonwidget.cpp 0 \snippet KisSignalMapper/buttonwidget.cpp 1 \snippet KisSignalMapper/buttonwidget.cpp 2 A list of texts is passed to the constructor. A signal mapper is constructed and for each text in the list a QPushButton is created. We connect each button's \c clicked() signal to the signal mapper's map() slot, and create a mapping in the signal mapper from each button to the button's text. Finally we connect the signal mapper's mapped() signal to the custom widget's \c clicked() signal. When the user clicks a button, the custom widget will emit a single \c clicked() signal whose argument is the text of the button the user clicked. This class was mostly useful before lambda functions could be used as slots. The example above can be rewritten simpler without KisSignalMapper by connecting to a lambda function. \snippet KisSignalMapper/buttonwidget.cpp 3 \sa QObject, QButtonGroup, QActionGroup */ /*! Constructs a KisSignalMapper with parent \a parent. */ KisSignalMapper::KisSignalMapper(QObject* parent) : QObject(parent) , d(new Private(this)) { } /*! Destroys the KisSignalMapper. */ KisSignalMapper::~KisSignalMapper() { } /*! Adds a mapping so that when map() is signalled from the given \a sender, the signal mapped(\a id) is emitted. There may be at most one integer ID for each sender. \sa mapping() */ void KisSignalMapper::setMapping(QObject *sender, int id) { d->intHash.insert(sender, id); connect(sender, SIGNAL(destroyed()), this, SLOT(_q_senderDestroyed())); } /*! Adds a mapping so that when map() is signalled from the \a sender, the signal mapped(\a text ) is emitted. There may be at most one text for each sender. */ void KisSignalMapper::setMapping(QObject *sender, const QString &text) { d->stringHash.insert(sender, text); connect(sender, SIGNAL(destroyed()), this, SLOT(_q_senderDestroyed())); } /*! Adds a mapping so that when map() is signalled from the \a sender, the signal mapped(\a widget ) is emitted. There may be at most one widget for each sender. */ void KisSignalMapper::setMapping(QObject *sender, QWidget *widget) { d->widgetHash.insert(sender, widget); connect(sender, SIGNAL(destroyed()), this, SLOT(_q_senderDestroyed())); } /*! Adds a mapping so that when map() is signalled from the \a sender, the signal mapped(\a object ) is emitted. There may be at most one object for each sender. */ void KisSignalMapper::setMapping(QObject *sender, QObject *object) { d->objectHash.insert(sender, object); connect(sender, SIGNAL(destroyed()), this, SLOT(_q_senderDestroyed())); } /*! Returns the sender QObject that is associated with the \a id. \sa setMapping() */ QObject *KisSignalMapper::mapping(int id) const { return d->intHash.key(id); } /*! \overload mapping() */ QObject *KisSignalMapper::mapping(const QString &id) const { return d->stringHash.key(id); } /*! \overload mapping() Returns the sender QObject that is associated with the \a widget. */ QObject *KisSignalMapper::mapping(QWidget *widget) const { return d->widgetHash.key(widget); } /*! \overload mapping() Returns the sender QObject that is associated with the \a object. */ QObject *KisSignalMapper::mapping(QObject *object) const { return d->objectHash.key(object); } /*! Removes all mappings for \a sender. This is done automatically when mapped objects are destroyed. \note This does not disconnect any signals. If \a sender is not destroyed then this will need to be done explicitly if required. */ void KisSignalMapper::removeMappings(QObject *sender) { d->intHash.remove(sender); d->stringHash.remove(sender); d->widgetHash.remove(sender); d->objectHash.remove(sender); } /*! This slot emits signals based on which object sends signals to it. */ void KisSignalMapper::map() { map(sender()); } /*! This slot emits signals based on the \a sender object. */ void KisSignalMapper::map(QObject *sender) { if (d->intHash.contains(sender)) emit mapped(d->intHash.value(sender)); if (d->stringHash.contains(sender)) emit mapped(d->stringHash.value(sender)); if (d->widgetHash.contains(sender)) emit mapped(d->widgetHash.value(sender)); if (d->objectHash.contains(sender)) emit mapped(d->objectHash.value(sender)); } /*! \fn void KisSignalMapper::mapped(int i) This signal is emitted when map() is signalled from an object that has an integer mapping set. The object's mapped integer is passed in \a i. \sa setMapping() */ /*! \fn void KisSignalMapper::mapped(const QString &text) This signal is emitted when map() is signalled from an object that has a string mapping set. The object's mapped string is passed in \a text. \sa setMapping() */ /*! \fn void KisSignalMapper::mapped(QWidget *widget) This signal is emitted when map() is signalled from an object that has a widget mapping set. The object's mapped widget is passed in \a widget. \sa setMapping() */ /*! \fn void KisSignalMapper::mapped(QObject *object) This signal is emitted when map() is signalled from an object that has an object mapping set. The object provided by the map is passed in \a object. \sa setMapping() */ #include "moc_KisSignalMapper.cpp" diff --git a/libs/global/KisSignalMapper.h b/libs/global/KisSignalMapper.h index 354159dbac..5e19979049 100644 --- a/libs/global/KisSignalMapper.h +++ b/libs/global/KisSignalMapper.h @@ -1,255 +1,255 @@ /**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtCore module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #ifndef KisSignalMapper_H #define KisSignalMapper_H #include #include "kritaglobal_export.h" #include /*! \class KisSignalMapper \inmodule QtCore \obsolete The recommended solution is connecting the signal to a lambda. \brief The KisSignalMapper class bundles signals from identifiable senders. \ingroup objectmodel This class collects a set of parameterless signals, and re-emits them with integer, string or widget parameters corresponding to the object that sent the signal. The class supports the mapping of particular strings or integers with particular objects using setMapping(). The objects' signals can then be connected to the map() slot which will emit the mapped() signal with the string or integer associated with the - original signalling object. Mappings can be removed later using + original signaling object. Mappings can be removed later using removeMappings(). Example: Suppose we want to create a custom widget that contains a group of buttons (like a tool palette). One approach is to connect each button's \c clicked() signal to its own custom slot; but in this example we want to connect all the buttons to a single slot and parameterize the slot by the button that was clicked. Here's the definition of a simple custom widget that has a single signal, \c clicked(), which is emitted with the text of the button that was clicked: \snippet KisSignalMapper/buttonwidget.h 0 \snippet KisSignalMapper/buttonwidget.h 1 The only function that we need to implement is the constructor: \snippet KisSignalMapper/buttonwidget.cpp 0 \snippet KisSignalMapper/buttonwidget.cpp 1 \snippet KisSignalMapper/buttonwidget.cpp 2 A list of texts is passed to the constructor. A signal mapper is constructed and for each text in the list a QPushButton is created. We connect each button's \c clicked() signal to the signal mapper's map() slot, and create a mapping in the signal mapper from each button to the button's text. Finally we connect the signal mapper's mapped() signal to the custom widget's \c clicked() signal. When the user clicks a button, the custom widget will emit a single \c clicked() signal whose argument is the text of the button the user clicked. This class was mostly useful before lambda functions could be used as slots. The example above can be rewritten simpler without KisSignalMapper by connecting to a lambda function. \snippet KisSignalMapper/buttonwidget.cpp 3 \sa QObject, QButtonGroup, QActionGroup */ class KRITAGLOBAL_EXPORT KisSignalMapper : public QObject { Q_OBJECT public: explicit KisSignalMapper(QObject *parent = nullptr); /*! Destroys the KisSignalMapper. */ ~KisSignalMapper(); /*! Adds a mapping so that when map() is signalled from the given \a sender, the signal mapped(\a id) is emitted. There may be at most one integer ID for each sender. \sa mapping() */ void setMapping(QObject *sender, int id); /*! Adds a mapping so that when map() is signalled from the \a sender, the signal mapped(\a text ) is emitted. There may be at most one text for each sender. */ void setMapping(QObject *sender, const QString &text); /*! Adds a mapping so that when map() is signalled from the \a sender, the signal mapped(\a widget ) is emitted. There may be at most one widget for each sender. */ void setMapping(QObject *sender, QWidget *widget); /*! Adds a mapping so that when map() is signalled from the \a sender, the signal mapped(\a object ) is emitted. There may be at most one object for each sender. */ void setMapping(QObject *sender, QObject *object); /*! Removes all mappings for \a sender. This is done automatically when mapped objects are destroyed. \note This does not disconnect any signals. If \a sender is not destroyed then this will need to be done explicitly if required. */ void removeMappings(QObject *sender); /*! \overload mapping() */ QObject *mapping(int id) const; /*! \overload mapping() Returns the sender QObject that is associated with the \a widget. */ QObject *mapping(const QString &text) const; /*! \overload mapping() Returns the sender QObject that is associated with the \a object. */ QObject *mapping(QWidget *widget) const; /*! Returns the sender QObject that is associated with the \a id. \sa setMapping() */ QObject *mapping(QObject *object) const; Q_SIGNALS: /*! \fn void KisSignalMapper::mapped(int i) This signal is emitted when map() is signalled from an object that has an integer mapping set. The object's mapped integer is passed in \a i. \sa setMapping() */ void mapped(int); /*! \fn void KisSignalMapper::mapped(const QString &text) This signal is emitted when map() is signalled from an object that has a string mapping set. The object's mapped string is passed in \a text. \sa setMapping() */ void mapped(const QString &); /*! \fn void KisSignalMapper::mapped(QWidget *widget) This signal is emitted when map() is signalled from an object that has a widget mapping set. The object's mapped widget is passed in \a widget. \sa setMapping() */ void mapped(QWidget *); /*! \fn void KisSignalMapper::mapped(QObject *object) This signal is emitted when map() is signalled from an object that has an object mapping set. The object provided by the map is passed in \a object. \sa setMapping() */ void mapped(QObject *); public Q_SLOTS: /*! This slot emits signals based on which object sends signals to it. */ void map(); /*! This slot emits signals based on the \a sender object. */ void map(QObject *sender); private: class Private; QScopedPointer d; Q_DISABLE_COPY(KisSignalMapper) Q_PRIVATE_SLOT(d, void _q_senderDestroyed()) }; #endif // KisSignalMapper_H diff --git a/libs/global/KisUsageLogger.cpp b/libs/global/KisUsageLogger.cpp index 6a627f7dad..fdce7c3901 100644 --- a/libs/global/KisUsageLogger.cpp +++ b/libs/global/KisUsageLogger.cpp @@ -1,171 +1,206 @@ /* * Copyright (c) 2019 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 "KisUsageLogger.h" #include #include #include #include #include #include +#include #include #include #include #include #include #include Q_GLOBAL_STATIC(KisUsageLogger, s_instance) const QString KisUsageLogger::s_sectionHeader("================================================================================\n"); struct KisUsageLogger::Private { bool active {false}; QFile logFile; }; KisUsageLogger::KisUsageLogger() : d(new Private) { d->logFile.setFileName(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/krita.log"); rotateLog(); d->logFile.open(QFile::Append | QFile::Text); } KisUsageLogger::~KisUsageLogger() { if (d->active) { close(); } } void KisUsageLogger::initialize() { s_instance->d->active = true; } void KisUsageLogger::close() { - log("Closing."); + log("CLOSING SESSION"); s_instance->d->active = false; s_instance->d->logFile.flush(); s_instance->d->logFile.close(); } void KisUsageLogger::log(const QString &message) { if (!s_instance->d->active) return; if (!s_instance->d->logFile.isOpen()) return; s_instance->d->logFile.write(QDateTime::currentDateTime().toString(Qt::RFC2822Date).toUtf8()); s_instance->d->logFile.write(": "); write(message); } void KisUsageLogger::write(const QString &message) { if (!s_instance->d->active) return; if (!s_instance->d->logFile.isOpen()) return; s_instance->d->logFile.write(message.toUtf8()); s_instance->d->logFile.write("\n"); s_instance->d->logFile.flush(); } void KisUsageLogger::writeSectionHeader() { s_instance->d->logFile.write(s_sectionHeader.toUtf8()); } void KisUsageLogger::writeHeader() { Q_ASSERT(s_instance->d->logFile.isOpen()); QString sessionHeader = QString("SESSION: %1. Executing %2\n\n") .arg(QDateTime::currentDateTime().toString(Qt::RFC2822Date)) .arg(qApp->arguments().join(' ')); QString disclaimer = i18n("WARNING: This file contains information about your system and the\n" "images you have been working with.\n" "\n" "If you have problems with Krita, the Krita developers might ask\n" "you to share this file with them. The information in this file is\n" "not shared automatically with the Krita developers in any way. You\n" "can disable logging to this file in Krita's Configure Krita Dialog.\n" "\n" "Please review the contents of this file before sharing this file with\n" "anyone.\n\n"); QString systemInfo; // NOTE: This is intentionally not translated! // Krita version info systemInfo.append("Krita\n"); systemInfo.append("\n Version: ").append(KritaVersionWrapper::versionString(true)); systemInfo.append("\n Languages: ").append(KLocalizedString::languages().join(", ")); systemInfo.append("\n Hidpi: ").append(QCoreApplication::testAttribute(Qt::AA_EnableHighDpiScaling) ? "true" : "false"); systemInfo.append("\n\n"); systemInfo.append("Qt\n"); systemInfo.append("\n Version (compiled): ").append(QT_VERSION_STR); systemInfo.append("\n Version (loaded): ").append(qVersion()); systemInfo.append("\n\n"); // OS information systemInfo.append("OS Information\n"); systemInfo.append("\n Build ABI: ").append(QSysInfo::buildAbi()); systemInfo.append("\n Build CPU: ").append(QSysInfo::buildCpuArchitecture()); systemInfo.append("\n CPU: ").append(QSysInfo::currentCpuArchitecture()); systemInfo.append("\n Kernel Type: ").append(QSysInfo::kernelType()); systemInfo.append("\n Kernel Version: ").append(QSysInfo::kernelVersion()); systemInfo.append("\n Pretty Productname: ").append(QSysInfo::prettyProductName()); systemInfo.append("\n Product Type: ").append(QSysInfo::productType()); systemInfo.append("\n Product Version: ").append(QSysInfo::productVersion()); systemInfo.append("\n\n"); writeSectionHeader(); s_instance->d->logFile.write(sessionHeader.toUtf8()); s_instance->d->logFile.write(disclaimer.toUtf8()); s_instance->d->logFile.write(systemInfo.toUtf8()); } void KisUsageLogger::rotateLog() { if (d->logFile.exists()) { - d->logFile.open(QFile::ReadOnly); - QString log = QString::fromUtf8(d->logFile.readAll()); - int sectionCount = log.count(s_sectionHeader); - int nextSectionIndex = log.indexOf(s_sectionHeader, s_sectionHeader.length()); - while(sectionCount >= s_maxLogs) { - log = log.remove(0, log.indexOf(s_sectionHeader, nextSectionIndex)); - nextSectionIndex = log.indexOf(s_sectionHeader, s_sectionHeader.length()); - sectionCount = log.count(s_sectionHeader); + { + // Check for CLOSING SESSION + d->logFile.open(QFile::ReadOnly); + QString log = QString::fromUtf8(d->logFile.readAll()); + if (!log.split("\n").last().contains("CLOSING SESSION")) { + log.append("\nKRITA DID NOT CLOSE CORRECTLY\n"); + QString crashLog = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QStringLiteral("/kritacrash.log"); + if (QFileInfo(crashLog).exists()) { + QFile f(crashLog); + f.open(QFile::ReadOnly); + QString crashes = QString::fromUtf8(f.readAll()); + f.close(); + + QStringList crashlist = crashes.split("-------------------"); + log.append(QString("\nThere were %1 crashes in total in the crash log.\n").arg(crashlist.size())); + + if (crashes.size() > 0) { + log.append(crashlist.last()); + } + } + d->logFile.close(); + d->logFile.open(QFile::WriteOnly); + d->logFile.write(log.toUtf8()); + } + d->logFile.flush(); + d->logFile.close(); } - d->logFile.close(); - d->logFile.open(QFile::WriteOnly); - d->logFile.write(log.toUtf8()); - d->logFile.close(); + + { + // Rotate + d->logFile.open(QFile::ReadOnly); + QString log = QString::fromUtf8(d->logFile.readAll()); + int sectionCount = log.count(s_sectionHeader); + int nextSectionIndex = log.indexOf(s_sectionHeader, s_sectionHeader.length()); + while(sectionCount >= s_maxLogs) { + log = log.remove(0, log.indexOf(s_sectionHeader, nextSectionIndex)); + nextSectionIndex = log.indexOf(s_sectionHeader, s_sectionHeader.length()); + sectionCount = log.count(s_sectionHeader); + } + d->logFile.close(); + d->logFile.open(QFile::WriteOnly); + d->logFile.write(log.toUtf8()); + d->logFile.flush(); + d->logFile.close(); + } + + } } diff --git a/libs/global/kis_global.h b/libs/global/kis_global.h index 6316973033..a2ca24bbc8 100644 --- a/libs/global/kis_global.h +++ b/libs/global/kis_global.h @@ -1,265 +1,269 @@ /* * 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, 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 }; 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 #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); +template +typename std::enable_if::value, T>::type +normalizeAngle(T a) { + if (a < T(0.0)) { + a = T(2 * M_PI) + std::fmod(a, T(2 * M_PI)); } - return a >= 2 * M_PI ? fmod(a, 2 * M_PI) : a; + return a >= T(2 * M_PI) ? std::fmod(a, T(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); +template +typename std::enable_if::value, T>::type +normalizeAngleDegrees(T a) { + if (a < T(0.0)) { + a = T(360.0) + std::fmod(a, T(360.0)); } - return a >= 360.0 ? fmod(a, 360.0) : a; + return a >= T(360.0) ? std::fmod(a, T(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/CMakeLists.txt b/libs/image/CMakeLists.txt index 12449658eb..a28bbf86ca 100644 --- a/libs/image/CMakeLists.txt +++ b/libs/image/CMakeLists.txt @@ -1,379 +1,380 @@ add_subdirectory( tests ) add_subdirectory( tiles3 ) include_directories( ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty ${CMAKE_CURRENT_SOURCE_DIR}/brushengine ${CMAKE_CURRENT_SOURCE_DIR}/commands ${CMAKE_CURRENT_SOURCE_DIR}/commands_new ${CMAKE_CURRENT_SOURCE_DIR}/filter ${CMAKE_CURRENT_SOURCE_DIR}/floodfill ${CMAKE_CURRENT_SOURCE_DIR}/generator ${CMAKE_CURRENT_SOURCE_DIR}/layerstyles ${CMAKE_CURRENT_SOURCE_DIR}/processing ${CMAKE_SOURCE_DIR}/sdk/tests ) include_directories(SYSTEM ${EIGEN3_INCLUDE_DIR} ) if(FFTW3_FOUND) include_directories(${FFTW3_INCLUDE_DIR}) endif() if(HAVE_VC) include_directories(SYSTEM ${Vc_INCLUDE_DIR} ${Qt5Core_INCLUDE_DIRS} ${Qt5Gui_INCLUDE_DIRS}) ko_compile_for_all_implementations(__per_arch_circle_mask_generator_objs kis_brush_mask_applicator_factories.cpp) else() set(__per_arch_circle_mask_generator_objs kis_brush_mask_applicator_factories.cpp) endif() set(kritaimage_LIB_SRCS tiles3/kis_tile.cc tiles3/kis_tile_data.cc tiles3/kis_tile_data_store.cc tiles3/kis_tile_data_pooler.cc tiles3/kis_tiled_data_manager.cc tiles3/KisTiledExtentManager.cpp tiles3/kis_memento_manager.cc tiles3/kis_hline_iterator.cpp tiles3/kis_vline_iterator.cpp tiles3/kis_random_accessor.cc tiles3/swap/kis_abstract_compression.cpp tiles3/swap/kis_lzf_compression.cpp tiles3/swap/kis_abstract_tile_compressor.cpp tiles3/swap/kis_legacy_tile_compressor.cpp tiles3/swap/kis_tile_compressor_2.cpp tiles3/swap/kis_chunk_allocator.cpp tiles3/swap/kis_memory_window.cpp tiles3/swap/kis_swapped_data_store.cpp tiles3/swap/kis_tile_data_swapper.cpp kis_distance_information.cpp kis_painter.cc kis_painter_blt_multi_fixed.cpp kis_marker_painter.cpp KisPrecisePaintDeviceWrapper.cpp kis_progress_updater.cpp brushengine/kis_paint_information.cc brushengine/kis_random_source.cpp brushengine/KisPerStrokeRandomSource.cpp brushengine/kis_stroke_random_source.cpp brushengine/kis_paintop.cc brushengine/kis_paintop_factory.cpp brushengine/kis_paintop_preset.cpp brushengine/kis_paintop_registry.cc brushengine/kis_paintop_settings.cpp brushengine/kis_paintop_settings_update_proxy.cpp brushengine/kis_paintop_utils.cpp brushengine/kis_no_size_paintop_settings.cpp brushengine/kis_locked_properties.cc brushengine/kis_locked_properties_proxy.cpp brushengine/kis_locked_properties_server.cpp brushengine/kis_paintop_config_widget.cpp brushengine/kis_uniform_paintop_property.cpp brushengine/kis_combo_based_paintop_property.cpp brushengine/kis_slider_based_paintop_property.cpp brushengine/kis_standard_uniform_properties_factory.cpp brushengine/KisStrokeSpeedMeasurer.cpp brushengine/KisPaintopSettingsIds.cpp commands/kis_deselect_global_selection_command.cpp commands/KisDeselectActiveSelectionCommand.cpp commands/kis_image_change_layers_command.cpp commands/kis_image_change_visibility_command.cpp commands/kis_image_command.cpp commands/kis_image_set_projection_color_space_command.cpp commands/kis_image_layer_add_command.cpp commands/kis_image_layer_move_command.cpp commands/kis_image_layer_remove_command.cpp commands/kis_image_layer_remove_command_impl.cpp commands/kis_image_lock_command.cpp commands/kis_node_command.cpp commands/kis_node_compositeop_command.cpp commands/kis_node_opacity_command.cpp commands/kis_node_property_list_command.cpp commands/kis_reselect_global_selection_command.cpp commands/KisReselectActiveSelectionCommand.cpp commands/kis_set_global_selection_command.cpp commands/KisNodeRenameCommand.cpp commands_new/kis_saved_commands.cpp commands_new/kis_processing_command.cpp commands_new/kis_image_resize_command.cpp commands_new/kis_image_set_resolution_command.cpp commands_new/kis_node_move_command2.cpp commands_new/kis_set_layer_style_command.cpp commands_new/kis_selection_move_command2.cpp commands_new/kis_update_command.cpp commands_new/kis_switch_current_time_command.cpp commands_new/kis_change_projection_color_command.cpp commands_new/kis_activate_selection_mask_command.cpp commands_new/kis_transaction_based_command.cpp commands_new/KisHoldUIUpdatesCommand.cpp processing/kis_do_nothing_processing_visitor.cpp processing/kis_simple_processing_visitor.cpp processing/kis_crop_processing_visitor.cpp processing/kis_crop_selections_processing_visitor.cpp processing/kis_transform_processing_visitor.cpp processing/kis_mirror_processing_visitor.cpp processing/KisSelectionBasedProcessingHelper.cpp filter/kis_filter.cc filter/kis_filter_category_ids.cpp filter/kis_filter_configuration.cc filter/kis_color_transformation_configuration.cc filter/kis_filter_registry.cc filter/kis_color_transformation_filter.cc generator/kis_generator.cpp generator/kis_generator_layer.cpp generator/kis_generator_registry.cpp floodfill/kis_fill_interval_map.cpp floodfill/kis_scanline_fill.cpp lazybrush/kis_min_cut_worker.cpp lazybrush/kis_lazy_fill_tools.cpp lazybrush/kis_multiway_cut.cpp lazybrush/KisWatershedWorker.cpp lazybrush/kis_colorize_mask.cpp lazybrush/kis_colorize_stroke_strategy.cpp KisDelayedUpdateNodeInterface.cpp KisCroppedOriginalLayerInterface.cpp kis_adjustment_layer.cc kis_selection_based_layer.cpp kis_node_filter_interface.cpp kis_base_accessor.cpp kis_base_node.cpp kis_base_processor.cpp kis_bookmarked_configuration_manager.cc kis_node_uuid_info.cpp kis_clone_layer.cpp kis_colorspace_convert_visitor.cpp kis_config_widget.cpp kis_convolution_kernel.cc kis_convolution_painter.cc kis_gaussian_kernel.cpp kis_edge_detection_kernel.cpp kis_cubic_curve.cpp kis_default_bounds.cpp kis_default_bounds_base.cpp kis_effect_mask.cc kis_fast_math.cpp kis_fill_painter.cc kis_filter_mask.cpp kis_filter_strategy.cc kis_transform_mask.cpp kis_transform_mask_params_interface.cpp kis_recalculate_transform_mask_job.cpp kis_recalculate_generator_layer_job.cpp kis_transform_mask_params_factory_registry.cpp kis_safe_transform.cpp kis_gradient_painter.cc kis_gradient_shape_strategy.cpp kis_cached_gradient_shape_strategy.cpp kis_polygonal_gradient_shape_strategy.cpp kis_iterator_ng.cpp kis_async_merger.cpp kis_merge_walker.cc kis_updater_context.cpp kis_update_job_item.cpp kis_stroke_strategy_undo_command_based.cpp kis_simple_stroke_strategy.cpp KisRunnableBasedStrokeStrategy.cpp KisRunnableStrokeJobDataBase.cpp KisRunnableStrokeJobData.cpp KisRunnableStrokeJobsInterface.cpp KisFakeRunnableStrokeJobsExecutor.cpp kis_stroke_job_strategy.cpp kis_stroke_strategy.cpp kis_stroke.cpp kis_strokes_queue.cpp KisStrokesQueueMutatedJobInterface.cpp kis_simple_update_queue.cpp kis_update_scheduler.cpp kis_queues_progress_updater.cpp kis_composite_progress_proxy.cpp kis_sync_lod_cache_stroke_strategy.cpp kis_lod_capable_layer_offset.cpp kis_update_time_monitor.cpp KisImageConfigNotifier.cpp kis_group_layer.cc kis_count_visitor.cpp kis_histogram.cc kis_image_interfaces.cpp kis_image_animation_interface.cpp kis_time_range.cpp kis_node_graph_listener.cpp kis_image.cc kis_image_signal_router.cpp KisImageSignals.cpp kis_image_config.cpp kis_projection_updates_filter.cpp kis_suspend_projection_updates_stroke_strategy.cpp kis_regenerate_frame_stroke_strategy.cpp kis_switch_time_stroke_strategy.cpp kis_crop_saved_extra_data.cpp kis_timed_signal_threshold.cpp kis_layer.cc kis_indirect_painting_support.cpp kis_abstract_projection_plane.cpp kis_layer_projection_plane.cpp kis_layer_utils.cpp kis_mask_projection_plane.cpp kis_projection_leaf.cpp KisSafeNodeProjectionStore.cpp kis_mask.cc kis_base_mask_generator.cpp kis_rect_mask_generator.cpp kis_circle_mask_generator.cpp kis_gauss_circle_mask_generator.cpp kis_gauss_rect_mask_generator.cpp ${__per_arch_circle_mask_generator_objs} kis_curve_circle_mask_generator.cpp kis_curve_rect_mask_generator.cpp kis_math_toolbox.cpp kis_memory_statistics_server.cpp kis_name_server.cpp kis_node.cpp kis_node_facade.cpp kis_node_progress_proxy.cpp kis_busy_progress_indicator.cpp kis_node_visitor.cpp kis_paint_device.cc kis_paint_device_debug_utils.cpp kis_fixed_paint_device.cpp KisOptimizedByteArray.cpp kis_paint_layer.cc kis_perspective_math.cpp kis_pixel_selection.cpp kis_processing_information.cpp kis_properties_configuration.cc kis_random_accessor_ng.cpp kis_random_generator.cc kis_random_sub_accessor.cpp kis_wrapped_random_accessor.cpp kis_selection.cc KisSelectionUpdateCompressor.cpp kis_selection_mask.cpp kis_update_outline_job.cpp kis_update_selection_job.cpp kis_serializable_configuration.cc kis_transaction_data.cpp kis_transform_worker.cc kis_perspectivetransform_worker.cpp bsplines/kis_bspline_1d.cpp bsplines/kis_bspline_2d.cpp bsplines/kis_nu_bspline_2d.cpp kis_warptransform_worker.cc kis_cage_transform_worker.cpp kis_liquify_transform_worker.cpp kis_green_coordinates_math.cpp kis_transparency_mask.cc kis_undo_adapter.cpp kis_macro_based_undo_store.cpp kis_surrogate_undo_adapter.cpp kis_legacy_undo_adapter.cpp kis_post_execution_undo_adapter.cpp kis_processing_visitor.cpp kis_processing_applicator.cpp krita_utils.cpp kis_outline_generator.cpp kis_layer_composition.cpp kis_selection_filters.cpp KisProofingConfiguration.h KisRecycleProjectionsJob.cpp kis_keyframe.cpp kis_keyframe_channel.cpp kis_keyframe_commands.cpp kis_scalar_keyframe_channel.cpp kis_raster_keyframe_channel.cpp kis_onion_skin_compositor.cpp kis_onion_skin_cache.cpp kis_idle_watcher.cpp kis_psd_layer_style.cpp kis_layer_properties_icons.cpp layerstyles/kis_multiple_projection.cpp layerstyles/kis_layer_style_filter.cpp layerstyles/kis_layer_style_filter_environment.cpp layerstyles/kis_layer_style_filter_projection_plane.cpp layerstyles/kis_layer_style_projection_plane.cpp layerstyles/kis_ls_drop_shadow_filter.cpp layerstyles/kis_ls_satin_filter.cpp layerstyles/kis_ls_stroke_filter.cpp layerstyles/kis_ls_bevel_emboss_filter.cpp layerstyles/kis_ls_overlay_filter.cpp layerstyles/kis_ls_utils.cpp layerstyles/gimp_bump_map.cpp + layerstyles/KisLayerStyleKnockoutBlower.cpp KisProofingConfiguration.cpp kis_node_query_path.cc ) set(einspline_SRCS 3rdparty/einspline/bspline_create.cpp 3rdparty/einspline/bspline_data.cpp 3rdparty/einspline/multi_bspline_create.cpp 3rdparty/einspline/nubasis.cpp 3rdparty/einspline/nubspline_create.cpp 3rdparty/einspline/nugrid.cpp ) add_library(kritaimage SHARED ${kritaimage_LIB_SRCS} ${einspline_SRCS}) generate_export_header(kritaimage BASE_NAME kritaimage) target_link_libraries(kritaimage PUBLIC kritaversion kritawidgets kritaglobal kritapsd kritaodf kritapigment kritacommand kritawidgetutils kritametadata Qt5::Concurrent ) target_link_libraries(kritaimage PUBLIC ${Boost_SYSTEM_LIBRARY}) if(NOT HAVE_CXX_ATOMICS_WITHOUT_LIB) if(NOT HAVE_CXX_ATOMICS64_WITHOUT_LIB) target_link_libraries(kritaimage PUBLIC atomic) endif() endif() if(OPENEXR_FOUND) target_link_libraries(kritaimage PUBLIC ${OPENEXR_LIBRARIES}) endif() if(FFTW3_FOUND) target_link_libraries(kritaimage PRIVATE ${FFTW3_LIBRARIES}) endif() if(HAVE_VC) target_link_libraries(kritaimage PUBLIC ${Vc_LIBRARIES}) endif() if (NOT GSL_FOUND) message (WARNING "KRITA WARNING! No GNU Scientific Library was found! Krita's Shaped Gradients might be non-normalized! Please install GSL library.") else () target_link_libraries(kritaimage PRIVATE ${GSL_LIBRARIES} ${GSL_CBLAS_LIBRARIES}) endif () target_include_directories(kritaimage PUBLIC $ $ $ $ $ ) set_target_properties(kritaimage PROPERTIES VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION} ) install(TARGETS kritaimage ${INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/libs/image/commands/kis_change_filter_command.h b/libs/image/commands/kis_change_filter_command.h index 847b3b799d..0222792c8f 100644 --- a/libs/image/commands/kis_change_filter_command.h +++ b/libs/image/commands/kis_change_filter_command.h @@ -1,96 +1,96 @@ /* * Copyright (c) 2008 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_CHANGE_FILTER_COMMAND_H #define KIS_CHANGE_FILTER_COMMAND_H #include #include #include "kis_types.h" #include #include "filter/kis_filter_configuration.h" #include "kis_node.h" #include "kis_node_filter_interface.h" #include "filter/kis_filter_registry.h" #include "filter/kis_filter.h" #include "generator/kis_generator_registry.h" #include "generator/kis_generator.h" class KisChangeFilterCmd : public KUndo2Command { public: KisChangeFilterCmd(KisNodeSP node, const QString &filterNameBefore, const QString &xmlBefore, const QString &filterNameAfter, const QString &xmlAfter, bool useGeneratorRegistry) : KUndo2Command(kundo2_i18n("Change Filter")) { m_node = node; m_filterInterface = dynamic_cast(node.data()); Q_ASSERT(m_filterInterface); m_useGeneratorRegistry = useGeneratorRegistry; m_xmlBefore = xmlBefore; m_xmlAfter = xmlAfter; m_filterNameBefore = filterNameBefore; m_filterNameAfter = filterNameAfter; } public: void redo() override { m_filterInterface->setFilter(createConfiguration(m_filterNameAfter, m_xmlAfter)); m_node->setDirty(); } void undo() override { m_filterInterface->setFilter(createConfiguration(m_filterNameBefore, m_xmlBefore)); m_node->setDirty(); } private: KisFilterConfigurationSP createConfiguration(const QString &name, const QString &data) { KisFilterConfigurationSP config; if (m_useGeneratorRegistry) { KisGeneratorSP generator = KisGeneratorRegistry::instance()->value(name); - config = generator->defaultConfiguration(); + config = generator->factoryConfiguration(); } else { KisFilterSP filter = KisFilterRegistry::instance()->value(name); - config = filter->defaultConfiguration(); + config = filter->factoryConfiguration(); } config->fromXML(data); return config; } private: KisNodeSP m_node; KisNodeFilterInterface *m_filterInterface; bool m_useGeneratorRegistry; QString m_xmlBefore; QString m_xmlAfter; QString m_filterNameBefore; QString m_filterNameAfter; }; #endif diff --git a/libs/image/filter/kis_filter_registry.cc b/libs/image/filter/kis_filter_registry.cc index 01bbddb4cf..96c98f3562 100644 --- a/libs/image/filter/kis_filter_registry.cc +++ b/libs/image/filter/kis_filter_registry.cc @@ -1,82 +1,82 @@ /* * Copyright (c) 2003 Patrick Julien * Copyright (c) 2004 Cyrille Berger * Copyright (c) 2004-2008 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "filter/kis_filter_registry.h" #include #include #include #include #include #include "kis_debug.h" #include "kis_types.h" #include "kis_paint_device.h" #include "filter/kis_filter.h" #include "kis_filter_configuration.h" KisFilterRegistry::KisFilterRegistry(QObject *parent) : QObject(parent) { } KisFilterRegistry::~KisFilterRegistry() { dbgRegistry << "deleting KisFilterRegistry"; Q_FOREACH (KisFilterSP filter, values()) { remove(filter->id()); filter.clear(); } } KisFilterRegistry* KisFilterRegistry::instance() { KisFilterRegistry *reg = qApp->findChild(QString()); if (!reg) { dbgRegistry << "initializing KisFilterRegistry"; reg = new KisFilterRegistry(qApp); KoPluginLoader::instance()->load("Krita/Filter", "Type == 'Service' and ([X-Krita-Version] == 28)"); } return reg; } void KisFilterRegistry::add(KisFilterSP item) { add(item->id(), item); } void KisFilterRegistry::add(const QString &id, KisFilterSP item) { KoGenericRegistry::add(id, item); emit(filterAdded(id)); } KisFilterConfigurationSP KisFilterRegistry::cloneConfiguration(const KisFilterConfigurationSP kfc) { Q_ASSERT(kfc); KisFilterSP filter = value(kfc->name()); - KisFilterConfigurationSP newkfc(filter->defaultConfiguration()); + KisFilterConfigurationSP newkfc(filter->factoryConfiguration()); newkfc->fromXML(kfc->toXML()); return newkfc; } diff --git a/libs/image/generator/kis_generator_registry.cpp b/libs/image/generator/kis_generator_registry.cpp index 9a14e03798..dc48bf8b71 100644 --- a/libs/image/generator/kis_generator_registry.cpp +++ b/libs/image/generator/kis_generator_registry.cpp @@ -1,81 +1,81 @@ /* * Copyright (c) 2008 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "generator/kis_generator_registry.h" #include #include #include #include #include #include "filter/kis_filter_configuration.h" #include "kis_debug.h" #include "kis_types.h" #include "kis_paint_device.h" #include "generator/kis_generator.h" KisGeneratorRegistry::KisGeneratorRegistry(QObject *parent) : QObject(parent) { } KisGeneratorRegistry::~KisGeneratorRegistry() { Q_FOREACH (KisGeneratorSP generator, values()) { remove(generator->id()); generator.clear(); } dbgRegistry << "deleting KisGeneratorRegistry"; } KisGeneratorRegistry* KisGeneratorRegistry::instance() { KisGeneratorRegistry *reg = qApp->findChild(QString()); if (!reg) { dbgRegistry << "initializing KisGeneratorRegistry"; reg = new KisGeneratorRegistry(qApp); KoPluginLoader::instance()->load("Krita/Generator", "Type == 'Service' and ([X-Krita-Version] == 28)"); } return reg; } void KisGeneratorRegistry::add(KisGeneratorSP item) { dbgPlugins << "adding " << item->name(); add(item->id(), item); } void KisGeneratorRegistry::add(const QString &id, KisGeneratorSP item) { dbgPlugins << "adding " << item->name() << " with id " << id; KoGenericRegistry::add(id, item); emit(generatorAdded(id)); } KisFilterConfigurationSP KisGeneratorRegistry::cloneConfiguration(const KisFilterConfigurationSP kfc) { KisGeneratorSP filter = value(kfc->name()); - KisFilterConfigurationSP newkfc(filter->defaultConfiguration()); + KisFilterConfigurationSP newkfc(filter->factoryConfiguration()); newkfc->fromXML(kfc->toXML()); return newkfc; } diff --git a/libs/image/kis_base_processor.cpp b/libs/image/kis_base_processor.cpp index af11c9b738..02d1514e41 100644 --- a/libs/image/kis_base_processor.cpp +++ b/libs/image/kis_base_processor.cpp @@ -1,191 +1,191 @@ /* * Copyright (c) 2004,2006-2007 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_base_processor.h" #include #include "kis_bookmarked_configuration_manager.h" #include "filter/kis_filter_configuration.h" #include "kis_paint_device.h" #include "kis_selection.h" class KisBaseProcessorConfigurationFactory : public KisSerializableConfigurationFactory { public: KisBaseProcessorConfigurationFactory(KisBaseProcessor* _generator) : m_generator(_generator) {} ~KisBaseProcessorConfigurationFactory() override {} KisSerializableConfigurationSP createDefault() override { - return m_generator->factoryConfiguration(); + return m_generator->defaultConfiguration(); } KisSerializableConfigurationSP create(const QDomElement& e) override { KisSerializableConfigurationSP config = m_generator->factoryConfiguration(); config->fromXML(e); return config; } private: KisBaseProcessor* m_generator; }; struct Q_DECL_HIDDEN KisBaseProcessor::Private { Private() : bookmarkManager(0) , supportsPainting(false) , supportsAdjustmentLayers(true) , supportsThreading(true) , showConfigurationWidget(true) , colorSpaceIndependence(FULLY_INDEPENDENT) { } KisBookmarkedConfigurationManager* bookmarkManager; KoID id; KoID category; // The category in the filter menu this filter fits QString entry; // the i18n'ed accelerated menu text QKeySequence shortcut; bool supportsPainting; bool supportsAdjustmentLayers; bool supportsThreading; bool showConfigurationWidget; ColorSpaceIndependence colorSpaceIndependence; }; KisBaseProcessor::KisBaseProcessor(const KoID& id, const KoID & category, const QString & entry) : d(new Private) { d->id = id; d->category = category; d->entry = entry; } void KisBaseProcessor::init(const QString& configEntryGroup) { d->bookmarkManager = new KisBookmarkedConfigurationManager(configEntryGroup, new KisBaseProcessorConfigurationFactory(this)); } KisBaseProcessor::~KisBaseProcessor() { delete d->bookmarkManager; delete d; } KisFilterConfigurationSP KisBaseProcessor::factoryConfiguration() const { - return new KisFilterConfiguration(id(), 0); + return new KisFilterConfiguration(id(), 1); } KisFilterConfigurationSP KisBaseProcessor::defaultConfiguration() const { return factoryConfiguration(); } KisConfigWidget * KisBaseProcessor::createConfigurationWidget(QWidget *, const KisPaintDeviceSP, bool) const { return 0; } KisBookmarkedConfigurationManager* KisBaseProcessor::bookmarkManager() { return d->bookmarkManager; } const KisBookmarkedConfigurationManager* KisBaseProcessor::bookmarkManager() const { return d->bookmarkManager; } QString KisBaseProcessor::id() const { return d->id.id(); } QString KisBaseProcessor::name() const { return d->id.name(); } KoID KisBaseProcessor::menuCategory() const { return d->category; } QString KisBaseProcessor::menuEntry() const { return d->entry; } QKeySequence KisBaseProcessor::shortcut() const { return d->shortcut; } void KisBaseProcessor::setShortcut(const QKeySequence & shortcut) { d->shortcut = shortcut; } bool KisBaseProcessor::supportsPainting() const { return d->supportsPainting; } bool KisBaseProcessor::supportsAdjustmentLayers() const { return d->supportsAdjustmentLayers; } bool KisBaseProcessor::supportsThreading() const { return d->supportsThreading; } ColorSpaceIndependence KisBaseProcessor::colorSpaceIndependence() const { return d->colorSpaceIndependence; } void KisBaseProcessor::setSupportsPainting(bool v) { d->supportsPainting = v; } void KisBaseProcessor::setSupportsAdjustmentLayers(bool v) { d->supportsAdjustmentLayers = v; } void KisBaseProcessor::setSupportsThreading(bool v) { d->supportsThreading = v; } void KisBaseProcessor::setColorSpaceIndependence(ColorSpaceIndependence v) { d->colorSpaceIndependence = v; } bool KisBaseProcessor::showConfigurationWidget() { return d->showConfigurationWidget; } void KisBaseProcessor::setShowConfigurationWidget(bool v) { d->showConfigurationWidget = v; } diff --git a/libs/image/kis_base_processor.h b/libs/image/kis_base_processor.h index d391cd0c98..e2062ecc21 100644 --- a/libs/image/kis_base_processor.h +++ b/libs/image/kis_base_processor.h @@ -1,169 +1,170 @@ /* * Copyright (c) 2008 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _KIS_BASE_PROCESSOR_H_ #define _KIS_BASE_PROCESSOR_H_ #include #include #include #include #include "KoID.h" #include "KoColorSpace.h" #include "kis_types.h" #include "kis_shared.h" #include "kis_image.h" #include "kritaimage_export.h" class QWidget; class KisBookmarkedConfigurationManager; class KisFilterConfiguration; class KisConfigWidget; /** * Base class for classes that process areas of pixels. * Processors can either read in pixels and write out pixels * or just write out pixels, using a certain set of configuration * pixels. * * in-out processing is typically filtering: @see KisFilter. * out-only processing is typically generating: @see KisGenerator. * * Information about the area that needs to be processed is contained * @see KisProcessingInformation and @see KisConstProcessingInformation. */ class KRITAIMAGE_EXPORT KisBaseProcessor : public KisShared { friend class KisBaseProcessorConfigurationFactory; public: KisBaseProcessor(const KoID& id, const KoID & category, const QString & entry); virtual ~KisBaseProcessor(); /** * Return the configuration set as the default by the user or the default * configuration from the filter writer as returned by factoryConfiguration. * * This configuration is used by default for the configuration widget and * given to the process function if there is no configuration widget. * * @return the default configuration of this widget */ - KisFilterConfigurationSP defaultConfiguration() const; + virtual KisFilterConfigurationSP defaultConfiguration() const; /** * @return the bookmark manager for this processor */ KisBookmarkedConfigurationManager* bookmarkManager(); /** * @return the bookmark manager for this processor */ const KisBookmarkedConfigurationManager* bookmarkManager() const; /// @return Unique identification for this processor QString id() const; /// @return User-visible identification for this processor QString name() const; /// @return the submenu in the filters menu does processor want to go? KoID menuCategory() const; /// @return the i18n'ed string this filter wants to show itself in the menu QString menuEntry() const; /** * Return the default keyboard shortcut for activation of this filter * * @return the shortcut */ QKeySequence shortcut() const; /** * Create the configuration widget for this processor. * * @param parent the Qt owner widget of this widget * @param dev the paintdevice this filter will act on * @param useForMasks shown if the filer is going to be used in a mask. Some filters * may provide limited options when applied as a mask (e.g. Gaussian Blur) */ virtual KisConfigWidget * createConfigurationWidget(QWidget * parent, const KisPaintDeviceSP dev, bool useForMasks) const; // "Support" functions public: /** * If true, this filter can be used in painting tools as a paint operation */ bool supportsPainting() const; /// This filter can be used in adjustment layers bool supportsAdjustmentLayers() const; /** * This filter supports cutting up the work area and filtering * each chunk in a separate thread. Filters that need access to the * whole area for correct computations should return false. */ bool supportsThreading() const; /// If true, the filter wants to show a configuration widget bool showConfigurationWidget(); /** * Determine the colorspace independence of this filter. * @see ColorSpaceIndependence * * @return the degree of independence */ ColorSpaceIndependence colorSpaceIndependence() const; - /// @return the default configuration as defined by whoever wrote the plugin + /// @return the default configuration object as defined by whoever wrote the plugin. + /// This object must be filled in with fromXML after that. virtual KisFilterConfigurationSP factoryConfiguration() const; protected: void setSupportsPainting(bool v); void setSupportsAdjustmentLayers(bool v); void setSupportsThreading(bool v); void setColorSpaceIndependence(ColorSpaceIndependence v); void setShowConfigurationWidget(bool v); /** * Set the default shortcut for activation of this filter. */ void setShortcut(const QKeySequence & shortcut); protected: void init(const QString& configEntryGroup); private: struct Private; Private* const d; }; #endif diff --git a/libs/image/kis_cached_paint_device.h b/libs/image/kis_cached_paint_device.h index 1cb038d39b..f83eb34480 100644 --- a/libs/image/kis_cached_paint_device.h +++ b/libs/image/kis_cached_paint_device.h @@ -1,73 +1,114 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_CACHED_PAINT_DEVICE_H #define __KIS_CACHED_PAINT_DEVICE_H #include "tiles3/kis_lockless_stack.h" #include "kis_paint_device.h" #include "kis_selection.h" class KisCachedPaintDevice { public: KisPaintDeviceSP getDevice(KisPaintDeviceSP prototype) { KisPaintDeviceSP device; if(!m_stack.pop(device)) { device = new KisPaintDevice(prototype->colorSpace()); } device->prepareClone(prototype); return device; } void putDevice(KisPaintDeviceSP device) { device->clear(); device->setDefaultBounds(new KisDefaultBounds()); m_stack.push(device); } + struct Guard { + Guard(KisPaintDeviceSP prototype, KisCachedPaintDevice &parent) + : m_parent(parent) + { + m_device = m_parent.getDevice(prototype); + } + + ~Guard() { + m_parent.putDevice(m_device); + } + + KisPaintDeviceSP device() const { + return m_device; + } + + private: + KisCachedPaintDevice &m_parent; + KisPaintDeviceSP m_device; + }; + private: KisLocklessStack m_stack; }; class KisCachedSelection { public: KisSelectionSP getSelection() { KisSelectionSP selection; if(!m_stack.pop(selection)) { - selection = new KisSelection(); + selection = new KisSelection(new KisSelectionEmptyBounds(0)); } return selection; } void putSelection(KisSelectionSP selection) { selection->clear(); - selection->setDefaultBounds(new KisDefaultBounds()); + selection->setDefaultBounds(new KisSelectionEmptyBounds(0)); + selection->pixelSelection()->moveTo(QPoint()); m_stack.push(selection); } + struct Guard { + Guard(KisCachedSelection &parent) + : m_parent(parent) + { + m_selection = m_parent.getSelection(); + } + + ~Guard() { + m_parent.putSelection(m_selection); + } + + KisSelectionSP selection() const { + return m_selection; + } + + private: + KisCachedSelection &m_parent; + KisSelectionSP m_selection; + }; + private: KisLocklessStack m_stack; }; #endif /* __KIS_CACHED_PAINT_DEVICE_H */ diff --git a/libs/image/kis_convolution_painter.cc b/libs/image/kis_convolution_painter.cc index 7ec9e31ca4..384f59f55f 100644 --- a/libs/image/kis_convolution_painter.cc +++ b/libs/image/kis_convolution_painter.cc @@ -1,189 +1,189 @@ /* * 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. */ #include "kis_convolution_painter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_convolution_kernel.h" #include "kis_global.h" #include "kis_image.h" #include "kis_layer.h" #include "kis_paint_device.h" #include "kis_painter.h" #include "KoColorSpace.h" #include #include "kis_types.h" #include "kis_selection.h" #include "kis_convolution_worker.h" #include "kis_convolution_worker_spatial.h" #include "config_convolution.h" #ifdef HAVE_FFTW3 #include "kis_convolution_worker_fft.h" #endif -bool KisConvolutionPainter::useFFTImplemenation(const KisConvolutionKernelSP kernel) const +bool KisConvolutionPainter::useFFTImplementation(const KisConvolutionKernelSP kernel) const { bool result = false; #ifdef HAVE_FFTW3 #define THRESHOLD_SIZE 5 result = m_enginePreference == FFTW || (m_enginePreference == NONE && (kernel->width() > THRESHOLD_SIZE || kernel->height() > THRESHOLD_SIZE)); #else Q_UNUSED(kernel); #endif return result; } template KisConvolutionWorker* KisConvolutionPainter::createWorker(const KisConvolutionKernelSP kernel, KisPainter *painter, KoUpdater *progress) { KisConvolutionWorker *worker; #ifdef HAVE_FFTW3 - if (useFFTImplemenation(kernel)) { + if (useFFTImplementation(kernel)) { worker = new KisConvolutionWorkerFFT(painter, progress); } else { worker = new KisConvolutionWorkerSpatial(painter, progress); } #else Q_UNUSED(kernel); worker = new KisConvolutionWorkerSpatial(painter, progress); #endif return worker; } bool KisConvolutionPainter::supportsFFTW() { #ifdef HAVE_FFTW3 return true; #else return false; #endif } KisConvolutionPainter::KisConvolutionPainter() : KisPainter(), m_enginePreference(NONE) { } KisConvolutionPainter::KisConvolutionPainter(KisPaintDeviceSP device) : KisPainter(device), m_enginePreference(NONE) { } KisConvolutionPainter::KisConvolutionPainter(KisPaintDeviceSP device, KisSelectionSP selection) : KisPainter(device, selection), m_enginePreference(NONE) { } KisConvolutionPainter::KisConvolutionPainter(KisPaintDeviceSP device, TestingEnginePreference enginePreference) : KisPainter(device), m_enginePreference(enginePreference) { } void KisConvolutionPainter::applyMatrix(const KisConvolutionKernelSP kernel, const KisPaintDeviceSP src, QPoint srcPos, QPoint dstPos, QSize areaSize, KisConvolutionBorderOp borderOp) { /** * Force BORDER_IGNORE op for the wraparound mode, * because the paint device has its own special * iterators, which do everything for us. */ if (src->defaultBounds()->wrapAroundMode()) { borderOp = BORDER_IGNORE; } // Determine whether we convolve border pixels, or not. switch (borderOp) { case BORDER_REPEAT: { const QRect boundsRect = src->defaultBounds()->bounds(); const QRect requestedRect = QRect(srcPos, areaSize); QRect dataRect = requestedRect | boundsRect; KIS_SAFE_ASSERT_RECOVER(boundsRect != KisDefaultBounds().bounds()) { dataRect = requestedRect | src->exactBounds(); } /** * FIXME: Implementation can return empty destination device * on faults and has no way to report this. This will cause a crash * on sequential convolutions inside iteratiors. * * o implementation should do it's work or assert otherwise * (or report the issue somehow) * o check other cases of the switch for the vulnerability */ if(dataRect.isValid()) { KisConvolutionWorker *worker; worker = createWorker(kernel, this, progressUpdater()); worker->execute(kernel, src, srcPos, dstPos, areaSize, dataRect); delete worker; } break; } case BORDER_IGNORE: default: { KisConvolutionWorker *worker; worker = createWorker(kernel, this, progressUpdater()); worker->execute(kernel, src, srcPos, dstPos, areaSize, QRect()); delete worker; } } } bool KisConvolutionPainter::needsTransaction(const KisConvolutionKernelSP kernel) const { - return !useFFTImplemenation(kernel); + return !useFFTImplementation(kernel); } diff --git a/libs/image/kis_convolution_painter.h b/libs/image/kis_convolution_painter.h index 4f0a17910d..2db38414a7 100644 --- a/libs/image/kis_convolution_painter.h +++ b/libs/image/kis_convolution_painter.h @@ -1,104 +1,104 @@ /* * 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_CONVOLUTION_PAINTER_H_ #define KIS_CONVOLUTION_PAINTER_H_ #include "kis_types.h" #include "kis_painter.h" #include "kis_image.h" #include "kritaimage_export.h" template class KisConvolutionWorker; enum KisConvolutionBorderOp { BORDER_IGNORE = 0, // read the pixels outside of the application rect BORDER_REPEAT = 1 // Use the border for the missing pixels }; /** * @brief The KisConvolutionPainter class applies a convolution kernel to a paint device. * * * Note: https://bugs.kde.org/show_bug.cgi?id=220310 shows that there's something here * that we need to fix... */ class KRITAIMAGE_EXPORT KisConvolutionPainter : public KisPainter { public: KisConvolutionPainter(); KisConvolutionPainter(KisPaintDeviceSP device); KisConvolutionPainter(KisPaintDeviceSP device, KisSelectionSP selection); enum TestingEnginePreference { NONE, SPATIAL, FFTW }; KisConvolutionPainter(KisPaintDeviceSP device, TestingEnginePreference enginePreference); /** * Convolve all channels in src using the specified kernel; there is only one kernel for all * channels possible. By default the border pixels are not convolved, that is, convolving * starts with at (x + kernel.width/2, y + kernel.height/2) and stops at w - (kernel.width/2) * and h - (kernel.height/2) * * The border op decides what to do with pixels too close to the edge of the rect as defined above. * * The channels flag determines which set out of color channels, alpha channels. * channels we convolve. * * Note that we do not (currently) support different kernels for * different channels _or_ channel types. * * If you want to convolve a subset of the channels in a pixel, * set those channels with KisPainter::setChannelFlags(); */ void applyMatrix(const KisConvolutionKernelSP kernel, const KisPaintDeviceSP src, QPoint srcPos, QPoint dstPos, QSize areaSize, KisConvolutionBorderOp borderOp = BORDER_REPEAT); /** * The caller should ask if the painter needs an explicit transaction iff * the source and destination devices coincide. Otherwise, the transaction is * just not needed. */ bool needsTransaction(const KisConvolutionKernelSP kernel) const; static bool supportsFFTW(); protected: friend class KisConvolutionPainterTest; private: template KisConvolutionWorker* createWorker(const KisConvolutionKernelSP kernel, KisPainter *painter, KoUpdater *progress); - bool useFFTImplemenation(const KisConvolutionKernelSP kernel) const; + bool useFFTImplementation(const KisConvolutionKernelSP kernel) const; private: TestingEnginePreference m_enginePreference; }; #endif //KIS_CONVOLUTION_PAINTER_H_ diff --git a/libs/image/kis_cubic_curve.cpp b/libs/image/kis_cubic_curve.cpp index 1f12831310..7c55e3d1d9 100644 --- a/libs/image/kis_cubic_curve.cpp +++ b/libs/image/kis_cubic_curve.cpp @@ -1,519 +1,524 @@ /* * 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::isIdentity() const { const QList &points = d->data->points; + const int size = points.size(); - Q_FOREACH (const QPointF &pt, points) { - if (!qFuzzyCompare(pt.x(), pt.y())) { + if (points[0] != QPointF(0,0) || points[size-1] != QPointF(1,1)) { + return false; + } + + for (int i = 1; i < size-1; i++) { + if (!qFuzzyCompare(points[i].x(), points[i].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_default_bounds.cpp b/libs/image/kis_default_bounds.cpp index 98b849b050..bf2a3880aa 100644 --- a/libs/image/kis_default_bounds.cpp +++ b/libs/image/kis_default_bounds.cpp @@ -1,126 +1,149 @@ /* * Copyright (c) 2010 Boudewijn Rempt * Copyright (c) 2010 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_global.h" #include "kis_default_bounds.h" #include "kis_paint_device.h" #include "kis_image_animation_interface.h" #include "kis_image.h" const QRect KisDefaultBounds::infiniteRect = QRect(qint32_MIN/2, qint32_MIN/2, qint32_MAX, qint32_MAX); /******************************************************************/ /* KisDefaultBounds */ /******************************************************************/ struct Q_DECL_HIDDEN KisDefaultBounds::Private { KisImageWSP image; }; KisDefaultBounds::KisDefaultBounds(KisImageWSP image) : m_d(new Private()) { m_d->image = image; } KisDefaultBounds::~KisDefaultBounds() { delete m_d; } QRect KisDefaultBounds::bounds() const { /** * By default return infinite rect to cover everything */ return m_d->image ? m_d->image->effectiveLodBounds() : infiniteRect; } bool KisDefaultBounds::wrapAroundMode() const { return m_d->image ? m_d->image->wrapAroundModeActive() : false; } int KisDefaultBounds::currentLevelOfDetail() const { return m_d->image ? m_d->image->currentLevelOfDetail() : 0; } int KisDefaultBounds::currentTime() const { KisImageAnimationInterface *interface = m_d->image ? m_d->image->animationInterface() : 0; return interface ? interface->currentTime() : 0; } bool KisDefaultBounds::externalFrameActive() const { KisImageAnimationInterface *interface = m_d->image ? m_d->image->animationInterface() : 0; return interface ? interface->externalFrameActive() : false; } /******************************************************************/ /* KisSelectionDefaultBounds */ /******************************************************************/ struct Q_DECL_HIDDEN KisSelectionDefaultBounds::Private { KisPaintDeviceWSP parentDevice; }; -KisSelectionDefaultBounds::KisSelectionDefaultBounds(KisPaintDeviceSP parentDevice, KisImageWSP image) - : KisDefaultBounds(image), - m_d(new Private()) +KisSelectionDefaultBounds::KisSelectionDefaultBounds(KisPaintDeviceSP parentDevice) + : m_d(new Private()) { m_d->parentDevice = parentDevice; } KisSelectionDefaultBounds::~KisSelectionDefaultBounds() { delete m_d; } QRect KisSelectionDefaultBounds::bounds() const { - QRect additionalRect = m_d->parentDevice ? m_d->parentDevice->extent() : QRect(); - return additionalRect | KisDefaultBounds::bounds(); + return m_d->parentDevice ? + m_d->parentDevice->extent() | m_d->parentDevice->defaultBounds()->bounds() : QRect(); +} + +bool KisSelectionDefaultBounds::wrapAroundMode() const +{ + return m_d->parentDevice ? + m_d->parentDevice->defaultBounds()->wrapAroundMode() : false; +} + +int KisSelectionDefaultBounds::currentLevelOfDetail() const +{ + return m_d->parentDevice ? + m_d->parentDevice->defaultBounds()->currentLevelOfDetail() : 0; +} + +int KisSelectionDefaultBounds::currentTime() const +{ + return m_d->parentDevice ? + m_d->parentDevice->defaultBounds()->currentTime() : 0; +} + +bool KisSelectionDefaultBounds::externalFrameActive() const +{ + return m_d->parentDevice ? + m_d->parentDevice->defaultBounds()->externalFrameActive() : false; } /******************************************************************/ /* KisSelectionEmptyBounds */ /******************************************************************/ KisSelectionEmptyBounds::KisSelectionEmptyBounds(KisImageWSP image) : KisDefaultBounds(image) { } KisSelectionEmptyBounds::~KisSelectionEmptyBounds() { } QRect KisSelectionEmptyBounds::bounds() const { return QRect(0, 0, 0, 0); } diff --git a/libs/image/kis_default_bounds.h b/libs/image/kis_default_bounds.h index ed8c89fff5..c3493225ff 100644 --- a/libs/image/kis_default_bounds.h +++ b/libs/image/kis_default_bounds.h @@ -1,79 +1,83 @@ /* * Copyright (c) 2010 Boudewijn Rempt * Copyright (c) 2010 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_DEFAULT_BOUNDS_H #define KIS_DEFAULT_BOUNDS_H #include #include "kis_types.h" #include "kis_default_bounds_base.h" class KisDefaultBounds; class KisSelectionDefaultBounds; class KisSelectionEmptyBounds; typedef KisSharedPtr KisDefaultBoundsSP; typedef KisSharedPtr KisSelectionDefaultBoundsSP; typedef KisSharedPtr KisSelectionEmptyBoundsSP; class KRITAIMAGE_EXPORT KisDefaultBounds : public KisDefaultBoundsBase { public: KisDefaultBounds(KisImageWSP image = 0); ~KisDefaultBounds() override; QRect bounds() const override; bool wrapAroundMode() const override; int currentLevelOfDetail() const override; int currentTime() const override; bool externalFrameActive() const override; protected: friend class KisPaintDeviceTest; static const QRect infiniteRect; private: Q_DISABLE_COPY(KisDefaultBounds) struct Private; Private * const m_d; }; -class KRITAIMAGE_EXPORT KisSelectionDefaultBounds : public KisDefaultBounds +class KRITAIMAGE_EXPORT KisSelectionDefaultBounds : public KisDefaultBoundsBase { public: - KisSelectionDefaultBounds(KisPaintDeviceSP parentPaintDevice, KisImageWSP image = 0); + KisSelectionDefaultBounds(KisPaintDeviceSP parentPaintDevice); ~KisSelectionDefaultBounds() override; QRect bounds() const override; + bool wrapAroundMode() const override; + int currentLevelOfDetail() const override; + int currentTime() const override; + bool externalFrameActive() const override; private: Q_DISABLE_COPY(KisSelectionDefaultBounds) struct Private; Private * const m_d; }; class KRITAIMAGE_EXPORT KisSelectionEmptyBounds : public KisDefaultBounds { public: KisSelectionEmptyBounds(KisImageWSP image); ~KisSelectionEmptyBounds() override; QRect bounds() const override; }; #endif // KIS_DEFAULT_BOUNDS_H diff --git a/libs/image/kis_gaussian_kernel.cpp b/libs/image/kis_gaussian_kernel.cpp index 1b82b6c4ef..d6966f1d49 100644 --- a/libs/image/kis_gaussian_kernel.cpp +++ b/libs/image/kis_gaussian_kernel.cpp @@ -1,359 +1,400 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_gaussian_kernel.h" #include "kis_global.h" #include "kis_convolution_kernel.h" #include #include #include qreal KisGaussianKernel::sigmaFromRadius(qreal radius) { return 0.3 * radius + 0.3; } int KisGaussianKernel::kernelSizeFromRadius(qreal radius) { return 6 * ceil(sigmaFromRadius(radius)) + 1; } Eigen::Matrix KisGaussianKernel::createHorizontalMatrix(qreal radius) { int kernelSize = kernelSizeFromRadius(radius); Eigen::Matrix matrix(1, kernelSize); const qreal sigma = sigmaFromRadius(radius); const qreal multiplicand = 1 / (sqrt(2 * M_PI * sigma * sigma)); const qreal exponentMultiplicand = 1 / (2 * sigma * sigma); /** * The kernel size should always be odd, then the position of the * central pixel can be easily calculated */ KIS_ASSERT_RECOVER_NOOP(kernelSize & 0x1); const int center = kernelSize / 2; for (int x = 0; x < kernelSize; x++) { qreal xDistance = center - x; matrix(0, x) = multiplicand * exp( -xDistance * xDistance * exponentMultiplicand ); } return matrix; } Eigen::Matrix KisGaussianKernel::createVerticalMatrix(qreal radius) { int kernelSize = kernelSizeFromRadius(radius); Eigen::Matrix matrix(kernelSize, 1); const qreal sigma = sigmaFromRadius(radius); const qreal multiplicand = 1 / (sqrt(2 * M_PI * sigma * sigma)); const qreal exponentMultiplicand = 1 / (2 * sigma * sigma); /** * The kernel size should always be odd, then the position of the * central pixel can be easily calculated */ KIS_ASSERT_RECOVER_NOOP(kernelSize & 0x1); const int center = kernelSize / 2; for (int y = 0; y < kernelSize; y++) { qreal yDistance = center - y; matrix(y, 0) = multiplicand * exp( -yDistance * yDistance * exponentMultiplicand ); } return matrix; } KisConvolutionKernelSP KisGaussianKernel::createHorizontalKernel(qreal radius) { Eigen::Matrix matrix = createHorizontalMatrix(radius); return KisConvolutionKernel::fromMatrix(matrix, 0, matrix.sum()); } KisConvolutionKernelSP KisGaussianKernel::createVerticalKernel(qreal radius) { Eigen::Matrix matrix = createVerticalMatrix(radius); return KisConvolutionKernel::fromMatrix(matrix, 0, matrix.sum()); } KisConvolutionKernelSP KisGaussianKernel::createUniform2DKernel(qreal xRadius, qreal yRadius) { Eigen::Matrix h = createHorizontalMatrix(xRadius); Eigen::Matrix v = createVerticalMatrix(yRadius); Eigen::Matrix uni = v * h; return KisConvolutionKernel::fromMatrix(uni, 0, uni.sum()); } void KisGaussianKernel::applyGaussian(KisPaintDeviceSP device, const QRect& rect, qreal xRadius, qreal yRadius, const QBitArray &channelFlags, KoUpdater *progressUpdater, bool createTransaction) { QPoint srcTopLeft = rect.topLeft(); if (KisConvolutionPainter::supportsFFTW()) { KisConvolutionPainter painter(device, KisConvolutionPainter::FFTW); painter.setChannelFlags(channelFlags); painter.setProgress(progressUpdater); KisConvolutionKernelSP kernel2D = KisGaussianKernel::createUniform2DKernel(xRadius, yRadius); QScopedPointer transaction; if (createTransaction && painter.needsTransaction(kernel2D)) { transaction.reset(new KisTransaction(device)); } painter.applyMatrix(kernel2D, device, srcTopLeft, srcTopLeft, rect.size(), BORDER_REPEAT); } else if (xRadius > 0.0 && yRadius > 0.0) { KisPaintDeviceSP interm = new KisPaintDevice(device->colorSpace()); interm->prepareClone(device); KisConvolutionKernelSP kernelHoriz = KisGaussianKernel::createHorizontalKernel(xRadius); KisConvolutionKernelSP kernelVertical = KisGaussianKernel::createVerticalKernel(yRadius); qreal verticalCenter = qreal(kernelVertical->height()) / 2.0; KisConvolutionPainter horizPainter(interm); horizPainter.setChannelFlags(channelFlags); horizPainter.setProgress(progressUpdater); horizPainter.applyMatrix(kernelHoriz, device, srcTopLeft - QPoint(0, ceil(verticalCenter)), srcTopLeft - QPoint(0, ceil(verticalCenter)), rect.size() + QSize(0, 2 * ceil(verticalCenter)), BORDER_REPEAT); KisConvolutionPainter verticalPainter(device); verticalPainter.setChannelFlags(channelFlags); verticalPainter.setProgress(progressUpdater); verticalPainter.applyMatrix(kernelVertical, interm, srcTopLeft, srcTopLeft, rect.size(), BORDER_REPEAT); } else if (xRadius > 0.0) { KisConvolutionPainter painter(device); painter.setChannelFlags(channelFlags); painter.setProgress(progressUpdater); KisConvolutionKernelSP kernelHoriz = KisGaussianKernel::createHorizontalKernel(xRadius); QScopedPointer transaction; if (createTransaction && painter.needsTransaction(kernelHoriz)) { transaction.reset(new KisTransaction(device)); } painter.applyMatrix(kernelHoriz, device, srcTopLeft, srcTopLeft, rect.size(), BORDER_REPEAT); } else if (yRadius > 0.0) { KisConvolutionPainter painter(device); painter.setChannelFlags(channelFlags); painter.setProgress(progressUpdater); KisConvolutionKernelSP kernelVertical = KisGaussianKernel::createVerticalKernel(yRadius); QScopedPointer transaction; if (createTransaction && painter.needsTransaction(kernelVertical)) { transaction.reset(new KisTransaction(device)); } painter.applyMatrix(kernelVertical, device, srcTopLeft, srcTopLeft, rect.size(), BORDER_REPEAT); } } Eigen::Matrix -KisGaussianKernel::createLoGMatrix(qreal radius, qreal coeff) +KisGaussianKernel::createLoGMatrix(qreal radius, qreal coeff, bool zeroCentered, bool includeWrappedArea) { - int kernelSize = 4 * std::ceil(radius) + 1; + int kernelSize = 2 * (includeWrappedArea ? 2 : 1) * std::ceil(radius) + 1; Eigen::Matrix matrix(kernelSize, kernelSize); const qreal sigma = radius/* / sqrt(2)*/; const qreal multiplicand = -1.0 / (M_PI * pow2(pow2(sigma))); const qreal exponentMultiplicand = 1 / (2 * pow2(sigma)); /** * The kernel size should always be odd, then the position of the * central pixel can be easily calculated */ KIS_ASSERT_RECOVER_NOOP(kernelSize & 0x1); const int center = kernelSize / 2; for (int y = 0; y < kernelSize; y++) { const qreal yDistance = center - y; for (int x = 0; x < kernelSize; x++) { const qreal xDistance = center - x; const qreal distance = pow2(xDistance) + pow2(yDistance); const qreal normalizedDistance = exponentMultiplicand * distance; matrix(x, y) = multiplicand * (1.0 - normalizedDistance) * exp(-normalizedDistance); } } qreal lateral = matrix.sum() - matrix(center, center); matrix(center, center) = -lateral; + qreal totalSum = 0; + + if (zeroCentered) { + for (int y = 0; y < kernelSize; y++) { + for (int x = 0; x < kernelSize; x++) { + const qreal value = matrix(x, y); + totalSum += value; + } + } + } + qreal positiveSum = 0; qreal sideSum = 0; qreal quarterSum = 0; + totalSum = 0; + + const qreal offset = totalSum / pow2(qreal(kernelSize)); for (int y = 0; y < kernelSize; y++) { for (int x = 0; x < kernelSize; x++) { - const qreal value = matrix(x, y); + qreal value = matrix(x, y); + value -= offset; + matrix(x, y) = value; + if (value > 0) { positiveSum += value; } if (x > center) { sideSum += value; } if (x > center && y > center) { quarterSum += value; } + totalSum += value; } } const qreal scale = coeff * 2.0 / positiveSum; matrix *= scale; positiveSum *= scale; sideSum *= scale; quarterSum *= scale; //qDebug() << ppVar(positiveSum) << ppVar(sideSum) << ppVar(quarterSum); return matrix; } void KisGaussianKernel::applyLoG(KisPaintDeviceSP device, const QRect& rect, qreal radius, qreal coeff, const QBitArray &channelFlags, KoUpdater *progressUpdater) { QPoint srcTopLeft = rect.topLeft(); KisConvolutionPainter painter(device); painter.setChannelFlags(channelFlags); painter.setProgress(progressUpdater); - Eigen::Matrix matrix = createLoGMatrix(radius, coeff); + Eigen::Matrix matrix = + createLoGMatrix(radius, coeff, false, true); + KisConvolutionKernelSP kernel = + KisConvolutionKernel::fromMatrix(matrix, + 0, + 0); + + painter.applyMatrix(kernel, device, srcTopLeft, srcTopLeft, rect.size(), BORDER_REPEAT); +} + +void KisGaussianKernel::applyTightLoG(KisPaintDeviceSP device, + const QRect& rect, + qreal radius, qreal coeff, + const QBitArray &channelFlags, + KoUpdater *progressUpdater) +{ + QPoint srcTopLeft = rect.topLeft(); + + KisConvolutionPainter painter(device); + painter.setChannelFlags(channelFlags); + painter.setProgress(progressUpdater); + + Eigen::Matrix matrix = + createLoGMatrix(radius, coeff, true, false); KisConvolutionKernelSP kernel = KisConvolutionKernel::fromMatrix(matrix, 0, 0); painter.applyMatrix(kernel, device, srcTopLeft, srcTopLeft, rect.size(), BORDER_REPEAT); } Eigen::Matrix KisGaussianKernel::createDilateMatrix(qreal radius) { const int kernelSize = 2 * std::ceil(radius) + 1; Eigen::Matrix matrix(kernelSize, kernelSize); const qreal fadeStart = qMax(1.0, radius - 1.0); /** * The kernel size should always be odd, then the position of the * central pixel can be easily calculated */ KIS_ASSERT_RECOVER_NOOP(kernelSize & 0x1); const int center = kernelSize / 2; for (int y = 0; y < kernelSize; y++) { const qreal yDistance = center - y; for (int x = 0; x < kernelSize; x++) { const qreal xDistance = center - x; const qreal distance = std::sqrt(pow2(xDistance) + pow2(yDistance)); qreal value = 1.0; - if (distance >= radius) { + if (distance > radius + 1e-3) { value = 0.0; } else if (distance > fadeStart) { - value = radius - distance; + value = qMax(0.0, radius - distance); } matrix(x, y) = value; } } return matrix; } void KisGaussianKernel::applyDilate(KisPaintDeviceSP device, const QRect &rect, qreal radius, const QBitArray &channelFlags, KoUpdater *progressUpdater, bool createTransaction) { KIS_SAFE_ASSERT_RECOVER_RETURN(device->colorSpace()->pixelSize() == 1); QPoint srcTopLeft = rect.topLeft(); KisConvolutionPainter painter(device); painter.setChannelFlags(channelFlags); painter.setProgress(progressUpdater); Eigen::Matrix matrix = createDilateMatrix(radius); KisConvolutionKernelSP kernel = KisConvolutionKernel::fromMatrix(matrix, 0, 1.0); QScopedPointer transaction; if (createTransaction && painter.needsTransaction(kernel)) { transaction.reset(new KisTransaction(device)); } painter.applyMatrix(kernel, device, srcTopLeft, srcTopLeft, rect.size(), BORDER_REPEAT); } #include "kis_sequential_iterator.h" void KisGaussianKernel::applyErodeU8(KisPaintDeviceSP device, const QRect &rect, qreal radius, const QBitArray &channelFlags, KoUpdater *progressUpdater, bool createTransaction) { KIS_SAFE_ASSERT_RECOVER_RETURN(device->colorSpace()->pixelSize() == 1); { KisSequentialIterator dstIt(device, rect); while (dstIt.nextPixel()) { quint8 *dstPtr = dstIt.rawData(); *dstPtr = 255 - *dstPtr; } } applyDilate(device, rect, radius, channelFlags, progressUpdater, createTransaction); { KisSequentialIterator dstIt(device, rect); while (dstIt.nextPixel()) { quint8 *dstPtr = dstIt.rawData(); *dstPtr = 255 - *dstPtr; } } } diff --git a/libs/image/kis_gaussian_kernel.h b/libs/image/kis_gaussian_kernel.h index c9c300fbdd..d1ae29e8c4 100644 --- a/libs/image/kis_gaussian_kernel.h +++ b/libs/image/kis_gaussian_kernel.h @@ -1,83 +1,90 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_GAUSSIAN_KERNEL_H #define __KIS_GAUSSIAN_KERNEL_H #include "kritaimage_export.h" #include "kis_types.h" #include class QRect; class KRITAIMAGE_EXPORT KisGaussianKernel { public: static Eigen::Matrix createHorizontalMatrix(qreal radius); static Eigen::Matrix createVerticalMatrix(qreal radius); static KisConvolutionKernelSP createHorizontalKernel(qreal radius); static KisConvolutionKernelSP createVerticalKernel(qreal radius); static KisConvolutionKernelSP createUniform2DKernel(qreal xRadius, qreal yRadius); static qreal sigmaFromRadius(qreal radius); static int kernelSizeFromRadius(qreal radius); static void applyGaussian(KisPaintDeviceSP device, const QRect& rect, qreal xRadius, qreal yRadius, const QBitArray &channelFlags, KoUpdater *updater, bool createTransaction = false); - static Eigen::Matrix createLoGMatrix(qreal radius, qreal coeff = 1.0); + static Eigen::Matrix createLoGMatrix(qreal radius, qreal coeff, bool zeroCentered, bool includeWrappedArea); static void applyLoG(KisPaintDeviceSP device, const QRect& rect, qreal radius, qreal coeff, const QBitArray &channelFlags, KoUpdater *progressUpdater); + static void applyTightLoG(KisPaintDeviceSP device, + const QRect& rect, + qreal radius, qreal coeff, + const QBitArray &channelFlags, + KoUpdater *progressUpdater); + + static Eigen::Matrix createDilateMatrix(qreal radius); static void applyDilate(KisPaintDeviceSP device, const QRect& rect, qreal radius, const QBitArray &channelFlags, KoUpdater *progressUpdater, bool createTransaction = false); static void applyErodeU8(KisPaintDeviceSP device, const QRect& rect, qreal radius, const QBitArray &channelFlags, KoUpdater *progressUpdater, bool createTransaction = false); }; #endif /* __KIS_GAUSSIAN_KERNEL_H */ diff --git a/libs/image/kis_image.cc b/libs/image/kis_image.cc index 4364c7cb19..8b112848fc 100644 --- a/libs/image/kis_image.cc +++ b/libs/image/kis_image.cc @@ -1,2068 +1,2068 @@ /* * 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 "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 "tiles3/kis_lockless_stack.h" #include #include #include "kis_time_range.h" #include "KisRunnableBasedStrokeStrategy.h" #include "KisRunnableStrokeJobData.h" #include "KisRunnableStrokeJobUtils.h" #include "KisRunnableStrokeJobsInterface.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) , signalRouter(_q) , animationInterface(_animationInterface) , scheduler(_q, _q) , axesCenter(QPointF(0.5, 0.5)) { { KisImageConfig cfg(true); 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 KisSelectionMaskSP targetOverlaySelectionMask; // the overlay switching stroke will try to switch into this mask KisSelectionMaskSP overlaySelectionMask; QList compositions; KisNodeSP isolatedRootNode; bool wrapAroundModePermitted = false; KisNameServer nserver; QScopedPointer undoStore; KisLegacyUndoAdapter legacyUndoAdapter; KisPostExecutionUndoAdapter postExecutionUndoAdapter; vKisAnnotationSP annotations; QAtomicInt disableUIUpdateSignals; KisLocklessStack savedDisabledUIUpdates; KisProjectionUpdatesFilterSP projectionUpdatesFilter; KisImageSignalRouter signalRouter; KisImageAnimationInterface *animationInterface; KisUpdateScheduler scheduler; QAtomicInt disableDirtyRequests; KisCompositeProgressProxy compositeProgressProxy; bool blockLevelOfDetail = false; QPointF axesCenter; bool allowMasksOnRootNode = false; bool tryCancelCurrentStrokeAsync(); void notifyProjectionUpdatedInPatches(const QRect &rc, QVector &jobs); }; 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 } KisImageSP KisImage::fromQImage(const QImage &image, KisUndoStore *undoStore) { const KoColorSpace *colorSpace = 0; switch (image.format()) { case QImage::Format_Invalid: case QImage::Format_Mono: case QImage::Format_MonoLSB: colorSpace = KoColorSpaceRegistry::instance()->graya8(); break; case QImage::Format_Indexed8: case QImage::Format_RGB32: case QImage::Format_ARGB32: case QImage::Format_ARGB32_Premultiplied: colorSpace = KoColorSpaceRegistry::instance()->rgb8(); break; case QImage::Format_RGB16: colorSpace = KoColorSpaceRegistry::instance()->rgb16(); break; case QImage::Format_ARGB8565_Premultiplied: case QImage::Format_RGB666: case QImage::Format_ARGB6666_Premultiplied: case QImage::Format_RGB555: case QImage::Format_ARGB8555_Premultiplied: case QImage::Format_RGB888: case QImage::Format_RGB444: case QImage::Format_ARGB4444_Premultiplied: case QImage::Format_RGBX8888: case QImage::Format_RGBA8888: case QImage::Format_RGBA8888_Premultiplied: colorSpace = KoColorSpaceRegistry::instance()->rgb8(); break; case QImage::Format_BGR30: case QImage::Format_A2BGR30_Premultiplied: case QImage::Format_RGB30: case QImage::Format_A2RGB30_Premultiplied: colorSpace = KoColorSpaceRegistry::instance()->rgb8(); break; case QImage::Format_Alpha8: colorSpace = KoColorSpaceRegistry::instance()->alpha8(); break; case QImage::Format_Grayscale8: colorSpace = KoColorSpaceRegistry::instance()->graya8(); break; #if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0) case QImage::Format_Grayscale16: colorSpace = KoColorSpaceRegistry::instance()->graya16(); break; #endif #if QT_VERSION >= QT_VERSION_CHECK(5, 12, 0) case QImage::Format_RGBX64: case QImage::Format_RGBA64: case QImage::Format_RGBA64_Premultiplied: colorSpace = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), 0); break; #endif default: colorSpace = 0; } KisImageSP img = new KisImage(undoStore, image.width(), image.height(), colorSpace, i18n("Imported Image")); KisPaintLayerSP layer = new KisPaintLayer(img, img->nextLayerName(), 255); layer->paintDevice()->convertFromQImage(image, 0, 0, 0); img->addNode(layer.data(), img->rootLayer().data()); return img; } KisImage *KisImage::clone(bool exactCopy) { return new KisImage(*this, 0, exactCopy); } void KisImage::copyFromImage(const KisImage &rhs) { copyFromImageImpl(rhs, REPLACE); } void KisImage::copyFromImageImpl(const KisImage &rhs, int policy) { // make sure we choose exactly one from REPLACE and CONSTRUCT KIS_ASSERT_RECOVER_RETURN((policy & REPLACE) != (policy & CONSTRUCT)); // only when replacing do we need to emit signals #define EMIT_IF_NEEDED if (!(policy & REPLACE)) {} else emit if (policy & REPLACE) { // if we are constructing the image, these are already set if (m_d->width != rhs.width() || m_d->height != rhs.height()) { m_d->width = rhs.width(); m_d->height = rhs.height(); emit sigSizeChanged(QPointF(), QPointF()); } if (m_d->colorSpace != rhs.colorSpace()) { m_d->colorSpace = rhs.colorSpace(); emit sigColorSpaceChanged(m_d->colorSpace); } } // from KisImage::KisImage(const KisImage &, KisUndoStore *, bool) setObjectName(rhs.objectName()); if (m_d->xres != rhs.m_d->xres || m_d->yres != rhs.m_d->yres) { m_d->xres = rhs.m_d->xres; m_d->yres = rhs.m_d->yres; EMIT_IF_NEEDED sigResolutionChanged(m_d->xres, m_d->yres); } m_d->allowMasksOnRootNode = rhs.m_d->allowMasksOnRootNode; if (rhs.m_d->proofingConfig) { KisProofingConfigurationSP proofingConfig(new KisProofingConfiguration(*rhs.m_d->proofingConfig)); if (policy & REPLACE) { setProofingConfiguration(proofingConfig); } else { m_d->proofingConfig = proofingConfig; } } KisNodeSP newRoot = rhs.root()->clone(); newRoot->setGraphListener(this); newRoot->setImage(this); m_d->rootLayer = dynamic_cast(newRoot.data()); setRoot(newRoot); bool exactCopy = policy & EXACT_COPY; 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; } }); } KisLayerUtils::recursiveApplyNodes(newRoot, [](KisNodeSP node) { dbgImage << "Node: " << (void *)node.data(); }); m_d->compositions.clear(); Q_FOREACH (KisLayerCompositionSP comp, rhs.m_d->compositions) { m_d->compositions << toQShared(new KisLayerComposition(*comp, this)); } EMIT_IF_NEEDED sigLayersChangedAsync(); m_d->nserver = 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; /** * The overlay device is not inherited when cloning the image! */ if (rhs.m_d->overlaySelectionMask) { const QRect dirtyRect = rhs.m_d->overlaySelectionMask->extent(); m_d->rootLayer->setDirty(dirtyRect); } #undef EMIT_IF_NEEDED } 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())); copyFromImageImpl(rhs, CONSTRUCT | (exactCopy ? EXACT_COPY : 0)); } 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); } void KisImage::aboutToRemoveANode(KisNode *parent, int index) { KisNodeSP deletedNode = parent->at(index); if (!dynamic_cast(deletedNode.data()) && deletedNode == m_d->isolatedRootNode) { 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()); } void KisImage::setOverlaySelectionMask(KisSelectionMaskSP mask) { if (m_d->targetOverlaySelectionMask == mask) return; m_d->targetOverlaySelectionMask = mask; struct UpdateOverlaySelectionStroke : public KisSimpleStrokeStrategy { UpdateOverlaySelectionStroke(KisImageSP image) : KisSimpleStrokeStrategy("update-overlay-selection-mask", kundo2_noi18n("update-overlay-selection-mask")), m_image(image) { this->enableJob(JOB_INIT, true, KisStrokeJobData::BARRIER, KisStrokeJobData::EXCLUSIVE); setClearsRedoOnStart(false); } void initStrokeCallback() { KisSelectionMaskSP oldMask = m_image->m_d->overlaySelectionMask; KisSelectionMaskSP newMask = m_image->m_d->targetOverlaySelectionMask; if (oldMask == newMask) return; KIS_SAFE_ASSERT_RECOVER_RETURN(!newMask || newMask->graphListener() == m_image); m_image->m_d->overlaySelectionMask = newMask; if (oldMask || newMask) { m_image->m_d->rootLayer->notifyChildMaskChanged(); } if (oldMask) { m_image->m_d->rootLayer->setDirtyDontResetAnimationCache(oldMask->extent()); } if (newMask) { newMask->setDirty(); } m_image->undoAdapter()->emitSelectionChanged(); } private: KisImageSP m_image; }; KisStrokeId id = startStroke(new UpdateOverlaySelectionStroke(this)); endStroke(id); } KisSelectionMaskSP KisImage::overlaySelectionMask() const { return m_d->overlaySelectionMask; } bool KisImage::hasOverlaySelectionMask() const { return m_d->overlaySelectionMask; } 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); } KIS_SAFE_ASSERT_RECOVER_NOOP(m_d->rootLayer->childCount() > 0); KIS_SAFE_ASSERT_RECOVER_NOOP(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, const QPointF ¢er, qreal scaleX, qreal scaleY, KisFilterStrategy *filterStrategy, KisSelectionSP selection) { KUndo2MagicString actionName(kundo2_i18n("Scale Layer")); KisImageSignalVector emitSignals; emitSignals << ModifiedSignal; QPointF offset; { KisTransformWorker worker(0, scaleX, scaleY, 0, 0, 0, 0, 0.0, 0, 0, 0, 0); QTransform transform = worker.transform(); offset = center - transform.map(center); } KisProcessingApplicator applicator(this, node, KisProcessingApplicator::RECURSIVE, emitSignals, actionName); KisTransformProcessingVisitor *visitor = new KisTransformProcessingVisitor(scaleX, scaleY, 0, 0, QPointF(), 0, offset.x(), offset.y(), filterStrategy); visitor->setSelection(selection); if (selection) { applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT); } else { applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); } applicator.end(); } void KisImage::rotateImpl(const KUndo2MagicString &actionName, KisNodeSP rootNode, double radians, bool resizeImage, KisSelectionSP selection) { // we can either transform (and resize) the whole image or // transform a selection, we cannot do both at the same time KIS_SAFE_ASSERT_RECOVER(!(bool(selection) && resizeImage)) { selection = 0; } const QRect baseBounds = resizeImage ? bounds() : selection ? selection->selectedExactRect() : rootNode->exactBounds(); 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(baseBounds); newSize = newRect.size(); offset = -newRect.topLeft(); } else { QPointF origin = QRectF(baseBounds).center(); newSize = size(); offset = -(transform.map(origin) - origin); } } bool sizeChanged = resizeImage && (newSize.width() != baseBounds.width() || newSize.height() != baseBounds.height()); // These signals will be emitted after processing is done KisImageSignalVector emitSignals; if (sizeChanged) emitSignals << ComplexSizeChangedSignal(baseBounds, 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"); KisTransformProcessingVisitor *visitor = new KisTransformProcessingVisitor(1.0, 1.0, 0.0, 0.0, QPointF(), radians, offset.x(), offset.y(), filter); if (selection) { visitor->setSelection(selection); } if (selection) { applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT); } else { 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(), radians, true, 0); } void KisImage::rotateNode(KisNodeSP node, double radians, KisSelectionSP selection) { if (node->inherits("KisMask")) { rotateImpl(kundo2_i18n("Rotate Mask"), node, radians, false, selection); } else { rotateImpl(kundo2_i18n("Rotate Layer"), node, radians, false, selection); } } void KisImage::shearImpl(const KUndo2MagicString &actionName, KisNodeSP rootNode, bool resizeImage, double angleX, double angleY, KisSelectionSP selection) { const QRect baseBounds = resizeImage ? bounds() : selection ? selection->selectedExactRect() : rootNode->exactBounds(); const QPointF origin = QRectF(baseBounds).center(); //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(baseBounds); newSize = newRect.size(); if (resizeImage) offset = -newRect.topLeft(); } if (newSize == baseBounds.size()) return; KisImageSignalVector emitSignals; if (resizeImage) emitSignals << ComplexSizeChangedSignal(baseBounds, 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"); KisTransformProcessingVisitor *visitor = new KisTransformProcessingVisitor(1.0, 1.0, tanX, tanY, origin, 0, offset.x(), offset.y(), filter); if (selection) { visitor->setSelection(selection); } if (selection) { applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT); } else { applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); } if (resizeImage) { applicator.applyCommand(new KisImageResizeCommand(this, newSize)); } applicator.end(); } void KisImage::shearNode(KisNodeSP node, double angleX, double angleY, KisSelectionSP selection) { if (node->inherits("KisMask")) { shearImpl(kundo2_i18n("Shear Mask"), node, false, angleX, angleY, selection); } else { shearImpl(kundo2_i18n("Shear Layer"), node, false, angleX, angleY, selection); } } void KisImage::shear(double angleX, double angleY) { shearImpl(kundo2_i18n("Shear Image"), m_d->rootLayer, true, angleX, angleY, 0); } 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(KisNodeSP activeNode) { KisLayerUtils::flattenImage(this, activeNode); } 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; } const KUndo2Command* KisImage::lastExecutedCommand() const { return m_d->undoStore->presentCommand(); } 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; } 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); setRoot(m_d->rootLayer.data()); this->setDefaultProjectionColor(defaultProjectionColor); } 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, QVector &jobs) { KisImageConfig imageConfig(true); 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; KritaUtils::addJobConcurrent(jobs, std::bind(&KisImage::notifyProjectionUpdated, q, patchRect)); } } } bool KisImage::startIsolatedMode(KisNodeSP node) { struct StartIsolatedModeStroke : public KisRunnableBasedStrokeStrategy { StartIsolatedModeStroke(KisNodeSP node, KisImageSP image) : KisRunnableBasedStrokeStrategy("start-isolated-mode", kundo2_noi18n("start-isolated-mode")), m_node(node), m_image(image) { this->enableJob(JOB_INIT, true, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); this->enableJob(JOB_DOSTROKE, true); 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 QVector jobs; m_image->m_d->notifyProjectionUpdatedInPatches(m_image->bounds(), jobs); this->runnableJobsInterface()->addRunnableJobs(jobs); 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 KisRunnableBasedStrokeStrategy { StopIsolatedModeStroke(KisImageSP image) : KisRunnableBasedStrokeStrategy("stop-isolated-mode", kundo2_noi18n("stop-isolated-mode")), m_image(image) { this->enableJob(JOB_INIT); this->enableJob(JOB_DOSTROKE, true); 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(); m_image->invalidateAllFrames(); // the GUI uses our thread to do the color space conversion so we // need to emit this signal in multiple threads QVector jobs; m_image->m_d->notifyProjectionUpdatedInPatches(m_image->bounds(), jobs); this->runnableJobsInterface()->addRunnableJobs(jobs); // 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); } bool KisImage::hasUpdatesRunning() const { return m_d->scheduler.hasUpdatesRunning(); } 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::notifyBatchUpdateStarted() { m_d->signalRouter.emitNotifyBatchUpdateStarted(); } void KisImage::notifyBatchUpdateEnded() { m_d->signalRouter.emitNotifyBatchUpdateEnded(); } void KisImage::notifyUIUpdateCompleted(const QRect &rc) { notifyProjectionUpdated(rc); } QVector KisImage::enableUIUpdates() { m_d->disableUIUpdateSignals.deref(); QRect rect; QVector postponedUpdates; while (m_d->savedDisabledUIUpdates.pop(rect)) { postponedUpdates.append(rect); } return postponedUpdates; } 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); } else { m_d->savedDisabledUIUpdates.push(rc); } } 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 + * The selection is calculated asynchronously, 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); } KisNode *KisImage::graphOverlayNode() const { return m_d->overlaySelectionMask.data(); } 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::nodeCollapsedChanged(KisNode * node) { Q_UNUSED(node); 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; } void KisImage::setAllowMasksOnRootNode(bool value) { m_d->allowMasksOnRootNode = value; } bool KisImage::allowMasksOnRootNode() const { return m_d->allowMasksOnRootNode; } diff --git a/libs/image/kis_image.h b/libs/image/kis_image.h index 12f78a29bb..042a2d1515 100644 --- a/libs/image/kis_image.h +++ b/libs/image/kis_image.h @@ -1,1189 +1,1189 @@ /* * 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. */ #ifndef KIS_IMAGE_H_ #define KIS_IMAGE_H_ #include #include #include #include #include #include #include #include "kis_types.h" #include "kis_shared.h" #include "kis_node_graph_listener.h" #include "kis_node_facade.h" #include "kis_image_interfaces.h" #include "kis_strokes_queue_undo_result.h" #include class KoColorSpace; class KoColor; class KisCompositeProgressProxy; class KisUndoStore; class KisUndoAdapter; class KisImageSignalRouter; class KisPostExecutionUndoAdapter; class KisFilterStrategy; class KoColorProfile; class KisLayerComposition; class KisSpontaneousJob; class KisImageAnimationInterface; class KUndo2MagicString; class KisProofingConfiguration; class KisPaintDevice; namespace KisMetaData { class MergeStrategy; } /** * This is the image class, it contains a tree of KisLayer stack and * meta information about the image. And it also provides some * functions to manipulate the whole image. */ class KRITAIMAGE_EXPORT KisImage : public QObject, public KisStrokesFacade, public KisStrokeUndoFacade, public KisUpdatesFacade, public KisProjectionUpdateListener, public KisNodeFacade, public KisNodeGraphListener, public KisShared { Q_OBJECT public: /// @p colorSpace can be null. In that case, it will be initialised to a default color space. KisImage(KisUndoStore *undoStore, qint32 width, qint32 height, const KoColorSpace *colorSpace, const QString& name); ~KisImage() override; static KisImageSP fromQImage(const QImage &image, KisUndoStore *undoStore); public: // KisNodeGraphListener implementation void aboutToAddANode(KisNode *parent, int index) override; void nodeHasBeenAdded(KisNode *parent, int index) override; void aboutToRemoveANode(KisNode *parent, int index) override; void nodeChanged(KisNode * node) override; void nodeCollapsedChanged(KisNode *node) override; void invalidateAllFrames() override; void notifySelectionChanged() override; void requestProjectionUpdate(KisNode *node, const QVector &rects, bool resetAnimationCache) override; void invalidateFrames(const KisTimeRange &range, const QRect &rect) override; void requestTimeSwitch(int time) override; KisNode* graphOverlayNode() const override; public: // KisProjectionUpdateListener implementation void notifyProjectionUpdated(const QRect &rc) override; public: /** * Set the number of threads used by the image's working threads */ void setWorkingThreadsLimit(int value); /** * Return the number of threads available to the image's working threads */ int workingThreadsLimit() const; /** * Makes a copy of the image with all the layers. If possible, shallow * copies of the layers are made. * * \p exactCopy shows if the copied image should look *exactly* the same as * the other one (according to it's .kra xml representation). It means that * the layers will have the same UUID keys and, therefore, you are not * expected to use the copied image anywhere except for saving. Don't use * this option if you plan to work with the copied image later. */ KisImage *clone(bool exactCopy = false); void copyFromImage(const KisImage &rhs); private: // must specify exactly one from CONSTRUCT or REPLACE. enum CopyPolicy { CONSTRUCT = 1, ///< we are copy-constructing a new KisImage REPLACE = 2, ///< we are replacing the current KisImage with another EXACT_COPY = 4, /// we need an exact copy of the original image }; void copyFromImageImpl(const KisImage &rhs, int policy); public: /** * Render the projection onto a QImage. */ QImage convertToQImage(qint32 x1, qint32 y1, qint32 width, qint32 height, const KoColorProfile * profile); /** * Render the projection onto a QImage. * (this is an overloaded function) */ QImage convertToQImage(QRect imageRect, const KoColorProfile * profile); /** * Render a thumbnail of the projection onto a QImage. */ QImage convertToQImage(const QSize& scaledImageSize, const KoColorProfile *profile); /** * [low-level] Lock the image without waiting for all the internal job queues are processed * * WARNING: Don't use it unless you really know what you are doing! Use barrierLock() instead! * * Waits for all the **currently running** internal jobs to complete and locks the image * for writing. Please note that this function does **not** wait for all the internal * queues to process, so there might be some non-finished actions pending. It means that * you just postpone these actions until you unlock() the image back. Until then, then image * might easily be frozen in some inconsistent state. * * The only sane usage for this function is to lock the image for **emergency** * processing, when some internal action or scheduler got hung up, and you just want * to fetch some data from the image without races. * * In all other cases, please use barrierLock() instead! */ void lock(); /** * Unlocks the image and starts/resumes all the pending internal jobs. If the image * has been locked for a non-readOnly access, then all the internal caches of the image * (e.g. lod-planes) are reset and regeneration jobs are scheduled. */ void unlock(); /** * @return return true if the image is in a locked state, i.e. all the internal * jobs are blocked from execution by calling wither lock() or barrierLock(). * * When the image is locked, the user can do some modifications to the image * contents safely without a perspective having race conditions with internal * image jobs. */ bool locked() const; /** * Sets the mask (it must be a part of the node hierarchy already) to be paited on * the top of all layers. This method does all the locking and syncing for you. It * is executed asynchronously. */ void setOverlaySelectionMask(KisSelectionMaskSP mask); /** * \see setOverlaySelectionMask */ KisSelectionMaskSP overlaySelectionMask() const; /** * \see setOverlaySelectionMask */ bool hasOverlaySelectionMask() const; /** * @return the global selection object or 0 if there is none. The * global selection is always read-write. */ KisSelectionSP globalSelection() const; /** * Retrieve the next automatic layername (XXX: fix to add option to return Mask X) */ QString nextLayerName(const QString &baseName = "") const; /** * Set the automatic layer name counter one back. */ void rollBackLayerName(); /** * @brief start asynchronous operation on resizing the image * * The method will resize the image to fit the new size without * dropping any pixel data. The GUI will get correct * notification with old and new sizes, so it adjust canvas origin * accordingly and avoid jumping of the canvas on screen * * @param newRect the rectangle of the image which will be visible * after operation is completed * * Please note that the actual operation starts asynchronously in * a background, so you cannot expect the image having new size * right after this call. */ void resizeImage(const QRect& newRect); /** * @brief start asynchronous operation on cropping the image * * The method will **drop** all the image data outside \p newRect * and resize the image to fit the new size. The GUI will get correct * notification with old and new sizes, so it adjust canvas origin * accordingly and avoid jumping of the canvas on screen * * @param newRect the rectangle of the image which will be cut-out * * Please note that the actual operation starts asynchronously in * a background, so you cannot expect the image having new size * right after this call. */ void cropImage(const QRect& newRect); /** * @brief start asynchronous operation on cropping a subtree of nodes starting at \p node * * The method will **drop** all the layer data outside \p newRect. Neither * image nor a layer will be moved anywhere * * @param node node to crop * @param newRect the rectangle of the layer which will be cut-out * * Please note that the actual operation starts asynchronously in * a background, so you cannot expect the image having new size * right after this call. */ void cropNode(KisNodeSP node, const QRect& newRect); /** * @brief start asynchronous operation on scaling the image * @param size new image size in pixels * @param xres new image x-resolution pixels-per-pt * @param yres new image y-resolution pixels-per-pt * @param filterStrategy filtering strategy * * Please note that the actual operation starts asynchronously in * a background, so you cannot expect the image having new size * right after this call. */ void scaleImage(const QSize &size, qreal xres, qreal yres, KisFilterStrategy *filterStrategy); /** * @brief start asynchronous operation on scaling a subtree of nodes starting at \p node * @param node node to scale * @param center the center of the scaling * @param scaleX x-scale coefficient to be applied to the node * @param scaleY y-scale coefficient to be applied to the node * @param filterStrategy filtering strategy * @param selection the selection we based on * * Please note that the actual operation starts asynchronously in * a background, so you cannot expect the image having new size * right after this call. */ void scaleNode(KisNodeSP node, const QPointF ¢er, qreal scaleX, qreal scaleY, KisFilterStrategy *filterStrategy, KisSelectionSP selection); /** * @brief start asynchronous operation on rotating the image * * The image is resized to fit the rotated rectangle * * @param radians rotation angle in radians * * Please note that the actual operation starts asynchronously in * a background, so you cannot expect the operation being completed * right after the call */ void rotateImage(double radians); /** * @brief start asynchronous operation on rotating a subtree of nodes starting at \p node * * The image is not resized! * * @param node the root of the subtree to rotate * @param radians rotation angle in radians * @param selection the selection we based on * * Please note that the actual operation starts asynchronously in * a background, so you cannot expect the operation being completed * right after the call */ void rotateNode(KisNodeSP node, double radians, KisSelectionSP selection); /** * @brief start asynchronous operation on shearing the image * * The image is resized to fit the sheared polygon * * @p angleX, @p angleY are given in degrees. * * Please note that the actual operation starts asynchronously in * a background, so you cannot expect the operation being completed * right after the call */ void shear(double angleX, double angleY); /** * @brief start asynchronous operation on shearing a subtree of nodes starting at \p node * * The image is not resized! * * @param node the root of the subtree to rotate * @param angleX x-shear given in degrees. * @param angleY y-shear given in degrees. * @param selection the selection we based on * * Please note that the actual operation starts asynchronously in * a background, so you cannot expect the operation being completed * right after the call */ void shearNode(KisNodeSP node, double angleX, double angleY, KisSelectionSP selection); /** * Convert the image and all its layers to the dstColorSpace */ void convertImageColorSpace(const KoColorSpace *dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags); /** * Set the color space of the projection (and the root layer) * to dstColorSpace. No conversion is done for other layers, * their colorspace can differ. * @note No conversion is done, only regeneration, so no rendering * intent needed */ void convertProjectionColorSpace(const KoColorSpace *dstColorSpace); // Get the profile associated with this image const KoColorProfile * profile() const; /** * Set the profile of the image to the new profile and do the same for * all layers that have the same colorspace and profile of the image. * It doesn't do any pixel conversion. * * This is essential if you have loaded an image that didn't * have an embedded profile to which you want to attach the right profile. * * This does not create an undo action; only call it when creating or * loading an image. * * @returns false if the profile could not be assigned */ bool assignImageProfile(const KoColorProfile *profile); /** * Returns the current undo adapter. You can add new commands to the * undo stack using the adapter. This adapter is used for a backward * compatibility for old commands created before strokes. It blocks * all the porcessing at the scheduler, waits until it's finished * and executes commands exclusively. */ KisUndoAdapter* undoAdapter() const; /** * This adapter is used by the strokes system. The commands are added * to it *after* redo() is done (in the scheduler context). They are * wrapped into a special command and added to the undo stack. redo() * in not called. */ KisPostExecutionUndoAdapter* postExecutionUndoAdapter() const override; /** * Return the lastly executed LoD0 command. It is effectively the same * as to call undoAdapter()->presentCommand(); */ const KUndo2Command* lastExecutedCommand() const override; /** * Replace current undo store with the new one. The old store * will be deleted. * This method is used by KisDocument for dropping all the commands * during file loading. */ void setUndoStore(KisUndoStore *undoStore); /** * Return current undo store of the image */ KisUndoStore* undoStore(); /** * Tell the image it's modified; this emits the sigImageModified * signal. This happens when the image needs to be saved */ void setModified(); /** * The default colorspace of this image: new layers will have this * colorspace and the projection will have this colorspace. */ const KoColorSpace * colorSpace() const; /** * X resolution in pixels per pt */ double xRes() const; /** * Y resolution in pixels per pt */ double yRes() const; /** * Set the resolution in pixels per pt. */ void setResolution(double xres, double yres); /** * Convert a document coordinate to a pixel coordinate. * * @param documentCoord PostScript Pt coordinate to convert. */ QPointF documentToPixel(const QPointF &documentCoord) const; /** * Convert a document coordinate to an integer pixel coordinate rounded down. * * @param documentCoord PostScript Pt coordinate to convert. */ QPoint documentToImagePixelFloored(const QPointF &documentCoord) const; /** * Convert a document rectangle to a pixel rectangle. * * @param documentRect PostScript Pt rectangle to convert. */ QRectF documentToPixel(const QRectF &documentRect) const; /** * Convert a pixel coordinate to a document coordinate. * * @param pixelCoord pixel coordinate to convert. */ QPointF pixelToDocument(const QPointF &pixelCoord) const; /** * Convert an integer pixel coordinate to a document coordinate. * The document coordinate is at the centre of the pixel. * * @param pixelCoord pixel coordinate to convert. */ QPointF pixelToDocument(const QPoint &pixelCoord) const; /** * Convert a document rectangle to an integer pixel rectangle. * * @param pixelCoord pixel coordinate to convert. */ QRectF pixelToDocument(const QRectF &pixelCoord) const; /** * Return the width of the image */ qint32 width() const; /** * Return the height of the image */ qint32 height() const; /** * Return the size of the image */ QSize size() const { return QSize(width(), height()); } /** * @return the root node of the image node graph */ KisGroupLayerSP rootLayer() const; /** * Return the projection; that is, the complete, composited * representation of this image. */ KisPaintDeviceSP projection() const; /** * Return the number of layers (not other nodes) that are in this * image. */ qint32 nlayers() const; /** * Return the number of layers (not other node types) that are in * this image and that are hidden. */ qint32 nHiddenLayers() const; /** * Merge all visible layers and discard hidden ones. */ void flatten(KisNodeSP activeNode); /** * Merge the specified layer with the layer * below this layer, remove the specified layer. */ void mergeDown(KisLayerSP l, const KisMetaData::MergeStrategy* strategy); /** * flatten the layer: that is, the projection becomes the layer * and all subnodes are removed. If this is not a paint layer, it will morph * into a paint layer. */ void flattenLayer(KisLayerSP layer); /** * Merges layers in \p mergedLayers and creates a new layer above * \p putAfter */ void mergeMultipleLayers(QList mergedLayers, KisNodeSP putAfter); /// @return the exact bounds of the image in pixel coordinates. QRect bounds() const override; /** * Returns the actual bounds of the image, taking LevelOfDetail * into account. This value is used as a bounds() value of * KisDefaultBounds object. */ QRect effectiveLodBounds() const; /// use if the layers have changed _completely_ (eg. when flattening) void notifyLayersChanged(); /** * Sets the default color of the root layer projection. All the layers * will be merged on top of this very color */ void setDefaultProjectionColor(const KoColor &color); /** * \see setDefaultProjectionColor() */ KoColor defaultProjectionColor() const; void setRootLayer(KisGroupLayerSP rootLayer); /** * Add an annotation for this image. This can be anything: Gamma, EXIF, etc. * Note that the "icc" annotation is reserved for the color strategies. * If the annotation already exists, overwrite it with this one. */ void addAnnotation(KisAnnotationSP annotation); /** get the annotation with the given type, can return 0 */ KisAnnotationSP annotation(const QString& type); /** delete the annotation, if the image contains it */ void removeAnnotation(const QString& type); /** * Start of an iteration over the annotations of this image (including the ICC Profile) */ vKisAnnotationSP_it beginAnnotations(); /** end of an iteration over the annotations of this image */ vKisAnnotationSP_it endAnnotations(); /** * Called before the image is deleted and sends the sigAboutToBeDeleted signal */ void notifyAboutToBeDeleted(); KisImageSignalRouter* signalRouter(); /** * Returns whether we can reselect current global selection * * \see reselectGlobalSelection() */ bool canReselectGlobalSelection(); /** * Returns the layer compositions for the image */ QList compositions(); /** * Adds a new layer composition, will be saved with the image */ void addComposition(KisLayerCompositionSP composition); /** * Remove the layer compostion */ void removeComposition(KisLayerCompositionSP composition); /** * Permit or deny the wrap-around mode for all the paint devices * of the image. Note that permitting the wraparound mode will not * necessarily activate it right now. To be activated the wrap * around mode should be 1) permitted; 2) supported by the * currently running stroke. */ void setWrapAroundModePermitted(bool value); /** * \return whether the wrap-around mode is permitted for this * image. If the wrap around mode is permitted and the * currently running stroke supports it, the mode will be * activated for all paint devices of the image. * * \see setWrapAroundMode */ bool wrapAroundModePermitted() const; /** * \return whether the wraparound mode is activated for all the * devices of the image. The mode is activated when both * factors are true: the user permitted it and the stroke * supports it */ bool wrapAroundModeActive() const; /** * \return current level of detail which is used when processing the image. * Current working zoom = 2 ^ (- currentLevelOfDetail()). Default value is * null, which means we work on the original image. */ int currentLevelOfDetail() const; /** * Notify KisImage which level of detail should be used in the * lod-mode. Setting the mode does not guarantee the LOD to be * used. It will be activated only when the stokes supports it. */ void setDesiredLevelOfDetail(int lod); /** * Relative position of the mirror axis center * 0,0 - topleft corner of the image * 1,1 - bottomright corner of the image */ QPointF mirrorAxesCenter() const; /** * Sets the relative position of the axes center * \see mirrorAxesCenter() for details */ void setMirrorAxesCenter(const QPointF &value) const; /** * Configure the image to allow masks on the root not (as reported by * root()->allowAsChild()). By default it is not allowed (because it * looks weird from GUI point of view) */ void setAllowMasksOnRootNode(bool value); /** * \see setAllowMasksOnRootNode() */ bool allowMasksOnRootNode() const; public Q_SLOTS: /** * Explicitly start regeneration of LoD planes of all the devices * in the image. This call should be performed when the user is idle, * just to make the quality of image updates better. */ void explicitRegenerateLevelOfDetail(); public: /** * Blocks usage of level of detail functionality. After this method * has been called, no new strokes will use LoD. */ void setLevelOfDetailBlocked(bool value); /** * \see setLevelOfDetailBlocked() */ bool levelOfDetailBlocked() const; KisImageAnimationInterface *animationInterface() const; /** * @brief setProofingConfiguration, this sets the image's proofing configuration, and signals * the proofingConfiguration has changed. * @param proofingConfig - the kis proofing config that will be used instead. */ void setProofingConfiguration(KisProofingConfigurationSP proofingConfig); /** * @brief proofingConfiguration * @return the proofing configuration of the image. */ KisProofingConfigurationSP proofingConfiguration() const; public Q_SLOTS: bool startIsolatedMode(KisNodeSP node); void stopIsolatedMode(); public: KisNodeSP isolatedModeRoot() const; Q_SIGNALS: /** * Emitted whenever an action has caused the image to be * recomposited. Parameter is the rect that has been recomposited. */ void sigImageUpdated(const QRect &); /** Emitted whenever the image has been modified, so that it doesn't match with the version saved on disk. */ void sigImageModified(); /** * The signal is emitted when the size of the image is changed. * \p oldStillPoint and \p newStillPoint give the receiver the * hint about how the new and old rect of the image correspond to * each other. They specify the point of the image around which * the conversion was done. This point will stay still on the * user's screen. That is the \p newStillPoint of the new image * will be painted at the same screen position, where \p * oldStillPoint of the old image was painted. * * \param oldStillPoint is a still point represented in *old* * image coordinates * * \param newStillPoint is a still point represented in *new* * image coordinates */ void sigSizeChanged(const QPointF &oldStillPoint, const QPointF &newStillPoint); void sigProfileChanged(const KoColorProfile * profile); void sigColorSpaceChanged(const KoColorSpace* cs); void sigResolutionChanged(double xRes, double yRes); void sigRequestNodeReselection(KisNodeSP activeNode, const KisNodeList &selectedNodes); /** * Inform the model that a node was changed */ void sigNodeChanged(KisNodeSP node); /** * Inform that the image is going to be deleted */ void sigAboutToBeDeleted(); /** * The signal is emitted right after a node has been connected * to the graph of the nodes. * * WARNING: you must not request any graph-related information * about the node being run in a not-scheduler thread. If you need * information about the parent/siblings of the node connect * with Qt::DirectConnection, get needed information and then * emit another Qt::AutoConnection signal to pass this information * to your thread. See details of the implementation * in KisDummiesfacadeBase. */ void sigNodeAddedAsync(KisNodeSP node); /** * This signal is emitted right before a node is going to removed * from the graph of the nodes. * * WARNING: you must not request any graph-related information * about the node being run in a not-scheduler thread. * * \see comment in sigNodeAddedAsync() */ void sigRemoveNodeAsync(KisNodeSP node); /** * Emitted when the root node of the image has changed. * It happens, e.g. when we flatten the image. When * this happens the receiver should reload information * about the image */ void sigLayersChangedAsync(); /** * Emitted when the UI has requested the undo of the last stroke's * operation. The point is, we cannot deal with the internals of * the stroke without its creator knowing about it (which most * probably cause a crash), so we just forward this request from * the UI to the creator of the stroke. * * If your tool supports undoing part of its work, just listen to * this signal and undo when it comes */ void sigUndoDuringStrokeRequested(); /** * Emitted when the UI has requested the cancellation of * the stroke. The point is, we cannot cancel the stroke * without its creator knowing about it (which most probably * cause a crash), so we just forward this request from the UI * to the creator of the stroke. * * If your tool supports cancelling of its work in the middle * of operation, just listen to this signal and cancel * the stroke when it comes */ void sigStrokeCancellationRequested(); /** * Emitted when the image decides that the stroke should better * be ended. The point is, we cannot just end the stroke * without its creator knowing about it (which most probably * cause a crash), so we just forward this request from the UI * to the creator of the stroke. * * If your tool supports long strokes that may involve multiple * mouse actions in one stroke, just listen to this signal and * end the stroke when it comes. */ void sigStrokeEndRequested(); /** * Same as sigStrokeEndRequested() but is not emitted when the active node * is changed. */ void sigStrokeEndRequestedActiveNodeFiltered(); /** * Emitted when the isolated mode status has changed. * * Can be used by the receivers to catch a fact of forcefully * stopping the isolated mode by the image when some complex * action was requested */ void sigIsolatedModeChanged(); /** * Emitted when one or more nodes changed the collapsed state * */ void sigNodeCollapsedChanged(); /** * Emitted when the proofing configuration of the image is being changed. * */ void sigProofingConfigChanged(); /** * Internal signal for asynchronously requesting isolated mode to stop. Don't use it * outside KisImage, use sigIsolatedModeChanged() instead. */ void sigInternalStopIsolatedModeRequested(); public Q_SLOTS: KisCompositeProgressProxy* compositeProgressProxy(); bool isIdle(bool allowLocked = false); /** * @brief Wait until all the queued background jobs are completed and lock the image. * * KisImage object has a local scheduler that executes long-running image * rendering/modifying jobs (we call them "strokes") in a background. Basically, * one should either access the image from the scope of such jobs (strokes) or * just lock the image before using. * * Calling barrierLock() will wait until all the queued operations are finished * and lock the image, so you can start accessing it in a safe way. * * @p readOnly tells the image if the caller is going to modify the image during * holding the lock. Locking with non-readOnly access will reset all * the internal caches of the image (lod-planes) when the lock status * will be lifted. */ void barrierLock(bool readOnly = false); /** * @brief Tries to lock the image without waiting for the jobs to finish * * Same as barrierLock(), but doesn't block execution of the calling thread * until all the background jobs are finished. Instead, in case of presence of * unfinished jobs in the queue, it just returns false * * @return whether the lock has been acquired * @see barrierLock */ bool tryBarrierLock(bool readOnly = false); /** * Wait for all the internal image jobs to complete and return without locking * the image. This function is handly for tests or other synchronous actions, * when one needs to wait for the result of his actions. */ void waitForDone(); KisStrokeId startStroke(KisStrokeStrategy *strokeStrategy) override; void addJob(KisStrokeId id, KisStrokeJobData *data) override; void endStroke(KisStrokeId id) override; bool cancelStroke(KisStrokeId id) override; /** * @brief blockUpdates block updating the image projection */ void blockUpdates() override; /** * @brief unblockUpdates unblock updating the image project. This * only restarts the scheduler and does not schedule a full refresh. */ void unblockUpdates() override; /** * Disables notification of the UI about the changes in the image. * This feature is used by KisProcessingApplicator. It is needed * when we change the size of the image. In this case, the whole * image will be reloaded into UI by sigSizeChanged(), so there is * no need to inform the UI about individual dirty rects. * - * The last call to enableUIUpdates() will return the list of udpates + * The last call to enableUIUpdates() will return the list of updates * that were requested while they were blocked. */ void disableUIUpdates() override; /** * Notify GUI about a bunch of updates planned. GUI is expected to wait * until all the updates are completed, and render them on screen only * in the very and of the batch. */ void notifyBatchUpdateStarted() override; /** * Notify GUI that batch update has been completed. Now GUI can start * showing all of them on screen. */ void notifyBatchUpdateEnded() override; /** * Notify GUI that rect \p rc is now prepared in the image and * GUI can read data from it. * * WARNING: GUI will read the data right in the handler of this * signal, so exclusive access to the area must be guaranteed * by the caller. */ void notifyUIUpdateCompleted(const QRect &rc) override; /** * \see disableUIUpdates */ QVector enableUIUpdates() override; /** * Disables the processing of all the setDirty() requests that * come to the image. The incoming requests are effectively * *dropped*. * * This feature is used by KisProcessingApplicator. For many cases * it provides its own updates interface, which recalculates the * whole subtree of nodes. But while we change any particular * node, it can ask for an update itself. This method is a way of * blocking such intermediate (and excessive) requests. * * NOTE: this is a convenience function for setProjectionUpdatesFilter() * that installs a predefined filter that eats everything. Please * note that these calls are *not* recursive */ void disableDirtyRequests() override; /** * \see disableDirtyRequests() */ void enableDirtyRequests() override; /** * Installs a filter object that will filter all the incoming projection update * requests. If the filter return true, the incoming update is dropped. * * NOTE: you cannot set filters recursively! */ void setProjectionUpdatesFilter(KisProjectionUpdatesFilterSP filter) override; /** * \see setProjectionUpdatesFilter() */ KisProjectionUpdatesFilterSP projectionUpdatesFilter() const override; void refreshGraphAsync(KisNodeSP root = KisNodeSP()) override; void refreshGraphAsync(KisNodeSP root, const QRect &rc) override; void refreshGraphAsync(KisNodeSP root, const QRect &rc, const QRect &cropRect) override; /** * Triggers synchronous recomposition of the projection */ void refreshGraph(KisNodeSP root = KisNodeSP()); void refreshGraph(KisNodeSP root, const QRect& rc, const QRect &cropRect); void initialRefreshGraph(); /** * Initiate a stack regeneration skipping the recalculation of the * filthy node's projection. * * Works exactly as pseudoFilthy->setDirty() with the only * exception that pseudoFilthy::updateProjection() will not be * called. That is used by KisRecalculateTransformMaskJob to avoid * cyclic dependencies. */ void requestProjectionUpdateNoFilthy(KisNodeSP pseudoFilthy, const QRect &rc, const QRect &cropRect); /** * Adds a spontaneous job to the updates queue. * * A spontaneous job may do some trivial tasks in the background, * like updating the outline of selection or purging unused tiles * from the existing paint devices. */ void addSpontaneousJob(KisSpontaneousJob *spontaneousJob); /** * \return true if there are some updates in the updates queue * Please note, that is doesn't guarantee that there are no updates * running in in the updater context at the very moment. To guarantee that * there are no updates left at all, please use barrier jobs instead. */ bool hasUpdatesRunning() const override; /** * This method is called by the UI (*not* by the creator of the * stroke) when it thinks the current stroke should undo its last * action, for example, when the user presses Ctrl+Z while some * stroke is active. * * If the creator of the stroke supports undoing of intermediate * actions, it will be notified about this request and can undo * its last action. */ void requestUndoDuringStroke(); /** * This method is called by the UI (*not* by the creator of the * stroke) when it thinks current stroke should be cancelled. If * there is a running stroke that has already been detached from * its creator (ended or cancelled), it will be forcefully * cancelled and reverted. If there is an open stroke present, and * if its creator supports cancelling, it will be notified about * the request and the stroke will be cancelled */ void requestStrokeCancellation(); /** * This method requests the last stroke executed on the image to become undone. * If the stroke is not ended, or if all the Lod0 strokes are completed, the method * returns UNDO_FAIL. If the last Lod0 is going to be finished soon, then UNDO_WAIT * is returned and the caller should just wait for its completion and call global undo * instead. UNDO_OK means one unfinished stroke has been undone. */ UndoResult tryUndoUnfinishedLod0Stroke(); /** * This method is called when image or some other part of Krita * (*not* the creator of the stroke) decides that the stroke * should be ended. If the creator of the stroke supports it, it * will be notified and the stroke will be cancelled */ void requestStrokeEnd(); /** * Same as requestStrokeEnd() but is called by view manager when * the current node is changed. Use to distinguish * sigStrokeEndRequested() and * sigStrokeEndRequestedActiveNodeFiltered() which are used by * KisNodeJugglerCompressed */ void requestStrokeEndActiveNode(); private: KisImage(const KisImage& rhs, KisUndoStore *undoStore, bool exactCopy); KisImage& operator=(const KisImage& rhs); void emitSizeChanged(); void resizeImageImpl(const QRect& newRect, bool cropLayers); void rotateImpl(const KUndo2MagicString &actionName, KisNodeSP rootNode, double radians, bool resizeImage, KisSelectionSP selection); void shearImpl(const KUndo2MagicString &actionName, KisNodeSP rootNode, bool resizeImage, double angleX, double angleY, KisSelectionSP selection); void safeRemoveTwoNodes(KisNodeSP node1, KisNodeSP node2); void refreshHiddenArea(KisNodeSP rootNode, const QRect &preparedArea); void requestProjectionUpdateImpl(KisNode *node, const QVector &rects, const QRect &cropRect); friend class KisImageResizeCommand; void setSize(const QSize& size); friend class KisImageSetProjectionColorSpaceCommand; void setProjectionColorSpace(const KoColorSpace * colorSpace); friend class KisDeselectGlobalSelectionCommand; friend class KisReselectGlobalSelectionCommand; friend class KisSetGlobalSelectionCommand; friend class KisImageTest; friend class Document; // For libkis /** * Replaces the current global selection with globalSelection. If * \p globalSelection is empty, removes the selection object, so that * \ref globalSelection() will return 0 after that. */ void setGlobalSelection(KisSelectionSP globalSelection); /** * Deselects current global selection. * \ref globalSelection() will return 0 after that. */ void deselectGlobalSelection(); /** * Reselects current deselected selection * * \see deselectGlobalSelection() */ void reselectGlobalSelection(); private: class KisImagePrivate; KisImagePrivate * m_d; }; #endif // KIS_IMAGE_H_ diff --git a/libs/image/kis_layer.cc b/libs/image/kis_layer.cc index 6713137a8e..1d573758cc 100644 --- a/libs/image/kis_layer.cc +++ b/libs/image/kis_layer.cc @@ -1,990 +1,991 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2005 C. Boemann * Copyright (c) 2009 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_layer.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_debug.h" #include "kis_image.h" #include "kis_painter.h" #include "kis_mask.h" #include "kis_effect_mask.h" #include "kis_selection_mask.h" #include "kis_meta_data_store.h" #include "kis_selection.h" #include "kis_paint_layer.h" #include "kis_raster_keyframe_channel.h" #include "kis_clone_layer.h" #include "kis_psd_layer_style.h" #include "kis_layer_projection_plane.h" #include "layerstyles/kis_layer_style_projection_plane.h" #include "krita_utils.h" #include "kis_layer_properties_icons.h" #include "kis_layer_utils.h" #include "kis_projection_leaf.h" #include "KisSafeNodeProjectionStore.h" class KisCloneLayersList { public: void addClone(KisCloneLayerWSP cloneLayer) { m_clonesList.append(cloneLayer); } void removeClone(KisCloneLayerWSP cloneLayer) { m_clonesList.removeOne(cloneLayer); } void setDirty(const QRect &rect) { Q_FOREACH (KisCloneLayerSP clone, m_clonesList) { if (clone) { clone->setDirtyOriginal(rect); } } } const QList registeredClones() const { return m_clonesList; } bool hasClones() const { return !m_clonesList.isEmpty(); } private: QList m_clonesList; }; class KisLayerMasksCache { public: KisLayerMasksCache(KisLayer *parent) : m_parent(parent) { } KisSelectionMaskSP selectionMask() { QReadLocker readLock(&m_lock); if (!m_isSelectionMaskValid) { readLock.unlock(); QWriteLocker writeLock(&m_lock); if (!m_isSelectionMaskValid) { KoProperties properties; properties.setProperty("active", true); properties.setProperty("visible", true); QList masks = m_parent->childNodes(QStringList("KisSelectionMask"), properties); // return the first visible mask Q_FOREACH (KisNodeSP mask, masks) { if (mask) { m_selectionMask = dynamic_cast(mask.data()); break; } } m_isSelectionMaskValid = true; } // return under write lock return m_selectionMask; } // return under read lock return m_selectionMask; } QList effectMasks() { QReadLocker readLock(&m_lock); if (!m_isEffectMasksValid) { readLock.unlock(); QWriteLocker writeLock(&m_lock); if (!m_isEffectMasksValid) { m_effectMasks = m_parent->searchEffectMasks(0); m_isEffectMasksValid = true; } // return under write lock return m_effectMasks; } // return under read lock return m_effectMasks; } void setDirty() { QWriteLocker l(&m_lock); m_isSelectionMaskValid = false; m_isEffectMasksValid = false; m_selectionMask = 0; m_effectMasks.clear(); } private: KisLayer *m_parent; QReadWriteLock m_lock; bool m_isSelectionMaskValid = false; bool m_isEffectMasksValid = false; KisSelectionMaskSP m_selectionMask; QList m_effectMasks; }; struct Q_DECL_HIDDEN KisLayer::Private { Private(KisLayer *q) : masksCache(q) { } QBitArray channelFlags; KisMetaData::Store* metaDataStore; KisCloneLayersList clonesList; KisPSDLayerStyleSP layerStyle; KisLayerStyleProjectionPlaneSP layerStyleProjectionPlane; - KisAbstractProjectionPlaneSP projectionPlane; + KisLayerProjectionPlaneSP projectionPlane; KisSafeNodeProjectionStoreSP safeProjection; KisLayerMasksCache masksCache; }; KisLayer::KisLayer(KisImageWSP image, const QString &name, quint8 opacity) : KisNode(image) , m_d(new Private(this)) { setName(name); setOpacity(opacity); m_d->metaDataStore = new KisMetaData::Store(); m_d->projectionPlane = toQShared(new KisLayerProjectionPlane(this)); m_d->safeProjection = new KisSafeNodeProjectionStore(); m_d->safeProjection->setImage(image); } KisLayer::KisLayer(const KisLayer& rhs) : KisNode(rhs) , m_d(new Private(this)) { if (this != &rhs) { m_d->metaDataStore = new KisMetaData::Store(*rhs.m_d->metaDataStore); m_d->channelFlags = rhs.m_d->channelFlags; setName(rhs.name()); m_d->projectionPlane = toQShared(new KisLayerProjectionPlane(this)); m_d->safeProjection = new KisSafeNodeProjectionStore(*rhs.m_d->safeProjection); m_d->safeProjection->setImage(image()); if (rhs.m_d->layerStyle) { m_d->layerStyle = rhs.m_d->layerStyle->clone(); if (rhs.m_d->layerStyleProjectionPlane) { m_d->layerStyleProjectionPlane = toQShared( new KisLayerStyleProjectionPlane(*rhs.m_d->layerStyleProjectionPlane, this, m_d->layerStyle)); } } } } KisLayer::~KisLayer() { delete m_d->metaDataStore; delete m_d; } const KoColorSpace * KisLayer::colorSpace() const { KisImageSP image = this->image(); if (!image) { return nullptr; } return image->colorSpace(); } const KoCompositeOp * KisLayer::compositeOp() const { /** * FIXME: This function duplicates the same function from * KisMask. We can't move it to KisBaseNode as it doesn't * know anything about parent() method of KisNode * Please think it over... */ KisNodeSP parentNode = parent(); if (!parentNode) return 0; if (!parentNode->colorSpace()) return 0; const KoCompositeOp* op = parentNode->colorSpace()->compositeOp(compositeOpId()); return op ? op : parentNode->colorSpace()->compositeOp(COMPOSITE_OVER); } KisPSDLayerStyleSP KisLayer::layerStyle() const { return m_d->layerStyle; } void KisLayer::setLayerStyle(KisPSDLayerStyleSP layerStyle) { if (layerStyle) { m_d->layerStyle = layerStyle; KisLayerStyleProjectionPlaneSP plane = !layerStyle->isEmpty() ? KisLayerStyleProjectionPlaneSP(new KisLayerStyleProjectionPlane(this)) : KisLayerStyleProjectionPlaneSP(0); m_d->layerStyleProjectionPlane = plane; } else { m_d->layerStyleProjectionPlane.clear(); m_d->layerStyle.clear(); } } KisBaseNode::PropertyList KisLayer::sectionModelProperties() const { KisBaseNode::PropertyList l = KisBaseNode::sectionModelProperties(); l << KisBaseNode::Property(KoID("opacity", i18n("Opacity")), i18n("%1%", percentOpacity())); const KoCompositeOp * compositeOp = this->compositeOp(); if (compositeOp) { l << KisBaseNode::Property(KoID("compositeop", i18n("Blending Mode")), compositeOp->description()); } if (m_d->layerStyle && !m_d->layerStyle->isEmpty()) { l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::layerStyle, m_d->layerStyle->isEnabled()); } l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::inheritAlpha, alphaChannelDisabled()); return l; } void KisLayer::setSectionModelProperties(const KisBaseNode::PropertyList &properties) { KisBaseNode::setSectionModelProperties(properties); Q_FOREACH (const KisBaseNode::Property &property, properties) { if (property.id == KisLayerPropertiesIcons::inheritAlpha.id()) { disableAlphaChannel(property.state.toBool()); } if (property.id == KisLayerPropertiesIcons::layerStyle.id()) { if (m_d->layerStyle && m_d->layerStyle->isEnabled() != property.state.toBool()) { m_d->layerStyle->setEnabled(property.state.toBool()); baseNodeChangedCallback(); baseNodeInvalidateAllFramesCallback(); } } } } void KisLayer::disableAlphaChannel(bool disable) { QBitArray newChannelFlags = m_d->channelFlags; if(newChannelFlags.isEmpty()) newChannelFlags = colorSpace()->channelFlags(true, true); if(disable) newChannelFlags &= colorSpace()->channelFlags(true, false); else newChannelFlags |= colorSpace()->channelFlags(false, true); setChannelFlags(newChannelFlags); } bool KisLayer::alphaChannelDisabled() const { QBitArray flags = colorSpace()->channelFlags(false, true) & m_d->channelFlags; return flags.count(true) == 0 && !m_d->channelFlags.isEmpty(); } void KisLayer::setChannelFlags(const QBitArray & channelFlags) { Q_ASSERT(channelFlags.isEmpty() ||((quint32)channelFlags.count() == colorSpace()->channelCount())); if (KritaUtils::compareChannelFlags(channelFlags, this->channelFlags())) { return; } if (!channelFlags.isEmpty() && channelFlags == QBitArray(channelFlags.size(), true)) { m_d->channelFlags.clear(); } else { m_d->channelFlags = channelFlags; } baseNodeChangedCallback(); baseNodeInvalidateAllFramesCallback(); } QBitArray & KisLayer::channelFlags() const { return m_d->channelFlags; } bool KisLayer::temporary() const { return nodeProperties().boolProperty("temporary", false); } void KisLayer::setTemporary(bool t) { setNodeProperty("temporary", t); } void KisLayer::setImage(KisImageWSP image) { // we own the projection device, so we should take care about it KisPaintDeviceSP projection = this->projection(); if (projection && projection != original()) { projection->setDefaultBounds(new KisDefaultBounds(image)); } m_d->safeProjection->setImage(image); KisNode::setImage(image); } bool KisLayer::canMergeAndKeepBlendOptions(KisLayerSP otherLayer) { return this->compositeOpId() == otherLayer->compositeOpId() && this->opacity() == otherLayer->opacity() && this->channelFlags() == otherLayer->channelFlags() && !this->layerStyle() && !otherLayer->layerStyle() && (this->colorSpace() == otherLayer->colorSpace() || *this->colorSpace() == *otherLayer->colorSpace()); } KisLayerSP KisLayer::createMergedLayerTemplate(KisLayerSP prevLayer) { const bool keepBlendingOptions = canMergeAndKeepBlendOptions(prevLayer); KisLayerSP newLayer = new KisPaintLayer(image(), prevLayer->name(), OPACITY_OPAQUE_U8); if (keepBlendingOptions) { newLayer->setCompositeOpId(compositeOpId()); newLayer->setOpacity(opacity()); newLayer->setChannelFlags(channelFlags()); } return newLayer; } void KisLayer::fillMergedLayerTemplate(KisLayerSP dstLayer, KisLayerSP prevLayer) { const bool keepBlendingOptions = canMergeAndKeepBlendOptions(prevLayer); QRect layerProjectionExtent = this->projection()->extent(); QRect prevLayerProjectionExtent = prevLayer->projection()->extent(); bool alphaDisabled = this->alphaChannelDisabled(); bool prevAlphaDisabled = prevLayer->alphaChannelDisabled(); KisPaintDeviceSP mergedDevice = dstLayer->paintDevice(); if (!keepBlendingOptions) { KisPainter gc(mergedDevice); KisImageSP imageSP = image().toStrongRef(); if (!imageSP) { return; } //Copy the pixels of previous layer with their actual alpha value prevLayer->disableAlphaChannel(false); prevLayer->projectionPlane()->apply(&gc, prevLayerProjectionExtent | imageSP->bounds()); //Restore the previous prevLayer disableAlpha status for correct undo/redo prevLayer->disableAlphaChannel(prevAlphaDisabled); //Paint the pixels of the current layer, using their actual alpha value if (alphaDisabled == prevAlphaDisabled) { this->disableAlphaChannel(false); } this->projectionPlane()->apply(&gc, layerProjectionExtent | imageSP->bounds()); //Restore the layer disableAlpha status for correct undo/redo this->disableAlphaChannel(alphaDisabled); } else { //Copy prevLayer KisPaintDeviceSP srcDev = prevLayer->projection(); mergedDevice->makeCloneFrom(srcDev, srcDev->extent()); //Paint layer on the copy KisPainter gc(mergedDevice); gc.bitBlt(layerProjectionExtent.topLeft(), this->projection(), layerProjectionExtent); } } void KisLayer::registerClone(KisCloneLayerWSP clone) { m_d->clonesList.addClone(clone); } void KisLayer::unregisterClone(KisCloneLayerWSP clone) { m_d->clonesList.removeClone(clone); } const QList KisLayer::registeredClones() const { return m_d->clonesList.registeredClones(); } bool KisLayer::hasClones() const { return m_d->clonesList.hasClones(); } void KisLayer::updateClones(const QRect &rect) { m_d->clonesList.setDirty(rect); } void KisLayer::notifyChildMaskChanged() { m_d->masksCache.setDirty(); } KisSelectionMaskSP KisLayer::selectionMask() const { return m_d->masksCache.selectionMask(); } KisSelectionSP KisLayer::selection() const { KisSelectionMaskSP mask = selectionMask(); if (mask) { return mask->selection(); } KisImageSP image = this->image(); if (image) { return image->globalSelection(); } return KisSelectionSP(); } /////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////// QList KisLayer::effectMasks() const { return m_d->masksCache.effectMasks(); } QList KisLayer::effectMasks(KisNodeSP lastNode) const { if (lastNode.isNull()) { return effectMasks(); } else { // happens rarely. return searchEffectMasks(lastNode); } } QList KisLayer::searchEffectMasks(KisNodeSP lastNode) const { QList masks; KIS_SAFE_ASSERT_RECOVER_NOOP(projectionLeaf()); KisProjectionLeafSP child = projectionLeaf()->firstChild(); while (child) { if (child->node() == lastNode) break; KIS_SAFE_ASSERT_RECOVER_NOOP(child); KIS_SAFE_ASSERT_RECOVER_NOOP(child->node()); if (child->visible()) { KisEffectMaskSP mask = dynamic_cast(const_cast(child->node().data())); if (mask) { masks.append(mask); } } child = child->nextSibling(); } return masks; } bool KisLayer::hasEffectMasks() const { return !m_d->masksCache.effectMasks().isEmpty(); } QRect KisLayer::masksChangeRect(const QList &masks, const QRect &requestedRect, bool &rectVariesFlag) const { rectVariesFlag = false; QRect prevChangeRect = requestedRect; /** * We set default value of the change rect for the case * when there is no mask at all */ QRect changeRect = requestedRect; Q_FOREACH (const KisEffectMaskSP& mask, masks) { changeRect = mask->changeRect(prevChangeRect); if (changeRect != prevChangeRect) rectVariesFlag = true; prevChangeRect = changeRect; } return changeRect; } QRect KisLayer::masksNeedRect(const QList &masks, const QRect &changeRect, QStack &applyRects, bool &rectVariesFlag) const { rectVariesFlag = false; QRect prevNeedRect = changeRect; QRect needRect; for (qint32 i = masks.size() - 1; i >= 0; i--) { applyRects.push(prevNeedRect); needRect = masks[i]->needRect(prevNeedRect); if (prevNeedRect != needRect) rectVariesFlag = true; prevNeedRect = needRect; } return needRect; } KisNode::PositionToFilthy calculatePositionToFilthy(KisNodeSP nodeInQuestion, KisNodeSP filthy, KisNodeSP parent) { if (parent == filthy || parent != filthy->parent()) { return KisNode::N_ABOVE_FILTHY; } if (nodeInQuestion == filthy) { return KisNode::N_FILTHY; } KisNodeSP node = nodeInQuestion->prevSibling(); while (node) { if (node == filthy) { return KisNode::N_ABOVE_FILTHY; } node = node->prevSibling(); } return KisNode::N_BELOW_FILTHY; } QRect KisLayer::applyMasks(const KisPaintDeviceSP source, KisPaintDeviceSP destination, const QRect &requestedRect, KisNodeSP filthyNode, KisNodeSP lastNode) const { Q_ASSERT(source); Q_ASSERT(destination); QList masks = effectMasks(lastNode); QRect changeRect; QRect needRect; if (masks.isEmpty()) { changeRect = requestedRect; if (source != destination) { copyOriginalToProjection(source, destination, requestedRect); } } else { QStack applyRects; bool changeRectVaries; bool needRectVaries; /** * FIXME: Assume that varying of the changeRect has already * been taken into account while preparing walkers */ changeRectVaries = false; changeRect = requestedRect; //changeRect = masksChangeRect(masks, requestedRect, // changeRectVaries); needRect = masksNeedRect(masks, changeRect, applyRects, needRectVaries); if (!changeRectVaries && !needRectVaries) { /** * A bit of optimization: * All filters will read/write exactly from/to the requested * rect so we needn't create temporary paint device, * just apply it onto destination */ Q_ASSERT(needRect == requestedRect); if (source != destination) { copyOriginalToProjection(source, destination, needRect); } Q_FOREACH (const KisEffectMaskSP& mask, masks) { const QRect maskApplyRect = applyRects.pop(); const QRect maskNeedRect = applyRects.isEmpty() ? needRect : applyRects.top(); PositionToFilthy maskPosition = calculatePositionToFilthy(mask, filthyNode, const_cast(this)); mask->apply(destination, maskApplyRect, maskNeedRect, maskPosition); } Q_ASSERT(applyRects.isEmpty()); } else { /** * We can't eliminate additional copy-op * as filters' behaviour may be quite insane here, * so let them work on their own paintDevice =) */ KisPaintDeviceSP tempDevice = new KisPaintDevice(colorSpace()); tempDevice->prepareClone(source); copyOriginalToProjection(source, tempDevice, needRect); QRect maskApplyRect = applyRects.pop(); QRect maskNeedRect = needRect; Q_FOREACH (const KisEffectMaskSP& mask, masks) { PositionToFilthy maskPosition = calculatePositionToFilthy(mask, filthyNode, const_cast(this)); mask->apply(tempDevice, maskApplyRect, maskNeedRect, maskPosition); if (!applyRects.isEmpty()) { maskNeedRect = maskApplyRect; maskApplyRect = applyRects.pop(); } } Q_ASSERT(applyRects.isEmpty()); KisPainter::copyAreaOptimized(changeRect.topLeft(), tempDevice, destination, changeRect); } } return changeRect; } QRect KisLayer::updateProjection(const QRect& rect, KisNodeSP filthyNode) { QRect updatedRect = rect; KisPaintDeviceSP originalDevice = original(); if (!rect.isValid() || (!visible() && !hasClones()) || !originalDevice) return QRect(); if (!needProjection() && !hasEffectMasks()) { m_d->safeProjection->releaseDevice(); } else { if (!updatedRect.isEmpty()) { KisPaintDeviceSP projection = m_d->safeProjection->getDeviceLazy(originalDevice); updatedRect = applyMasks(originalDevice, projection, updatedRect, filthyNode, 0); } } return updatedRect; } QRect KisLayer::partialChangeRect(KisNodeSP lastNode, const QRect& rect) { bool changeRectVaries = false; QRect changeRect = outgoingChangeRect(rect); changeRect = masksChangeRect(effectMasks(lastNode), changeRect, changeRectVaries); return changeRect; } /** * \p rect is a dirty rect in layer's original() coordinates! */ void KisLayer::buildProjectionUpToNode(KisPaintDeviceSP projection, KisNodeSP lastNode, const QRect& rect) { QRect changeRect = partialChangeRect(lastNode, rect); KisPaintDeviceSP originalDevice = original(); KIS_ASSERT_RECOVER_RETURN(needProjection() || hasEffectMasks()); if (!changeRect.isEmpty()) { applyMasks(originalDevice, projection, changeRect, this, lastNode); } } bool KisLayer::needProjection() const { return false; } void KisLayer::copyOriginalToProjection(const KisPaintDeviceSP original, KisPaintDeviceSP projection, const QRect& rect) const { KisPainter::copyAreaOptimized(rect.topLeft(), original, projection, rect); } KisAbstractProjectionPlaneSP KisLayer::projectionPlane() const { return m_d->layerStyleProjectionPlane ? - KisAbstractProjectionPlaneSP(m_d->layerStyleProjectionPlane) : m_d->projectionPlane; + KisAbstractProjectionPlaneSP(m_d->layerStyleProjectionPlane) : + KisAbstractProjectionPlaneSP(m_d->projectionPlane); } -KisAbstractProjectionPlaneSP KisLayer::internalProjectionPlane() const +KisLayerProjectionPlaneSP KisLayer::internalProjectionPlane() const { return m_d->projectionPlane; } KisPaintDeviceSP KisLayer::projection() const { KisPaintDeviceSP originalDevice = original(); return needProjection() || hasEffectMasks() ? m_d->safeProjection->getDeviceLazy(originalDevice) : originalDevice; } QRect KisLayer::changeRect(const QRect &rect, PositionToFilthy pos) const { QRect changeRect = rect; changeRect = incomingChangeRect(changeRect); if(pos == KisNode::N_FILTHY) { QRect projectionToBeUpdated = projection()->exactBoundsAmortized() & changeRect; bool changeRectVaries; changeRect = outgoingChangeRect(changeRect); changeRect = masksChangeRect(effectMasks(), changeRect, changeRectVaries); /** * If the projection contains some dirty areas we should also * add them to the change rect, because they might have * changed. E.g. when a visibility of the mask has chnaged * while the parent layer was invinisble. */ if (!projectionToBeUpdated.isEmpty() && !changeRect.contains(projectionToBeUpdated)) { changeRect |= projectionToBeUpdated; } } // TODO: string comparizon: optimize! if (pos != KisNode::N_FILTHY && pos != KisNode::N_FILTHY_PROJECTION && compositeOpId() != COMPOSITE_COPY) { changeRect |= rect; } return changeRect; } void KisLayer::childNodeChanged(KisNodeSP changedChildNode) { if (dynamic_cast(changedChildNode.data())) { notifyChildMaskChanged(); } } QRect KisLayer::incomingChangeRect(const QRect &rect) const { return rect; } QRect KisLayer::outgoingChangeRect(const QRect &rect) const { return rect; } QRect KisLayer::needRectForOriginal(const QRect &rect) const { QRect needRect = rect; const QList masks = effectMasks(); if (!masks.isEmpty()) { QStack applyRects; bool needRectVaries; needRect = masksNeedRect(masks, rect, applyRects, needRectVaries); } return needRect; } QImage KisLayer::createThumbnail(qint32 w, qint32 h) { if (w == 0 || h == 0) { return QImage(); } KisPaintDeviceSP originalDevice = original(); return originalDevice ? originalDevice->createThumbnail(w, h, 1, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()) : QImage(); } QImage KisLayer::createThumbnailForFrame(qint32 w, qint32 h, int time) { if (w == 0 || h == 0) { return QImage(); } KisPaintDeviceSP originalDevice = original(); if (originalDevice ) { KisRasterKeyframeChannel *channel = originalDevice->keyframeChannel(); if (channel) { KisPaintDeviceSP targetDevice = new KisPaintDevice(colorSpace()); KisKeyframeSP keyframe = channel->activeKeyframeAt(time); channel->fetchFrame(keyframe, targetDevice); return targetDevice->createThumbnail(w, h, 1, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); } } return createThumbnail(w, h); } qint32 KisLayer::x() const { KisPaintDeviceSP originalDevice = original(); return originalDevice ? originalDevice->x() : 0; } qint32 KisLayer::y() const { KisPaintDeviceSP originalDevice = original(); return originalDevice ? originalDevice->y() : 0; } void KisLayer::setX(qint32 x) { KisPaintDeviceSP originalDevice = original(); if (originalDevice) originalDevice->setX(x); } void KisLayer::setY(qint32 y) { KisPaintDeviceSP originalDevice = original(); if (originalDevice) originalDevice->setY(y); } QRect KisLayer::layerExtentImpl(bool needExactBounds) const { QRect additionalMaskExtent = QRect(); QList effectMasks = this->effectMasks(); Q_FOREACH(KisEffectMaskSP mask, effectMasks) { additionalMaskExtent |= mask->nonDependentExtent(); } KisPaintDeviceSP originalDevice = original(); QRect layerExtent; if (originalDevice) { layerExtent = needExactBounds ? originalDevice->exactBounds() : originalDevice->extent(); } QRect additionalCompositeOpExtent; if (compositeOpId() == COMPOSITE_DESTINATION_IN || compositeOpId() == COMPOSITE_DESTINATION_ATOP) { additionalCompositeOpExtent = originalDevice->defaultBounds()->bounds(); } return layerExtent | additionalMaskExtent | additionalCompositeOpExtent; } QRect KisLayer::extent() const { return layerExtentImpl(false); } QRect KisLayer::exactBounds() const { return layerExtentImpl(true); } KisLayerSP KisLayer::parentLayer() const { return qobject_cast(parent().data()); } KisMetaData::Store* KisLayer::metaData() { return m_d->metaDataStore; } diff --git a/libs/image/kis_layer.h b/libs/image/kis_layer.h index bd05ff04f0..380e3375ae 100644 --- a/libs/image/kis_layer.h +++ b/libs/image/kis_layer.h @@ -1,417 +1,419 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2005 C. Boemann * Copyright (c) 2007 Boudewijn Rempt * Copyright (c) 2009 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_LAYER_H_ #define KIS_LAYER_H_ #include #include #include #include #include "kritaimage_export.h" #include "kis_base_node.h" #include "kis_types.h" #include "kis_node.h" #include "kis_psd_layer_style.h" template class QStack; class QBitArray; class KisCloneLayer; class KisPSDLayerStyle; class KisAbstractProjectionPlane; +class KisLayerProjectionPlane; +typedef QSharedPointer KisLayerProjectionPlaneSP; namespace KisMetaData { class Store; } /** * Abstract class that represents the concept of a Layer in Krita. This is not related * to the paint devices: this is merely an abstraction of how layers can be stacked and * rendered differently. * Regarding the previous-, first-, next- and lastChild() calls, first means that it the layer * is at the top of the group in the layerlist, using next will iterate to the bottom to last, * whereas previous will go up to first again. * * * TODO: Add a layer mode whereby the projection of the layer is used * as a clipping path? **/ class KRITAIMAGE_EXPORT KisLayer : public KisNode { Q_OBJECT public: /** * @param image is the pointer of the image or null * @param opacity is a value between OPACITY_TRANSPARENT_U8 and OPACITY_OPAQUE_U8 **/ KisLayer(KisImageWSP image, const QString &name, quint8 opacity); KisLayer(const KisLayer& rhs); ~KisLayer() override; /// returns the image's colorSpace or null, if there is no image const KoColorSpace * colorSpace() const override; /// returns the layer's composite op for the colorspace of the layer's parent. const KoCompositeOp * compositeOp() const override; KisPSDLayerStyleSP layerStyle() const; void setLayerStyle(KisPSDLayerStyleSP layerStyle); /** * \see a comment in KisNode::projectionPlane() */ KisAbstractProjectionPlaneSP projectionPlane() const override; /** * The projection plane representing the layer itself without any * styles or anything else. It is used by the layer styles projection * plane to stack up the planes. */ - virtual KisAbstractProjectionPlaneSP internalProjectionPlane() const; + virtual KisLayerProjectionPlaneSP internalProjectionPlane() const; QRect partialChangeRect(KisNodeSP lastNode, const QRect& rect); void buildProjectionUpToNode(KisPaintDeviceSP projection, KisNodeSP lastNode, const QRect& rect); virtual bool needProjection() const; /** * Return the fully rendered representation of this layer: its * data and its effect masks */ KisPaintDeviceSP projection() const override; /** * Return the layer data before the effect masks have had their go * at it. */ KisPaintDeviceSP original() const override = 0; /** * @return the selection associated with this layer, if there is * one. Otherwise, return 0; */ virtual KisSelectionMaskSP selectionMask() const; /** * @return the selection contained in the first KisSelectionMask associated * with this layer or the image, if either exists, otherwise, return 0. */ virtual KisSelectionSP selection() const; KisBaseNode::PropertyList sectionModelProperties() const override; void setSectionModelProperties(const KisBaseNode::PropertyList &properties) override; /** * set/unset the channel flag for the alpha channel of this layer */ void disableAlphaChannel(bool disable); /** * returns true if the channel flag for the alpha channel * of this layer is not set. * returns false otherwise. */ bool alphaChannelDisabled() const; /** * set the channelflags for this layer to the specified bit array. * The bit array must have exactly the same number of channels as * the colorspace this layer is in, or be empty, in which case all * channels are active. */ virtual void setChannelFlags(const QBitArray & channelFlags); /** * Return a bit array where each bit indicates whether a * particular channel is active or not. If the channelflags bit * array is empty, all channels are active. */ QBitArray & channelFlags() const; /** * Returns true if this layer is temporary: i.e., it should not * appear in the layerbox, even though it is temporarily in the * layer stack and taken into account on recomposition. */ bool temporary() const; /** * Set to true if this layer should not appear in the layerbox, * even though it is temporarily in the layer stack and taken into * account on recomposition. */ void setTemporary(bool t); /** * Set the image this layer belongs to. */ void setImage(KisImageWSP image) override; /** * Create and return a layer that is the result of merging * this with layer. * * This method is designed to be called only within KisImage::mergeLayerDown(). * * Decendands override this to create specific merged types when possible. * The KisLayer one creates a KisPaintLayerSP via a bitBlt, and can work on all layer types. * * Descendants that perform their own version do NOT call KisLayer::createMergedLayer */ virtual KisLayerSP createMergedLayerTemplate(KisLayerSP prevLayer); virtual void fillMergedLayerTemplate(KisLayerSP dstLayer, KisLayerSP prevLayer); /** * Clones should be informed about updates of the original * layer, so this is a way to register them */ void registerClone(KisCloneLayerWSP clone); /** * Deregisters the clone from the update list * * \see registerClone() */ void unregisterClone(KisCloneLayerWSP clone); /** * Return the list of the clones of this node. Be careful * with the list, because it is not thread safe. */ const QList registeredClones() const; /** * Returns whether we have a clone. * * Be careful with it. It is not thread safe to add/remove * clone while checking hasClones(). So there should be no updates. */ bool hasClones() const; /** * It is calles by the async merger after projection update is done */ void updateClones(const QRect &rect); /** * Informs this layers that its masks might have changed. */ void notifyChildMaskChanged(); public: qint32 x() const override; qint32 y() const override; void setX(qint32 x) override; void setY(qint32 y) override; /** * Returns an approximation of where the bounds * of actual data of this layer are */ QRect extent() const override; /** * Returns the exact bounds of where the actual data * of this layer resides */ QRect exactBounds() const override; QImage createThumbnail(qint32 w, qint32 h) override; QImage createThumbnailForFrame(qint32 w, qint32 h, int time) override; public: /** * Returns true if there are any effect masks present */ bool hasEffectMasks() const; /** * @return the list of effect masks */ QList effectMasks() const; /** * @return the list of effect masks up to a certain node */ QList effectMasks(KisNodeSP lastNode) const; /** * Get the group layer that contains this layer. */ KisLayerSP parentLayer() const; /** * @return the metadata object associated with this object. */ KisMetaData::Store* metaData(); protected: // override from KisNode QRect changeRect(const QRect &rect, PositionToFilthy pos = N_FILTHY) const override; void childNodeChanged(KisNodeSP changedChildNode) override; protected: /** * Ask the layer to assemble its data & apply all the effect masks * to it. */ QRect updateProjection(const QRect& rect, KisNodeSP filthyNode); /** * Layers can override this method to get some special behavior * when copying data from \p original to \p projection, e.g. blend * in indirect painting device. If you need to modify data * outside \p rect, please also override outgoingChangeRect() * method. */ virtual void copyOriginalToProjection(const KisPaintDeviceSP original, KisPaintDeviceSP projection, const QRect& rect) const; /** * For KisLayer classes change rect transformation consists of two * parts: incoming and outgoing. * * 1) incomingChangeRect(rect) chande rect transformation * performed by the transformations done basing on global * projection. It is performed in KisAsyncMerger + * KisUpdateOriginalVisitor classes. It happens before data * coming to KisLayer::original() therefore it is * 'incoming'. See KisAdjustmentLayer for example of usage. * * 2) outgoingChangeRect(rect) change rect transformation that * happens in KisLayer::copyOriginalToProjection(). It applies * *only* when the layer is 'filthy', that is was the cause of * the merge process. See KisCloneLayer for example of usage. * * The flow of changed areas can be illustrated in the * following way: * * 1. Current projection of size R1 is stored in KisAsyncMerger::m_currentProjection * | * | <-- KisUpdateOriginalVisitor writes data into layer's original() device. * | The changed area on KisLayer::original() is * | R2 = KisLayer::incomingChangeRect(R1) * | * 2. KisLayer::original() / changed rect: R2 * | * | <-- KisLayer::updateProjection() starts composing a layer * | It calls KisLayer::copyOriginalToProjection() which copies some area * | to a temporaty device. The temporary device now stores * | R3 = KisLayer::outgoingChangeRect(R2) * | * 3. Temporary device / changed rect: R3 * | * | <-- KisLayer::updateProjection() continues composing a layer. It merges a mask. * | R4 = KisMask::changeRect(R3) * | * 4. KisLayer::original() / changed rect: R4 * * So in the end rect R4 will be passed up to the next layers in the stack. */ virtual QRect incomingChangeRect(const QRect &rect) const; /** * \see incomingChangeRect() */ virtual QRect outgoingChangeRect(const QRect &rect) const; /** * Return need rect that should be prepared on original() * device of the layer to get \p rect on its projection. * * This method is used either for layers that can have other * layers as children (yes, KisGroupLayer, I'm looking at you!), * or for layers that depend on the lower nodes (it's you, * KisAdjustmentLayer!). * * These layers may have some filter masks that need a bit * more pixels than requested, therefore child nodes should do * a bit more work to prepare them. */ QRect needRectForOriginal(const QRect &rect) const; /** * @param rectVariesFlag (out param) a flag, showing whether * a rect varies from mask to mask * @return an area that should be updated because of * the change of @requestedRect of the layer */ QRect masksChangeRect(const QList &masks, const QRect &requestedRect, bool &rectVariesFlag) const; /** * Get needRects for all masks * @param changeRect requested rect to be updated on final * projection. Should be a return value * of @ref masksChangedRect() * @param applyRects (out param) a stack of the rects where filters * should be applied * @param rectVariesFlag (out param) a flag, showing whether * a rect varies from mask to mask * @return a needRect that should be prepared on the layer's * paintDevice for all masks to succeed */ QRect masksNeedRect(const QList &masks, const QRect &changeRect, QStack &applyRects, bool &rectVariesFlag) const; QRect applyMasks(const KisPaintDeviceSP source, KisPaintDeviceSP destination, const QRect &requestedRect, KisNodeSP filthyNode, KisNodeSP lastNode) const; bool canMergeAndKeepBlendOptions(KisLayerSP otherLayer); QList searchEffectMasks(KisNodeSP lastNode) const; private: friend class KisLayerMasksCache; friend class KisLayerProjectionPlane; friend class KisTransformMask; friend class KisLayerTest; private: QRect layerExtentImpl(bool exactBounds) const; private: struct Private; Private * const m_d; }; Q_DECLARE_METATYPE(KisLayerSP) #endif // KIS_LAYER_H_ diff --git a/libs/image/kis_layer_composition.cpp b/libs/image/kis_layer_composition.cpp index 0ada52ce30..4db05ff4b1 100644 --- a/libs/image/kis_layer_composition.cpp +++ b/libs/image/kis_layer_composition.cpp @@ -1,206 +1,211 @@ /* * 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 "kis_layer_composition.h" #include "kis_node_visitor.h" #include "kis_group_layer.h" #include "kis_adjustment_layer.h" #include "kis_external_layer_iface.h" #include "kis_paint_layer.h" #include "generator/kis_generator_layer.h" #include "kis_clone_layer.h" #include "kis_filter_mask.h" #include "kis_transform_mask.h" #include "kis_transparency_mask.h" #include "kis_selection_mask.h" #include "lazybrush/kis_colorize_mask.h" #include "kis_layer_utils.h" #include "kis_node_query_path.h" #include class KisCompositionVisitor : public KisNodeVisitor { public: enum Mode { STORE, APPLY }; - KisCompositionVisitor(KisLayerComposition* layerComposition, Mode mode) : m_layerComposition(layerComposition), m_mode(mode) + KisCompositionVisitor(KisLayerComposition* layerComposition, Mode mode) + : m_layerComposition(layerComposition) + , m_mode(mode) { } bool visit(KisNode* node) override { return process(node); } bool visit(KisGroupLayer* layer) override { bool result = visitAll(layer); if(layer == layer->image()->rootLayer()) { return result; } return result && process(layer); } bool visit(KisAdjustmentLayer* layer) override { return process(layer); } bool visit(KisPaintLayer* layer) override { return process(layer); } bool visit(KisExternalLayer* layer) override { return process(layer); } bool visit(KisGeneratorLayer* layer) override { return process(layer); } bool visit(KisCloneLayer* layer) override { return process(layer); } bool visit(KisFilterMask* mask) override { return process(mask); } bool visit(KisTransformMask* mask) override { return process(mask); } bool visit(KisTransparencyMask* mask) override { return process(mask); } bool visit(KisSelectionMask* mask) override { return process(mask); } bool visit(KisColorizeMask* mask) override { return process(mask); } bool process(KisNode* node) { if(m_mode == STORE) { m_layerComposition->m_visibilityMap[node->uuid()] = node->visible(); m_layerComposition->m_collapsedMap[node->uuid()] = node->collapsed(); } else { bool newState = false; if(m_layerComposition->m_visibilityMap.contains(node->uuid())) { newState = m_layerComposition->m_visibilityMap[node->uuid()]; } if(node->visible() != newState) { node->setVisible(m_layerComposition->m_visibilityMap[node->uuid()]); node->setDirty(); } if(m_layerComposition->m_collapsedMap.contains(node->uuid())) { node->setCollapsed(m_layerComposition->m_collapsedMap[node->uuid()]); } } return true; } private: KisLayerComposition* m_layerComposition; Mode m_mode; }; -KisLayerComposition::KisLayerComposition(KisImageWSP image, const QString& name): m_image(image), m_name(name), m_exportEnabled(true) +KisLayerComposition::KisLayerComposition(KisImageWSP image, const QString& name) + : m_image(image) + , m_name(name) + , m_exportEnabled(true) { } KisLayerComposition::~KisLayerComposition() { } KisLayerComposition::KisLayerComposition(const KisLayerComposition &rhs, KisImageWSP otherImage) : m_image(otherImage ? otherImage : rhs.m_image), m_name(rhs.m_name), m_exportEnabled(rhs.m_exportEnabled) { { auto it = rhs.m_visibilityMap.constBegin(); for (; it != rhs.m_visibilityMap.constEnd(); ++it) { QUuid nodeUuid = it.key(); KisNodeSP node = KisLayerUtils::findNodeByUuid(rhs.m_image->root(), nodeUuid); if (node) { KisNodeQueryPath path = KisNodeQueryPath::absolutePath(node); KisNodeSP newNode = path.queryUniqueNode(m_image); KIS_ASSERT_RECOVER(newNode) { continue; } m_visibilityMap.insert(newNode->uuid(), it.value()); } } } { auto it = rhs.m_collapsedMap.constBegin(); for (; it != rhs.m_collapsedMap.constEnd(); ++it) { QUuid nodeUuid = it.key(); KisNodeSP node = KisLayerUtils::findNodeByUuid(rhs.m_image->root(), nodeUuid); if (node) { KisNodeQueryPath path = KisNodeQueryPath::absolutePath(node); KisNodeSP newNode = path.queryUniqueNode(m_image); KIS_ASSERT_RECOVER(newNode) { continue; } m_collapsedMap.insert(newNode->uuid(), it.value()); } } } } void KisLayerComposition::setName(const QString& name) { m_name = name; } QString KisLayerComposition::name() { return m_name; } void KisLayerComposition::store() { if(m_image.isNull()) { return; } KisCompositionVisitor visitor(this, KisCompositionVisitor::STORE); m_image->rootLayer()->accept(visitor); } void KisLayerComposition::apply() { - if(m_image.isNull()) { + if (m_image.isNull()) { return; } KisCompositionVisitor visitor(this, KisCompositionVisitor::APPLY); m_image->rootLayer()->accept(visitor); } void KisLayerComposition::setExportEnabled ( bool enabled ) { m_exportEnabled = enabled; } bool KisLayerComposition::isExportEnabled() { return m_exportEnabled; } void KisLayerComposition::setVisible(QUuid id, bool visible) { m_visibilityMap[id] = visible; } void KisLayerComposition::setCollapsed ( QUuid id, bool collapsed ) { m_collapsedMap[id] = collapsed; } void KisLayerComposition::save(QDomDocument& doc, QDomElement& element) { QDomElement compositionElement = doc.createElement("composition"); compositionElement.setAttribute("name", m_name); compositionElement.setAttribute("exportEnabled", m_exportEnabled); QMapIterator iter(m_visibilityMap); while (iter.hasNext()) { iter.next(); QDomElement valueElement = doc.createElement("value"); valueElement.setAttribute("uuid", iter.key().toString()); valueElement.setAttribute("visible", iter.value()); dbgKrita << "contains" << m_collapsedMap.contains(iter.key()); if (m_collapsedMap.contains(iter.key())) { dbgKrita << "colapsed :" << m_collapsedMap[iter.key()]; valueElement.setAttribute("collapsed", m_collapsedMap[iter.key()]); } compositionElement.appendChild(valueElement); } element.appendChild(compositionElement); } diff --git a/libs/image/kis_layer_projection_plane.cpp b/libs/image/kis_layer_projection_plane.cpp index 534f6334dc..0ee62c63e5 100644 --- a/libs/image/kis_layer_projection_plane.cpp +++ b/libs/image/kis_layer_projection_plane.cpp @@ -1,126 +1,158 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_layer_projection_plane.h" #include #include #include #include #include "kis_painter.h" #include "kis_projection_leaf.h" +#include "kis_cached_paint_device.h" +#include "kis_sequential_iterator.h" struct KisLayerProjectionPlane::Private { KisLayer *layer; + KisCachedPaintDevice cachedDevice; }; KisLayerProjectionPlane::KisLayerProjectionPlane(KisLayer *layer) : m_d(new Private) { m_d->layer = layer; } KisLayerProjectionPlane::~KisLayerProjectionPlane() { } QRect KisLayerProjectionPlane::recalculate(const QRect& rect, KisNodeSP filthyNode) { return m_d->layer->updateProjection(rect, filthyNode); } -void KisLayerProjectionPlane::apply(KisPainter *painter, const QRect &rect) +void KisLayerProjectionPlane::applyImpl(KisPainter *painter, const QRect &rect, bool maxOutAlpha) { KisPaintDeviceSP device = m_d->layer->projection(); if (!device) return; QRect needRect = rect; if (m_d->layer->compositeOpId() != COMPOSITE_COPY && m_d->layer->compositeOpId() != COMPOSITE_DESTINATION_IN && m_d->layer->compositeOpId() != COMPOSITE_DESTINATION_ATOP) { needRect &= device->extent(); } if(needRect.isEmpty()) return; QBitArray channelFlags = m_d->layer->projectionLeaf()->channelFlags(); // if the color spaces don't match we will have a problem with the channel flags // because the channel flags from the source layer doesn't match with the colorspace of the projection device // this leads to the situation that the wrong channels will be enabled/disabled const KoColorSpace* srcCS = device->colorSpace(); const KoColorSpace* dstCS = painter->device()->colorSpace(); if (!channelFlags.isEmpty() && srcCS != dstCS) { bool alphaFlagIsSet = (srcCS->channelFlags(false,true) & channelFlags) == srcCS->channelFlags(false,true); bool allColorFlagsAreSet = (srcCS->channelFlags(true,false) & channelFlags) == srcCS->channelFlags(true,false); bool allColorFlagsAreUnset = (srcCS->channelFlags(true,false) & channelFlags).count(true) == 0; if(allColorFlagsAreSet) { channelFlags = dstCS->channelFlags(true, alphaFlagIsSet); } else if(allColorFlagsAreUnset) { channelFlags = dstCS->channelFlags(false, alphaFlagIsSet); } else { //TODO: convert the cannel flags properly // for now just the alpha channel bit is copied and the other channels are left alone for (quint32 i=0; i < dstCS->channelCount(); ++i) { if (dstCS->channels()[i]->channelType() == KoChannelInfo::ALPHA) { channelFlags.setBit(i, alphaFlagIsSet); break; } } } } + if (maxOutAlpha) { + KisPaintDeviceSP tmp = m_d->cachedDevice.getDevice(device); + tmp->makeCloneFromRough(device, needRect); + const KoColorSpace *cs = tmp->colorSpace(); + + KisSequentialIterator it(tmp, needRect); + int numConseqPixels = it.nConseqPixels(); + while (it.nextPixels(numConseqPixels)) { + numConseqPixels = it.nConseqPixels(); + cs->setOpacity(it.rawData(), quint8(255), numConseqPixels); + } + + device = tmp; + } + painter->setChannelFlags(channelFlags); painter->setCompositeOp(m_d->layer->compositeOpId()); painter->setOpacity(m_d->layer->projectionLeaf()->opacity()); painter->bitBlt(needRect.topLeft(), device, needRect); + + if (maxOutAlpha) { + m_d->cachedDevice.putDevice(device); + } +} + +void KisLayerProjectionPlane::apply(KisPainter *painter, const QRect &rect) +{ + applyImpl(painter, rect, false); +} + +void KisLayerProjectionPlane::applyMaxOutAlpha(KisPainter *painter, const QRect &rect) +{ + applyImpl(painter, rect, true); } KisPaintDeviceList KisLayerProjectionPlane::getLodCapableDevices() const { return KisPaintDeviceList() << m_d->layer->projection(); } QRect KisLayerProjectionPlane::needRect(const QRect &rect, KisLayer::PositionToFilthy pos) const { return m_d->layer->needRect(rect, pos); } QRect KisLayerProjectionPlane::changeRect(const QRect &rect, KisLayer::PositionToFilthy pos) const { return m_d->layer->changeRect(rect, pos); } QRect KisLayerProjectionPlane::accessRect(const QRect &rect, KisLayer::PositionToFilthy pos) const { return m_d->layer->accessRect(rect, pos); } QRect KisLayerProjectionPlane::needRectForOriginal(const QRect &rect) const { return m_d->layer->needRectForOriginal(rect); } diff --git a/libs/image/kis_layer_projection_plane.h b/libs/image/kis_layer_projection_plane.h index a684461cd9..141e4588d2 100644 --- a/libs/image/kis_layer_projection_plane.h +++ b/libs/image/kis_layer_projection_plane.h @@ -1,52 +1,60 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_LAYER_PROJECTION_PLANE_H #define __KIS_LAYER_PROJECTION_PLANE_H #include "kis_abstract_projection_plane.h" #include /** * An implementation of the KisAbstractProjectionPlane interface for a * layer object */ class KisLayerProjectionPlane : public KisAbstractProjectionPlane { public: KisLayerProjectionPlane(KisLayer *layer); ~KisLayerProjectionPlane() override; QRect recalculate(const QRect& rect, KisNodeSP filthyNode) override; void apply(KisPainter *painter, const QRect &rect) override; + void applyMaxOutAlpha(KisPainter *painter, const QRect &rect); QRect needRect(const QRect &rect, KisLayer::PositionToFilthy pos) const override; QRect changeRect(const QRect &rect, KisLayer::PositionToFilthy pos) const override; QRect accessRect(const QRect &rect, KisLayer::PositionToFilthy pos) const override; QRect needRectForOriginal(const QRect &rect) const override; KisPaintDeviceList getLodCapableDevices() const override; +private: + void applyImpl(KisPainter *painter, const QRect &rect, bool maxOutAlpha); + private: struct Private; const QScopedPointer m_d; }; +typedef QSharedPointer KisLayerProjectionPlaneSP; +typedef QWeakPointer KisLayerProjectionPlaneWSP; + + #endif /* __KIS_LAYER_PROJECTION_PLANE_H */ diff --git a/libs/image/kis_mask.cc b/libs/image/kis_mask.cc index b07dd02001..71743c2c7b 100644 --- a/libs/image/kis_mask.cc +++ b/libs/image/kis_mask.cc @@ -1,514 +1,513 @@ /* * 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" #include "KisSafeNodeProjectionStore.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; KisSafeSelectionNodeProjectionStoreSP safeProjection; void initSelectionImpl(KisSelectionSP copyFrom, KisLayerSP parentLayer, KisPaintDeviceSP copyFromDevice); }; KisMask::KisMask(const QString & name) : KisNode(nullptr) , m_d(new Private(this)) { setName(name); m_d->safeProjection = new KisSafeSelectionNodeProjectionStore(); m_d->safeProjection->setImage(image()); } KisMask::KisMask(const KisMask& rhs) : KisNode(rhs) , KisIndirectPaintingSupport() , m_d(new Private(this)) { setName(rhs.name()); m_d->safeProjection = new KisSafeSelectionNodeProjectionStore(*rhs.m_d->safeProjection); 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() { if (m_d->selection) { m_d->selection->setParentNode(0); } delete m_d; } void KisMask::setImage(KisImageWSP image) { KisPaintDeviceSP parentPaintDevice = parent() ? parent()->original() : 0; - KisDefaultBoundsBaseSP defaultBounds = new KisSelectionDefaultBounds(parentPaintDevice, image); + KisDefaultBoundsBaseSP defaultBounds = new KisSelectionDefaultBounds(parentPaintDevice); if (m_d->selection) { m_d->selection->setDefaultBounds(defaultBounds); } m_d->safeProjection->setImage(image); KisNode::setImage(image); } 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())); + selection->setDefaultBounds(new KisSelectionDefaultBounds(parentPaintDevice)); if (copyFrom->hasShapeSelection()) { delete selection->flatten(); } } else if (copyFromDevice) { KritaUtils::DeviceCopyMode copyMode = q->inherits("KisFilterMask") || q->inherits("KisTransparencyMask") ? KritaUtils::CopyAllFrames : KritaUtils::CopySnapshot; - selection = new KisSelection(copyFromDevice, copyMode, new KisSelectionDefaultBounds(parentPaintDevice, parentLayer->image())); + selection = new KisSelection(copyFromDevice, copyMode, new KisSelectionDefaultBounds(parentPaintDevice)); KisPixelSelectionSP pixelSelection = selection->pixelSelection(); if (pixelSelection->framesInterface()) { KisRasterKeyframeChannel *keyframeChannel = pixelSelection->keyframeChannel(); keyframeChannel->setFilenameSuffix(".pixelselection"); q->addKeyframeChannel(keyframeChannel); q->enableAnimation(); } } else { - selection = new KisSelection(new KisSelectionDefaultBounds(parentPaintDevice, parentLayer->image())); + selection = new KisSelection(new KisSelectionDefaultBounds(parentPaintDevice)); 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 { KisSelectionSP selection = this->selection(); return selection ? selection->pixelSelection() : 0; } KisPaintDeviceSP KisMask::original() const { return paintDevice(); } KisPaintDeviceSP KisMask::projection() const { KisPaintDeviceSP originalDevice = original(); KisPaintDeviceSP result = originalDevice; KisSelectionSP selection = this->selection(); if (selection && hasTemporaryTarget()) { result = m_d->safeProjection->getDeviceLazy(selection)->pixelSelection(); } return result; } 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; } bool KisMask::paintsOutsideSelection() const { return false; } void KisMask::apply(KisPaintDeviceSP projection, const QRect &applyRect, const QRect &needRect, PositionToFilthy maskPos) const { if (selection()) { flattenSelectionProjection(m_d->selection, applyRect); KisSelectionSP effectiveSelection = m_d->selection; { // Access temporary target under the lock held KisIndirectPaintingSupport::ReadLocker l(this); if (!paintsOutsideSelection()) { // extent of m_d->selection should also be accessed under a lock, // because it might be being merged in by the temporary target atm QRect effectiveExtent = m_d->selection->selectedRect(); if (hasTemporaryTarget()) { effectiveExtent |= temporaryTarget()->extent(); } if(!effectiveExtent.intersects(applyRect)) { return; } } if (hasTemporaryTarget()) { effectiveSelection = m_d->safeProjection->getDeviceLazy(m_d->selection); KisPainter::copyAreaOptimized(applyRect.topLeft(), m_d->selection->pixelSelection(), effectiveSelection->pixelSelection(), applyRect); KisPainter gc(effectiveSelection->pixelSelection()); setupTemporaryPainter(&gc); gc.bitBlt(applyRect.topLeft(), temporaryTarget(), applyRect); } else { m_d->safeProjection->releaseDevice(); } mergeInMaskInternal(projection, effectiveSelection, applyRect, needRect, maskPos); } } else { mergeInMaskInternal(projection, 0, applyRect, needRect, maskPos); } } void KisMask::mergeInMaskInternal(KisPaintDeviceSP projection, KisSelectionSP effectiveSelection, const QRect &applyRect, const QRect &preparedNeedRect, KisNode::PositionToFilthy maskPos) const { - KisPaintDeviceSP cacheDevice = m_d->paintDeviceCache.getDevice(projection); + KisCachedPaintDevice::Guard d1(projection, m_d->paintDeviceCache); + KisPaintDeviceSP cacheDevice = d1.device(); if (effectiveSelection) { QRect updatedRect = decorateRect(projection, cacheDevice, applyRect, maskPos); // masks don't have any compositioning KisPainter::copyAreaOptimized(updatedRect.topLeft(), cacheDevice, projection, updatedRect, effectiveSelection); } else { cacheDevice->makeCloneFromRough(projection, preparedNeedRect); projection->clear(preparedNeedRect); decorateRect(cacheDevice, projection, applyRect, maskPos); } - - m_d->paintDeviceCache.putDevice(cacheDevice); } void KisMask::flattenSelectionProjection(KisSelectionSP selection, const QRect &dirtyRect) const { selection->updateProjection(dirtyRect); } QRect KisMask::needRect(const QRect &rect, PositionToFilthy pos) const { Q_UNUSED(pos); QRect resultRect = rect; 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 { return KisMask::needRect(rect, pos); } QRect KisMask::extent() const { 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 { 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())); + m_d->selection = new KisSelection(new KisSelectionDefaultBounds(parentLayer->paintDevice())); } 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(); } KisNode::baseNodeChangedCallback(); } diff --git a/libs/image/kis_node.h b/libs/image/kis_node.h index 07ca1fc29f..83083ca9d4 100644 --- a/libs/image/kis_node.h +++ b/libs/image/kis_node.h @@ -1,434 +1,434 @@ /* * 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_NODE_H #define _KIS_NODE_H #include "kis_types.h" #include "kis_base_node.h" #include "kritaimage_export.h" #include class QRect; class QStringList; class KoProperties; class KisNodeVisitor; class KisNodeGraphListener; class KisNodeProgressProxy; class KisBusyProgressIndicator; class KisAbstractProjectionPlane; class KisProjectionLeaf; class KisKeyframeChannel; class KisTimeRange; class KisUndoAdapter; /** * A KisNode is a KisBaseNode that knows about its direct peers, parent * and children and whether it can have children. * * THREAD-SAFETY: All const methods of this class and setDirty calls * are considered to be thread-safe(!). All the others * especially add(), remove() and setParent() must be * protected externally. * * NOTE: your subclasses must have the Q_OBJECT declaration, even if * you do not define new signals or slots. */ class KRITAIMAGE_EXPORT KisNode : public KisBaseNode { friend class KisFilterMaskTest; Q_OBJECT public: /** * The struct describing the position of the node * against the filthy node. * NOTE: please change KisBaseRectsWalker::getPositionToFilthy * when changing this struct */ enum PositionToFilthy { N_ABOVE_FILTHY = 0x08, N_FILTHY_PROJECTION = 0x20, N_FILTHY = 0x40, N_BELOW_FILTHY = 0x80 }; /** * Create an empty node without a parent. */ KisNode(KisImageWSP image); /** * Create a copy of this node. The copy will not have a parent * node. */ KisNode(const KisNode & rhs); /** * Delete this node */ ~KisNode() override; virtual KisNodeSP clone() const = 0; bool accept(KisNodeVisitor &v) override; void accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter) override; /** * Re-implement this method to add constraints for the * subclasses that can be added as children to this node * * @return false if the given node is not allowed as a child to this node */ virtual bool allowAsChild(KisNodeSP) const = 0; /** * Set the entire node extent dirty; this percolates up to parent * nodes all the way to the root node. By default this is the * empty rect (through KisBaseNode::extent()) */ virtual void setDirty(); /** * Add the given rect to the set of dirty rects for this node; * this percolates up to parent nodes all the way to the root * node. */ void setDirty(const QRect & rect); /** * Add the given rects to the set of dirty rects for this node; * this percolates up to parent nodes all the way to the root * node. */ virtual void setDirty(const QVector &rects); /** * Add the given region to the set of dirty rects for this node; * this percolates up to parent nodes all the way to the root * node, if propagate is true; */ void setDirty(const QRegion ®ion); /** * Convenience override of multirect version of setDirtyDontResetAnimationCache() * * @see setDirtyDontResetAnimationCache(const QVector &rects) */ void setDirtyDontResetAnimationCache(); /** * Convenience override of multirect version of setDirtyDontResetAnimationCache() * * @see setDirtyDontResetAnimationCache(const QVector &rects) */ void setDirtyDontResetAnimationCache(const QRect &rect); /** * @brief setDirtyDontResetAnimationCache does almost the same thing as usual * setDirty() call, but doesn't reset the animation cache (since onlion skins are * not used when rendering animation. */ void setDirtyDontResetAnimationCache(const QVector &rects); /** * Informs that the frames in the given range are no longer valid * and need to be recached. * @param range frames to invalidate */ void invalidateFrames(const KisTimeRange &range, const QRect &rect); /** * Informs that the current world time should be changed. * Might be caused by e.g. undo operation */ void requestTimeSwitch(int time); /** * \return a pointer to a KisAbstractProjectionPlane interface of * the node. This interface is used by the image merging * framework to get information and to blending for the * layer. * * Please note the difference between need/change/accessRect and * the projectionPlane() interface. The former one gives * information about internal composition of the layer, and the * latter one about the total composition, including layer styles, * pass-through blending and etc. */ virtual KisAbstractProjectionPlaneSP projectionPlane() const; /** * Synchronizes LoD caches of the node with the current state of it. * The current level of detail is fetched from the image pointed by * default bounds object */ virtual void syncLodCache(); virtual KisPaintDeviceList getLodCapableDevices() const; /** * The rendering of the image may not always happen in the order - * of the main graph. Pass-through nodes ake some subgraphs + * of the main graph. Pass-through nodes make some subgraphs * linear, so it the order of rendering change. projectionLeaf() * is a special interface of KisNode that represents "a graph for * projection rendering". Therefore the nodes in projectionLeaf() * graph may have a different order the main one. */ virtual KisProjectionLeafSP projectionLeaf() const; void setImage(KisImageWSP image) override; protected: /** * \return internal changeRect() of the node. Do not mix with \see * projectionPlane() * * Some filters will cause a change of pixels those are outside * a requested rect. E.g. we change a rect of 2x2, then we want to * apply a convolution filter with kernel 4x4 (changeRect is * (2+2*3)x(2+2*3)=8x8) to that area. The rect that should be updated - * on the layer will be exaclty 8x8. More than that the needRect for + * on the layer will be exactly 8x8. More than that the needRect for * that update will be 14x14. See \ref needeRect. */ virtual QRect changeRect(const QRect &rect, PositionToFilthy pos = N_FILTHY) const; /** * \return internal needRect() of the node. Do not mix with \see * projectionPlane() * * Some filters need pixels outside the current processing rect to * compute the new value (for instance, convolution filters) * See \ref changeRect * See \ref accessRect */ virtual QRect needRect(const QRect &rect, PositionToFilthy pos = N_FILTHY) const; /** * \return internal accessRect() of the node. Do not mix with \see * projectionPlane() * * Shows the area of image, that may be accessed during accessing * the node. * * Example. You have a layer that needs to prepare some rect on a * projection, say expectedRect. To perform this, the projection * of all the layers below of the size needRect(expectedRect) * should be calculated by the merger beforehand and the layer * will access some other area of image inside the rect * accessRect(expectedRect) during updateProjection call. * * This knowledge about real access rect of a node is used by the * scheduler to avoid collisions between two multithreaded updaters * and so avoid flickering of the image. * * Currently, this method has nondefault value for shifted clone * layers only. */ virtual QRect accessRect(const QRect &rect, PositionToFilthy pos = N_FILTHY) const; /** * Called each time direct child nodes are added or removed under this * node as parent. This does not track changes inside the child nodes * or the child nodes' properties. */ virtual void childNodeChanged(KisNodeSP changedChildNode); public: // Graph methods /** * @return the graph sequence number calculated by the associated * graph listener. You can use it for checking for changes in the * graph. */ int graphSequenceNumber() const; /** * @return the graph listener this node belongs to. 0 if the node * does not belong to a grap listener. */ KisNodeGraphListener * graphListener() const; /** * Set the graph listener for this node. The graphlistener will be * informed before and after the list of child nodes has changed. */ void setGraphListener(KisNodeGraphListener * graphListener); /** * Returns the parent node of this node. This is 0 only for a root * node; otherwise this will be an actual Node */ KisNodeSP parent() const; /** * Returns the first child node of this node, or 0 if there are no * child nodes. */ KisNodeSP firstChild() const; /** * Returns the last child node of this node, or 0 if there are no * child nodes. */ KisNodeSP lastChild() const; /** * Returns the previous sibling of this node in the parent's list. * This is the node *above* this node in the composition stack. 0 * is returned if this child has no more previous siblings (== * firstChild()) */ KisNodeSP prevSibling() const; /** * Returns the next sibling of this node in the parent's list. * This is the node *below* this node in the composition stack. 0 * is returned if this child has no more next siblings (== * lastChild()) */ KisNodeSP nextSibling() const; /** * Returns how many direct child nodes this node has (not * recursive). */ quint32 childCount() const; /** * Retrieve the child node at the specified index. * * @return 0 if there is no node at this index. */ KisNodeSP at(quint32 index) const; /** * Retrieve the index of the specified child node. * * @return -1 if the specified node is not a child node of this * node. */ int index(const KisNodeSP node) const; /** * Return a list of child nodes of the current node that conform * to the specified constraints. There are no guarantees about the * order of the nodes in the list. The function is not recursive. * * @param nodeTypes. if not empty, only nodes that inherit the * classnames in this stringlist will be returned. * @param properties. if not empty, only nodes for which * KisNodeBase::check(properties) returns true will be returned. */ QList childNodes(const QStringList & nodeTypes, const KoProperties & properties) const; /** * @brief findChildByName finds the first child that has the given name * @param name the name to look for * @return the first child with the given name */ KisNodeSP findChildByName(const QString &name); Q_SIGNALS: /** * Don't use this signal anywhere other than KisNodeShape. It's a hack. */ void sigNodeChangedInternal(); public: /** * @return the node progress proxy used by this node, if this node has no progress * proxy, it will return the proxy of its parent, if the parent has no progress proxy * it will return 0 */ KisNodeProgressProxy* nodeProgressProxy() const; KisBusyProgressIndicator* busyProgressIndicator() const; private: /** * Create a node progress proxy for this node. You need to create a progress proxy only * if the node is going to appear in the layerbox, and it needs to be created before * the layer box is made aware of the proxy. */ void createNodeProgressProxy(); protected: KisBaseNodeSP parentCallback() const override; void notifyParentVisibilityChanged(bool value) override; void baseNodeChangedCallback() override; void baseNodeInvalidateAllFramesCallback() override; void baseNodeCollapsedChangedCallback() override; protected: void addKeyframeChannel(KisKeyframeChannel* channel) override; private: friend class KisNodeFacade; friend class KisNodeTest; friend class KisLayer; // Note: only for setting the preview mask! /** * Set the parent of this node. */ void setParent(KisNodeWSP parent); /** * Add the specified node above the specified node. If aboveThis * is 0, the node is added at the bottom. */ bool add(KisNodeSP newNode, KisNodeSP aboveThis); /** * Removes the node at the specified index from the child nodes. * * @return false if there is no node at this index */ bool remove(quint32 index); /** * Removes the node from the child nodes. * * @return false if there's no such node in this node. */ bool remove(KisNodeSP node); KisNodeSP prevChildImpl(KisNodeSP child); KisNodeSP nextChildImpl(KisNodeSP child); private: struct Private; Private * const m_d; }; Q_DECLARE_METATYPE(KisNodeSP) Q_DECLARE_METATYPE(KisNodeWSP) #endif diff --git a/libs/image/kis_selection.cc b/libs/image/kis_selection.cc index 74439386d2..f2ca048515 100644 --- a/libs/image/kis_selection.cc +++ b/libs/image/kis_selection.cc @@ -1,356 +1,356 @@ /* * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2007 Sven Langkamp * * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_selection.h" #include "kundo2command.h" #include "kis_selection_component.h" #include "kis_pixel_selection.h" #include "kis_node_graph_listener.h" #include "kis_node.h" #include "kis_image.h" #include "kis_default_bounds.h" #include "kis_iterator_ng.h" #include "KisLazyStorage.h" #include "KisSelectionUpdateCompressor.h" struct Q_DECL_HIDDEN KisSelection::Private { Private(KisSelection *q) : isVisible(true), shapeSelection(0), updateCompressor(q) { } // used for forwarding setDirty signals only KisNodeWSP parentNode; bool isVisible; //false is the selection decoration should not be displayed KisDefaultBoundsBaseSP defaultBounds; KisPixelSelectionSP pixelSelection; KisSelectionComponent *shapeSelection; KisLazyStorage updateCompressor; }; KisSelection::KisSelection(KisDefaultBoundsBaseSP defaultBounds) : m_d(new Private(this)) { if (!defaultBounds) { - defaultBounds = new KisSelectionDefaultBounds(KisPaintDeviceSP()); + defaultBounds = new KisSelectionEmptyBounds(0); } m_d->defaultBounds = defaultBounds; m_d->pixelSelection = new KisPixelSelection(m_d->defaultBounds, this); m_d->pixelSelection->setParentNode(m_d->parentNode); } KisSelection::KisSelection(const KisSelection& rhs) : KisShared(), m_d(new Private(this)) { copyFrom(rhs); } KisSelection::KisSelection(const KisPaintDeviceSP source, KritaUtils::DeviceCopyMode copyMode, KisDefaultBoundsBaseSP defaultBounds) : m_d(new Private(this)) { if (!defaultBounds) { - defaultBounds = new KisSelectionDefaultBounds(KisPaintDeviceSP()); + defaultBounds = new KisSelectionEmptyBounds(0); } m_d->defaultBounds = defaultBounds; m_d->pixelSelection = new KisPixelSelection(source, copyMode); m_d->pixelSelection->setParentSelection(this); m_d->pixelSelection->setParentNode(m_d->parentNode); m_d->pixelSelection->setDefaultBounds(m_d->defaultBounds); } KisSelection &KisSelection::operator=(const KisSelection &rhs) { if (&rhs != this) { copyFrom(rhs); } return *this; } void KisSelection::copyFrom(const KisSelection &rhs) { m_d->isVisible = rhs.m_d->isVisible; m_d->defaultBounds = rhs.m_d->defaultBounds; m_d->parentNode = 0; // not supposed to be shared Q_ASSERT(rhs.m_d->pixelSelection); m_d->pixelSelection = new KisPixelSelection(*rhs.m_d->pixelSelection, KritaUtils::CopyAllFrames); m_d->pixelSelection->setParentSelection(this); if (rhs.m_d->shapeSelection) { m_d->shapeSelection = rhs.m_d->shapeSelection->clone(this); Q_ASSERT(m_d->shapeSelection); Q_ASSERT(m_d->shapeSelection != rhs.m_d->shapeSelection); } else { m_d->shapeSelection = 0; } } KisSelection::~KisSelection() { delete m_d->shapeSelection; delete m_d; } void KisSelection::setParentNode(KisNodeWSP node) { m_d->parentNode = node; m_d->pixelSelection->setParentNode(node); // the updates come through the parent image, so all the updates // that happened in the meantime are considered "stalled" if (node) { m_d->updateCompressor->tryProcessStalledUpdate(); } } // for testing purposes only KisNodeWSP KisSelection::parentNode() const { return m_d->parentNode; } bool KisSelection::outlineCacheValid() const { return hasShapeSelection() || m_d->pixelSelection->outlineCacheValid(); } QPainterPath KisSelection::outlineCache() const { QPainterPath outline; if (hasShapeSelection()) { outline += m_d->shapeSelection->outlineCache(); } else if (m_d->pixelSelection->outlineCacheValid()) { outline += m_d->pixelSelection->outlineCache(); } return outline; } void KisSelection::recalculateOutlineCache() { Q_ASSERT(m_d->pixelSelection); if (hasShapeSelection()) { m_d->shapeSelection->recalculateOutlineCache(); } else if (!m_d->pixelSelection->outlineCacheValid()) { m_d->pixelSelection->recalculateOutlineCache(); } } bool KisSelection::thumbnailImageValid() const { return m_d->pixelSelection->thumbnailImageValid(); } void KisSelection::recalculateThumbnailImage(const QColor &maskColor) { m_d->pixelSelection->recalculateThumbnailImage(maskColor); } QImage KisSelection::thumbnailImage() const { return m_d->pixelSelection->thumbnailImage(); } QTransform KisSelection::thumbnailImageTransform() const { return m_d->pixelSelection->thumbnailImageTransform(); } bool KisSelection::hasPixelSelection() const { return m_d->pixelSelection && !m_d->pixelSelection->isEmpty(); } bool KisSelection::hasShapeSelection() const { return m_d->shapeSelection && !m_d->shapeSelection->isEmpty(); } KisPixelSelectionSP KisSelection::pixelSelection() const { return m_d->pixelSelection; } KisSelectionComponent* KisSelection::shapeSelection() const { return m_d->shapeSelection; } void KisSelection::setShapeSelection(KisSelectionComponent* shapeSelection) { const bool needsNotification = shapeSelection != m_d->shapeSelection; m_d->shapeSelection = shapeSelection; if (needsNotification) { requestCompressedProjectionUpdate(QRect()); } } KisPixelSelectionSP KisSelection::projection() const { return m_d->pixelSelection; } void KisSelection::updateProjection(const QRect &rc) { if(hasShapeSelection()) { m_d->shapeSelection->renderToProjection(m_d->pixelSelection, rc); m_d->pixelSelection->setOutlineCache(m_d->shapeSelection->outlineCache()); } } void KisSelection::updateProjection() { if(hasShapeSelection()) { m_d->pixelSelection->clear(); m_d->shapeSelection->renderToProjection(m_d->pixelSelection); m_d->pixelSelection->setOutlineCache(m_d->shapeSelection->outlineCache()); } } void KisSelection::setVisible(bool visible) { bool needsNotification = visible != m_d->isVisible; m_d->isVisible = visible; if (needsNotification) { notifySelectionChanged(); } } bool KisSelection::isVisible() { return m_d->isVisible; } bool KisSelection::isTotallyUnselected(const QRect & r) const { return m_d->pixelSelection->isTotallyUnselected(r); } QRect KisSelection::selectedRect() const { return m_d->pixelSelection->selectedRect(); } QRect KisSelection::selectedExactRect() const { return m_d->pixelSelection->selectedExactRect(); } qint32 KisSelection::x() const { return m_d->pixelSelection->x(); } qint32 KisSelection::y() const { return m_d->pixelSelection->y(); } void KisSelection::setX(qint32 x) { Q_ASSERT(m_d->pixelSelection); qint32 delta = x - m_d->pixelSelection->x(); m_d->pixelSelection->setX(x); if (m_d->shapeSelection) { m_d->shapeSelection->moveX(delta); } } void KisSelection::setY(qint32 y) { Q_ASSERT(m_d->pixelSelection); qint32 delta = y - m_d->pixelSelection->y(); m_d->pixelSelection->setY(y); if (m_d->shapeSelection) { m_d->shapeSelection->moveY(delta); } } void KisSelection::setDefaultBounds(KisDefaultBoundsBaseSP bounds) { m_d->defaultBounds = bounds; m_d->pixelSelection->setDefaultBounds(bounds); } void KisSelection::clear() { // FIXME: check whether this is safe delete m_d->shapeSelection; m_d->shapeSelection = 0; m_d->pixelSelection->clear(); } KUndo2Command* KisSelection::flatten() { KUndo2Command *command = 0; if (hasShapeSelection()) { command = m_d->shapeSelection->resetToEmpty(); } return command; } void KisSelection::notifySelectionChanged() { KisNodeWSP parentNode; if (!(parentNode = this->parentNode())) return; KisNodeGraphListener *listener; if (!(listener = parentNode->graphListener())) return; listener->notifySelectionChanged(); } void KisSelection::requestCompressedProjectionUpdate(const QRect &rc) { m_d->updateCompressor->requestUpdate(rc); } quint8 KisSelection::selected(qint32 x, qint32 y) const { KisHLineConstIteratorSP iter = m_d->pixelSelection->createHLineConstIteratorNG(x, y, 1); const quint8 *pix = iter->oldRawData(); return *pix; } diff --git a/libs/image/kis_selection_mask.cpp b/libs/image/kis_selection_mask.cpp index 81329a2fa4..6a3474dd26 100644 --- a/libs/image/kis_selection_mask.cpp +++ b/libs/image/kis_selection_mask.cpp @@ -1,323 +1,321 @@ /* * Copyright (c) 2006 Boudewijn Rempt * * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_selection_mask.h" #include "kis_image.h" #include "kis_layer.h" #include "kis_selection.h" #include #include #include #include "kis_fill_painter.h" #include #include "kis_node_visitor.h" #include "kis_processing_visitor.h" #include "kis_pixel_selection.h" #include "kis_undo_adapter.h" #include #include #include "kis_thread_safe_signal_compressor.h" #include "kis_layer_properties_icons.h" #include "kis_cached_paint_device.h" #include "kis_image_config.h" #include "KisImageConfigNotifier.h" struct Q_DECL_HIDDEN KisSelectionMask::Private { public: Private(KisSelectionMask *_q) : q(_q) , updatesCompressor(0) , maskColor(Qt::green, KoColorSpaceRegistry::instance()->rgb8()) {} KisSelectionMask *q; KisImageWSP image; KisCachedPaintDevice paintDeviceCache; KisCachedSelection cachedSelection; KisThreadSafeSignalCompressor *updatesCompressor; KoColor maskColor; void slotSelectionChangedCompressed(); void slotConfigChanged(); }; KisSelectionMask::KisSelectionMask(KisImageWSP image) : KisEffectMask() , m_d(new Private(this)) { setName("selection"); setActive(false); setSupportsLodMoves(false); m_d->image = image; m_d->updatesCompressor = new KisThreadSafeSignalCompressor(50, KisSignalCompressor::FIRST_ACTIVE); connect(m_d->updatesCompressor, SIGNAL(timeout()), SLOT(slotSelectionChangedCompressed())); this->moveToThread(image->thread()); connect(KisImageConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); m_d->slotConfigChanged(); } KisSelectionMask::KisSelectionMask(const KisSelectionMask& rhs) : KisEffectMask(rhs) , m_d(new Private(this)) { m_d->image = rhs.image(); m_d->updatesCompressor = new KisThreadSafeSignalCompressor(300, KisSignalCompressor::POSTPONE); connect(m_d->updatesCompressor, SIGNAL(timeout()), SLOT(slotSelectionChangedCompressed())); this->moveToThread(m_d->image->thread()); connect(KisImageConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); m_d->slotConfigChanged(); } KisSelectionMask::~KisSelectionMask() { m_d->updatesCompressor->deleteLater(); delete m_d; } QIcon KisSelectionMask::icon() const { return KisIconUtils::loadIcon("selectionMask"); } void KisSelectionMask::mergeInMaskInternal(KisPaintDeviceSP projection, KisSelectionSP effectiveSelection, const QRect &applyRect, const QRect &preparedNeedRect, KisNode::PositionToFilthy maskPos) const { Q_UNUSED(maskPos); Q_UNUSED(preparedNeedRect); if (!effectiveSelection) return; { KisSelectionSP mainMaskSelection = this->selection(); if (mainMaskSelection && (!mainMaskSelection->isVisible() || mainMaskSelection->pixelSelection()->defaultBounds()->externalFrameActive())) { return; } } - KisPaintDeviceSP fillDevice = m_d->paintDeviceCache.getDevice(projection); + KisCachedPaintDevice::Guard d1(projection, m_d->paintDeviceCache); + KisPaintDeviceSP fillDevice = d1.device(); fillDevice->setDefaultPixel(m_d->maskColor); const QRect selectionExtent = effectiveSelection->selectedRect(); if (selectionExtent.contains(applyRect) || selectionExtent.intersects(applyRect)) { - KisSelectionSP invertedSelection = m_d->cachedSelection.getSelection(); + KisCachedSelection::Guard s1(m_d->cachedSelection); + KisSelectionSP invertedSelection = s1.selection(); invertedSelection->pixelSelection()->makeCloneFromRough(effectiveSelection->pixelSelection(), applyRect); invertedSelection->pixelSelection()->invert(); KisPainter gc(projection); gc.setSelection(invertedSelection); gc.bitBlt(applyRect.topLeft(), fillDevice, applyRect); - m_d->cachedSelection.putSelection(invertedSelection); - } else { KisPainter gc(projection); gc.bitBlt(applyRect.topLeft(), fillDevice, applyRect); } - - m_d->paintDeviceCache.putDevice(fillDevice); } bool KisSelectionMask::paintsOutsideSelection() const { return true; } void KisSelectionMask::setSelection(KisSelectionSP selection) { if (selection) { KisEffectMask::setSelection(selection); } else { KisEffectMask::setSelection(new KisSelection()); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->alpha8(); KisFillPainter gc(KisPaintDeviceSP(this->selection()->pixelSelection().data())); gc.fillRect(image()->bounds(), KoColor(Qt::white, cs), MAX_SELECTED); gc.end(); } setDirty(); } KisImageWSP KisSelectionMask::image() const { return m_d->image; } bool KisSelectionMask::accept(KisNodeVisitor &v) { return v.visit(this); } void KisSelectionMask::accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter) { return visitor.visit(this, undoAdapter); } KisBaseNode::PropertyList KisSelectionMask::sectionModelProperties() const { KisBaseNode::PropertyList l = KisBaseNode::sectionModelProperties(); l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::selectionActive, active()); return l; } void KisSelectionMask::setSectionModelProperties(const KisBaseNode::PropertyList &properties) { KisEffectMask::setSectionModelProperties(properties); setActive(properties.at(2).state.toBool()); } void KisSelectionMask::setVisible(bool visible, bool isLoading) { const bool oldVisible = this->visible(false); setNodeProperty("visible", visible); if (!isLoading && visible != oldVisible) { if (selection()) selection()->setVisible(visible); } } bool KisSelectionMask::active() const { return nodeProperties().boolProperty("active", true); } void KisSelectionMask::setActive(bool active) { KisImageWSP image = this->image(); KisLayerSP parentLayer = qobject_cast(parent().data()); if (active && parentLayer) { KisSelectionMaskSP activeMask = parentLayer->selectionMask(); if (activeMask && activeMask != this) { activeMask->setActive(false); } } const bool oldActive = this->active(); setNodeProperty("active", active); /** * WARNING: we have a direct link to the image here, but we * must not use it for notification until we are a part of * the nore graph! Notifications should be emitted iff we * have graph listener link set up. */ if (graphListener() && image && oldActive != active) { baseNodeChangedCallback(); image->undoAdapter()->emitSelectionChanged(); } } QRect KisSelectionMask::needRect(const QRect &rect, KisNode::PositionToFilthy pos) const { Q_UNUSED(pos); // selection masks just add an overlay, so the needed rect is simply passed through return rect; } QRect KisSelectionMask::changeRect(const QRect &rect, KisNode::PositionToFilthy pos) const { Q_UNUSED(pos); // selection masks just add an overlay, so the changed rect is simply passed through return rect; } QRect KisSelectionMask::extent() const { // since mask overlay is inverted, the mask paints over // the entire image bounds QRect resultRect; KisSelectionSP selection = this->selection(); if (selection) { resultRect = selection->pixelSelection()->defaultBounds()->bounds(); } else if (KisNodeSP parent = this->parent()) { KisPaintDeviceSP dev = parent->projection(); if (dev) { resultRect = dev->defaultBounds()->bounds(); } } return resultRect; } QRect KisSelectionMask::exactBounds() const { return extent(); } void KisSelectionMask::notifySelectionChangedCompressed() { m_d->updatesCompressor->start(); } void KisSelectionMask::flattenSelectionProjection(KisSelectionSP selection, const QRect &dirtyRect) const { Q_UNUSED(selection); Q_UNUSED(dirtyRect); } void KisSelectionMask::Private::slotSelectionChangedCompressed() { KisSelectionSP currentSelection = q->selection(); if (!currentSelection) return; currentSelection->notifySelectionChanged(); } void KisSelectionMask::Private::slotConfigChanged() { const KoColorSpace *cs = image ? image->colorSpace() : KoColorSpaceRegistry::instance()->rgb8(); KisImageConfig cfg(true); maskColor = KoColor(cfg.selectionOverlayMaskColor(), cs); if (image && image->overlaySelectionMask() == q) { q->setDirty(); } } #include "moc_kis_selection_mask.cpp" diff --git a/libs/image/layerstyles/KisLayerStyleKnockoutBlower.cpp b/libs/image/layerstyles/KisLayerStyleKnockoutBlower.cpp new file mode 100644 index 0000000000..1583567a0a --- /dev/null +++ b/libs/image/layerstyles/KisLayerStyleKnockoutBlower.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2019 Dmitry Kazakov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#include "KisLayerStyleKnockoutBlower.h" + +#include "kis_painter.h" +#include "KoCompositeOpRegistry.h" + +KisSelectionSP KisLayerStyleKnockoutBlower::knockoutSelectionLazy() +{ + { + QReadLocker l(&m_lock); + if (m_knockoutSelection) { + return m_knockoutSelection; + } + } + + { + QWriteLocker l(&m_lock); + if (m_knockoutSelection) { + return m_knockoutSelection; + } else { + m_knockoutSelection = new KisSelection(new KisSelectionEmptyBounds(0)); + return m_knockoutSelection; + } + } +} + +void KisLayerStyleKnockoutBlower::setKnockoutSelection(KisSelectionSP selection) +{ + QWriteLocker l(&m_lock); + m_knockoutSelection = selection; +} + +void KisLayerStyleKnockoutBlower::resetKnockoutSelection() +{ + QWriteLocker l(&m_lock); + m_knockoutSelection = 0; +} + +void KisLayerStyleKnockoutBlower::apply(KisPainter *painter, KisPaintDeviceSP mergedStyle, const QRect &rect) const +{ + QReadLocker l(&m_lock); + + KIS_SAFE_ASSERT_RECOVER_NOOP(m_knockoutSelection); + + painter->setOpacity(OPACITY_OPAQUE_U8); + painter->setChannelFlags(QBitArray()); + painter->setCompositeOp(COMPOSITE_COPY); + painter->setSelection(m_knockoutSelection); + painter->bitBlt(rect.topLeft(), mergedStyle, rect); +} + +bool KisLayerStyleKnockoutBlower::isEmpty() const +{ + QReadLocker l(&m_lock); + return !m_knockoutSelection; +} diff --git a/libs/image/layerstyles/KisLayerStyleKnockoutBlower.h b/libs/image/layerstyles/KisLayerStyleKnockoutBlower.h new file mode 100644 index 0000000000..50188eff42 --- /dev/null +++ b/libs/image/layerstyles/KisLayerStyleKnockoutBlower.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2019 Dmitry Kazakov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#ifndef KISLAYERSTYLEKNOCKOUTBLOWER_H +#define KISLAYERSTYLEKNOCKOUTBLOWER_H + +#include "kis_selection.h" +#include + +class KisPainter; + + +class KRITAIMAGE_EXPORT KisLayerStyleKnockoutBlower +{ +public: + KisSelectionSP knockoutSelectionLazy(); + + void setKnockoutSelection(KisSelectionSP selection); + void resetKnockoutSelection(); + + + void apply(KisPainter *painter, KisPaintDeviceSP mergedStyle, const QRect &rect) const; + bool isEmpty() const; + +private: + mutable QReadWriteLock m_lock; + KisSelectionSP m_knockoutSelection; +}; + +#endif // KISLAYERSTYLEKNOCKOUTBLOWER_H diff --git a/libs/image/layerstyles/kis_layer_style_filter.h b/libs/image/layerstyles/kis_layer_style_filter.h index 30a311d404..2aca6515a0 100644 --- a/libs/image/layerstyles/kis_layer_style_filter.h +++ b/libs/image/layerstyles/kis_layer_style_filter.h @@ -1,72 +1,74 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_LAYER_STYLE_FILTER_H #define __KIS_LAYER_STYLE_FILTER_H #include "kis_types.h" #include "kis_shared.h" #include "kritaimage_export.h" #include "kis_psd_layer_style.h" #include class KisLayerStyleFilterEnvironment; class KisMultipleProjection; +class KisLayerStyleKnockoutBlower; class KRITAIMAGE_EXPORT KisLayerStyleFilter : public KisShared { public: KisLayerStyleFilter(const KoID &id); virtual ~KisLayerStyleFilter(); /** * \return Unique identifier for this filter */ QString id() const; virtual KisLayerStyleFilter* clone() const = 0; virtual void processDirectly(KisPaintDeviceSP src, KisMultipleProjection *dst, + KisLayerStyleKnockoutBlower *blower, const QRect &applyRect, KisPSDLayerStyleSP style, KisLayerStyleFilterEnvironment *env) const = 0; /** * Some filters need pixels outside the current processing rect to compute the new * value (for instance, convolution filters) */ virtual QRect neededRect(const QRect & rect, KisPSDLayerStyleSP style, KisLayerStyleFilterEnvironment *env) const = 0; /** * Similar to \ref neededRect: some filters will alter a lot of pixels that are * near to each other at the same time. So when you changed a single rectangle * in a device, the actual rectangle that will feel the influence of this change * might be bigger. Use this function to determine that rect. */ virtual QRect changedRect(const QRect & rect, KisPSDLayerStyleSP style, KisLayerStyleFilterEnvironment *env) const = 0; protected: KisLayerStyleFilter(const KisLayerStyleFilter &rhs); private: struct Private; const QScopedPointer m_d; }; #endif /* __KIS_LAYER_STYLE_FILTER_H */ diff --git a/libs/image/layerstyles/kis_layer_style_filter_environment.cpp b/libs/image/layerstyles/kis_layer_style_filter_environment.cpp index a8cf6e834e..902a88bd8a 100644 --- a/libs/image/layerstyles/kis_layer_style_filter_environment.cpp +++ b/libs/image/layerstyles/kis_layer_style_filter_environment.cpp @@ -1,133 +1,146 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_layer_style_filter_environment.h" #include #include "kis_layer.h" #include "kis_ls_utils.h" #include "kis_selection.h" #include "kis_pixel_selection.h" #include "kis_painter.h" #include "kis_image.h" #include "krita_utils.h" #include #include "kis_random_accessor_ng.h" #include "kis_iterator_ng.h" +#include "kis_cached_paint_device.h" struct Q_DECL_HIDDEN KisLayerStyleFilterEnvironment::Private { KisLayer *sourceLayer; KisPixelSelectionSP cachedRandomSelection; + KisCachedSelection globalCachedSelection; + KisCachedPaintDevice globalCachedPaintDevice; static KisPixelSelectionSP generateRandomSelection(const QRect &rc); }; KisPixelSelectionSP KisLayerStyleFilterEnvironment::Private:: generateRandomSelection(const QRect &rc) { KisPixelSelectionSP selection = new KisPixelSelection(); KisSequentialIterator dstIt(selection, rc); boost::mt11213b uniformSource; if (uniformSource.max() >= 0x00FFFFFF) { while (dstIt.nextPixel()) { int randValue = uniformSource(); *dstIt.rawData() = (quint8) randValue; if (!dstIt.nextPixel()) break; randValue >>= 8; *dstIt.rawData() = (quint8) randValue; if (!dstIt.nextPixel()) break; randValue >>= 8; *dstIt.rawData() = (quint8) randValue; } } else { while (dstIt.nextPixel()) { *dstIt.rawData() = (quint8) uniformSource(); } } return selection; } KisLayerStyleFilterEnvironment::KisLayerStyleFilterEnvironment(KisLayer *sourceLayer) : m_d(new Private) { Q_ASSERT(sourceLayer); m_d->sourceLayer = sourceLayer; } KisLayerStyleFilterEnvironment::~KisLayerStyleFilterEnvironment() { } QRect KisLayerStyleFilterEnvironment::layerBounds() const { return m_d->sourceLayer ? m_d->sourceLayer->projection()->exactBounds() : QRect(); } QRect KisLayerStyleFilterEnvironment::defaultBounds() const { return m_d->sourceLayer ? m_d->sourceLayer->original()->defaultBounds()->bounds() : QRect(); } int KisLayerStyleFilterEnvironment::currentLevelOfDetail() const { return m_d->sourceLayer ? m_d->sourceLayer->original()->defaultBounds()->currentLevelOfDetail() : 0; } void KisLayerStyleFilterEnvironment::setupFinalPainter(KisPainter *gc, quint8 opacity, const QBitArray &channelFlags) const { Q_ASSERT(m_d->sourceLayer); gc->setOpacity(KritaUtils::mergeOpacity(opacity, m_d->sourceLayer->opacity())); gc->setChannelFlags(KritaUtils::mergeChannelFlags(channelFlags, m_d->sourceLayer->channelFlags())); } KisPixelSelectionSP KisLayerStyleFilterEnvironment::cachedRandomSelection(const QRect &requestedRect) const { KisPixelSelectionSP selection = m_d->cachedRandomSelection; QRect existingRect; if (selection) { existingRect = selection->selectedExactRect(); } if (!existingRect.contains(requestedRect)) { m_d->cachedRandomSelection = Private::generateRandomSelection(requestedRect | existingRect); } return m_d->cachedRandomSelection; } + +KisCachedSelection *KisLayerStyleFilterEnvironment::cachedSelection() +{ + return &m_d->globalCachedSelection; +} + +KisCachedPaintDevice *KisLayerStyleFilterEnvironment::cachedPaintDevice() +{ + return &m_d->globalCachedPaintDevice; +} diff --git a/libs/image/layerstyles/kis_layer_style_filter_environment.h b/libs/image/layerstyles/kis_layer_style_filter_environment.h index a54c9475c6..dfaa55cc1f 100644 --- a/libs/image/layerstyles/kis_layer_style_filter_environment.h +++ b/libs/image/layerstyles/kis_layer_style_filter_environment.h @@ -1,55 +1,60 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_LAYER_STYLE_FILTER_ENVIRONMENT_H #define __KIS_LAYER_STYLE_FILTER_ENVIRONMENT_H #include #include #include #include "kis_types.h" class KisPainter; class KisLayer; class QPainterPath; class QBitArray; +class KisCachedPaintDevice; +class KisCachedSelection; class KRITAIMAGE_EXPORT KisLayerStyleFilterEnvironment { public: KisLayerStyleFilterEnvironment(KisLayer *sourceLayer); ~KisLayerStyleFilterEnvironment(); QRect layerBounds() const; QRect defaultBounds() const; int currentLevelOfDetail() const; void setupFinalPainter(KisPainter *gc, quint8 opacity, const QBitArray &channelFlags) const; KisPixelSelectionSP cachedRandomSelection(const QRect &requestedRect) const; + KisCachedSelection* cachedSelection(); + KisCachedPaintDevice* cachedPaintDevice(); + private: struct Private; const QScopedPointer m_d; }; #endif /* __KIS_LAYER_STYLE_FILTER_ENVIRONMENT_H */ diff --git a/libs/image/layerstyles/kis_layer_style_filter_projection_plane.cpp b/libs/image/layerstyles/kis_layer_style_filter_projection_plane.cpp index 43c876d0d8..edc1f35503 100644 --- a/libs/image/layerstyles/kis_layer_style_filter_projection_plane.cpp +++ b/libs/image/layerstyles/kis_layer_style_filter_projection_plane.cpp @@ -1,142 +1,155 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_layer_style_filter_projection_plane.h" #include "filter/kis_filter.h" #include "filter/kis_filter_configuration.h" #include "filter/kis_filter_registry.h" #include "kis_layer_style_filter.h" #include "kis_layer_style_filter_environment.h" #include "kis_psd_layer_style.h" #include "kis_painter.h" #include "kis_multiple_projection.h" +#include "KisLayerStyleKnockoutBlower.h" struct KisLayerStyleFilterProjectionPlane::Private { Private(KisLayer *_sourceLayer) : sourceLayer(_sourceLayer), environment(new KisLayerStyleFilterEnvironment(_sourceLayer)) { KIS_SAFE_ASSERT_RECOVER_NOOP(_sourceLayer); } Private(const Private &rhs, KisLayer *_sourceLayer, KisPSDLayerStyleSP clonedStyle) : sourceLayer(_sourceLayer), filter(rhs.filter ? rhs.filter->clone() : 0), style(clonedStyle), environment(new KisLayerStyleFilterEnvironment(_sourceLayer)), projection(rhs.projection) { KIS_SAFE_ASSERT_RECOVER_NOOP(_sourceLayer); } KisLayer *sourceLayer; QScopedPointer filter; KisPSDLayerStyleSP style; QScopedPointer environment; + KisLayerStyleKnockoutBlower knockoutBlower; KisMultipleProjection projection; }; KisLayerStyleFilterProjectionPlane:: KisLayerStyleFilterProjectionPlane(KisLayer *sourceLayer) : m_d(new Private(sourceLayer)) { } KisLayerStyleFilterProjectionPlane::KisLayerStyleFilterProjectionPlane(const KisLayerStyleFilterProjectionPlane &rhs, KisLayer *sourceLayer, KisPSDLayerStyleSP clonedStyle) : m_d(new Private(*rhs.m_d, sourceLayer, clonedStyle)) { } KisLayerStyleFilterProjectionPlane::~KisLayerStyleFilterProjectionPlane() { } void KisLayerStyleFilterProjectionPlane::setStyle(KisLayerStyleFilter *filter, KisPSDLayerStyleSP style) { m_d->filter.reset(filter); m_d->style = style; } QRect KisLayerStyleFilterProjectionPlane::recalculate(const QRect& rect, KisNodeSP filthyNode) { Q_UNUSED(filthyNode); if (!m_d->sourceLayer || !m_d->filter) { warnKrita << "KisLayerStyleFilterProjectionPlane::recalculate(): [BUG] is not initialized"; return QRect(); } m_d->projection.clear(rect); m_d->filter->processDirectly(m_d->sourceLayer->projection(), &m_d->projection, + &m_d->knockoutBlower, rect, m_d->style, m_d->environment.data()); return rect; } void KisLayerStyleFilterProjectionPlane::apply(KisPainter *painter, const QRect &rect) { m_d->projection.apply(painter->device(), rect, m_d->environment.data()); } KisPaintDeviceList KisLayerStyleFilterProjectionPlane::getLodCapableDevices() const { return m_d->projection.getLodCapableDevices(); } +bool KisLayerStyleFilterProjectionPlane::isEmpty() const +{ + return m_d->projection.isEmpty(); +} + +KisLayerStyleKnockoutBlower *KisLayerStyleFilterProjectionPlane::knockoutBlower() const +{ + return &m_d->knockoutBlower; +} + QRect KisLayerStyleFilterProjectionPlane::needRect(const QRect &rect, KisLayer::PositionToFilthy pos) const { if (!m_d->sourceLayer || !m_d->filter) { warnKrita << "KisLayerStyleFilterProjectionPlane::needRect(): [BUG] is not initialized"; return rect; } KIS_ASSERT_RECOVER_NOOP(pos == KisLayer::N_ABOVE_FILTHY); return m_d->filter->neededRect(rect, m_d->style, m_d->environment.data()); } QRect KisLayerStyleFilterProjectionPlane::changeRect(const QRect &rect, KisLayer::PositionToFilthy pos) const { if (!m_d->sourceLayer || !m_d->filter) { warnKrita << "KisLayerStyleFilterProjectionPlane::changeRect(): [BUG] is not initialized"; return rect; } KIS_ASSERT_RECOVER_NOOP(pos == KisLayer::N_ABOVE_FILTHY); return m_d->filter->changedRect(rect, m_d->style, m_d->environment.data()); } QRect KisLayerStyleFilterProjectionPlane::accessRect(const QRect &rect, KisLayer::PositionToFilthy pos) const { return needRect(rect, pos); } QRect KisLayerStyleFilterProjectionPlane::needRectForOriginal(const QRect &rect) const { return needRect(rect, KisLayer::N_ABOVE_FILTHY); } diff --git a/libs/image/layerstyles/kis_layer_style_filter_projection_plane.h b/libs/image/layerstyles/kis_layer_style_filter_projection_plane.h index e3d5cc76ea..71b6bb2e38 100644 --- a/libs/image/layerstyles/kis_layer_style_filter_projection_plane.h +++ b/libs/image/layerstyles/kis_layer_style_filter_projection_plane.h @@ -1,56 +1,66 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_LAYER_STYLE_FILTER_PROJECTION_PLANE_H #define __KIS_LAYER_STYLE_FILTER_PROJECTION_PLANE_H #include "kis_abstract_projection_plane.h" #include #include "kis_types.h" +class KisLayerStyleKnockoutBlower; + class KisLayerStyleFilterProjectionPlane : public KisAbstractProjectionPlane { public: KisLayerStyleFilterProjectionPlane(KisLayer *sourceLayer); KisLayerStyleFilterProjectionPlane(const KisLayerStyleFilterProjectionPlane &rhs, KisLayer *sourceLayer, KisPSDLayerStyleSP clonedStyle); ~KisLayerStyleFilterProjectionPlane() override; void setStyle(KisLayerStyleFilter *filter, KisPSDLayerStyleSP style); QRect recalculate(const QRect& rect, KisNodeSP filthyNode) override; void apply(KisPainter *painter, const QRect &rect) override; QRect needRect(const QRect &rect, KisLayer::PositionToFilthy pos) const override; QRect changeRect(const QRect &rect, KisLayer::PositionToFilthy pos) const override; QRect accessRect(const QRect &rect, KisLayer::PositionToFilthy pos) const override; QRect needRectForOriginal(const QRect &rect) const override; KisPaintDeviceList getLodCapableDevices() const override; + /** + * \returns true if a call to apply() will actually paint anything. Basically, + * it is a cached version of isEnabled(), though the state may change after calling + * to recalculate(). + */ + bool isEmpty() const; + + KisLayerStyleKnockoutBlower *knockoutBlower() const; private: struct Private; const QScopedPointer m_d; }; typedef QSharedPointer KisLayerStyleFilterProjectionPlaneSP; typedef QWeakPointer KisLayerStyleFilterProjectionPlaneWSP; #endif /* __KIS_LAYER_STYLE_FILTER_PROJECTION_PLANE_H */ diff --git a/libs/image/layerstyles/kis_layer_style_projection_plane.cpp b/libs/image/layerstyles/kis_layer_style_projection_plane.cpp index 9b99308b48..e951d9f134 100644 --- a/libs/image/layerstyles/kis_layer_style_projection_plane.cpp +++ b/libs/image/layerstyles/kis_layer_style_projection_plane.cpp @@ -1,331 +1,414 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_layer_style_projection_plane.h" #include "kis_global.h" #include "kis_layer_style_filter_projection_plane.h" +#include "kis_layer_projection_plane.h" #include "kis_psd_layer_style.h" #include "kis_ls_drop_shadow_filter.h" #include "kis_ls_satin_filter.h" #include "kis_ls_overlay_filter.h" #include "kis_ls_stroke_filter.h" #include "kis_ls_bevel_emboss_filter.h" #include "kis_projection_leaf.h" +#include "kis_cached_paint_device.h" +#include "kis_painter.h" +#include "kis_ls_utils.h" +#include "KisLayerStyleKnockoutBlower.h" struct Q_DECL_HIDDEN KisLayerStyleProjectionPlane::Private { - KisAbstractProjectionPlaneWSP sourceProjectionPlane; + KisLayerProjectionPlaneWSP sourceProjectionPlane; QVector stylesBefore; QVector stylesAfter; + QVector stylesOverlay; + + KisCachedPaintDevice cachedPaintDevice; + KisCachedSelection cachedSelection; + KisLayer *sourceLayer = 0; + KisPSDLayerStyleSP style; bool canHaveChildNodes = false; bool dependsOnLowerNodes = false; void initSourcePlane(KisLayer *sourceLayer) { KIS_SAFE_ASSERT_RECOVER_RETURN(sourceLayer); sourceProjectionPlane = sourceLayer->internalProjectionPlane(); canHaveChildNodes = sourceLayer->projectionLeaf()->canHaveChildLayers(); dependsOnLowerNodes = sourceLayer->projectionLeaf()->dependsOnLowerNodes(); + this->sourceLayer = sourceLayer; + } + + QVector allStyles() const { + return stylesBefore + stylesOverlay + stylesAfter; + } + + bool hasOverlayStyles() const { + Q_FOREACH (KisLayerStyleFilterProjectionPlaneSP plane, stylesOverlay) { + if (!plane->isEmpty()) return true; + } + + return false; + } + + bool hasKnockoutStyles() const { + Q_FOREACH (KisLayerStyleFilterProjectionPlaneSP plane, stylesBefore) { + if (!plane->knockoutBlower()->isEmpty()) return true; + } + + Q_FOREACH (KisLayerStyleFilterProjectionPlaneSP plane, stylesAfter) { + if (!plane->knockoutBlower()->isEmpty()) return true; + } + + return false; } + + void applyComplexPlane(KisPainter *painter, + KisLayerStyleFilterProjectionPlaneSP plane, + const QRect &rect, + KisPaintDeviceSP originalClone); }; KisLayerStyleProjectionPlane::KisLayerStyleProjectionPlane(KisLayer *sourceLayer) : m_d(new Private) { KisPSDLayerStyleSP style = sourceLayer->layerStyle(); KIS_ASSERT_RECOVER(style) { style = toQShared(new KisPSDLayerStyle()); } init(sourceLayer, style); } KisLayerStyleProjectionPlane::KisLayerStyleProjectionPlane(const KisLayerStyleProjectionPlane &rhs, KisLayer *sourceLayer, KisPSDLayerStyleSP clonedStyle) : m_d(new Private) { m_d->initSourcePlane(sourceLayer); m_d->style = clonedStyle; KIS_SAFE_ASSERT_RECOVER(m_d->style) { m_d->style = toQShared(new KisPSDLayerStyle()); } - Q_FOREACH (KisLayerStyleFilterProjectionPlaneSP plane, rhs.m_d->stylesBefore) { + Q_FOREACH (KisLayerStyleFilterProjectionPlaneSP plane, rhs.m_d->allStyles()) { m_d->stylesBefore << toQShared(new KisLayerStyleFilterProjectionPlane(*plane, sourceLayer, m_d->style)); } - - Q_FOREACH (KisLayerStyleFilterProjectionPlaneSP plane, rhs.m_d->stylesAfter) { - m_d->stylesAfter << toQShared(new KisLayerStyleFilterProjectionPlane(*plane, sourceLayer, m_d->style)); - } } // for testing purposes only! KisLayerStyleProjectionPlane::KisLayerStyleProjectionPlane(KisLayer *sourceLayer, KisPSDLayerStyleSP layerStyle) : m_d(new Private) { init(sourceLayer, layerStyle); } void KisLayerStyleProjectionPlane::init(KisLayer *sourceLayer, KisPSDLayerStyleSP style) { KIS_SAFE_ASSERT_RECOVER_RETURN(sourceLayer); m_d->initSourcePlane(sourceLayer); m_d->style = style; { KisLayerStyleFilterProjectionPlane *dropShadow = new KisLayerStyleFilterProjectionPlane(sourceLayer); dropShadow->setStyle(new KisLsDropShadowFilter(KisLsDropShadowFilter::DropShadow), style); m_d->stylesBefore << toQShared(dropShadow); } - { - KisLayerStyleFilterProjectionPlane *innerShadow = - new KisLayerStyleFilterProjectionPlane(sourceLayer); - innerShadow->setStyle(new KisLsDropShadowFilter(KisLsDropShadowFilter::InnerShadow), style); - m_d->stylesAfter << toQShared(innerShadow); - } - { KisLayerStyleFilterProjectionPlane *outerGlow = new KisLayerStyleFilterProjectionPlane(sourceLayer); outerGlow->setStyle(new KisLsDropShadowFilter(KisLsDropShadowFilter::OuterGlow), style); m_d->stylesAfter << toQShared(outerGlow); } { - KisLayerStyleFilterProjectionPlane *innerGlow = + KisLayerStyleFilterProjectionPlane *stroke = new KisLayerStyleFilterProjectionPlane(sourceLayer); - innerGlow->setStyle(new KisLsDropShadowFilter(KisLsDropShadowFilter::InnerGlow), style); - m_d->stylesAfter << toQShared(innerGlow); + stroke->setStyle(new KisLsStrokeFilter(), style); + m_d->stylesAfter << toQShared(stroke); } { - KisLayerStyleFilterProjectionPlane *satin = + KisLayerStyleFilterProjectionPlane *bevelEmboss = new KisLayerStyleFilterProjectionPlane(sourceLayer); - satin->setStyle(new KisLsSatinFilter(), style); - m_d->stylesAfter << toQShared(satin); + bevelEmboss->setStyle(new KisLsBevelEmbossFilter(), style); + m_d->stylesAfter << toQShared(bevelEmboss); } { - KisLayerStyleFilterProjectionPlane *colorOverlay = + KisLayerStyleFilterProjectionPlane *patternOverlay = new KisLayerStyleFilterProjectionPlane(sourceLayer); - colorOverlay->setStyle(new KisLsOverlayFilter(KisLsOverlayFilter::Color), style); - m_d->stylesAfter << toQShared(colorOverlay); + patternOverlay->setStyle(new KisLsOverlayFilter(KisLsOverlayFilter::Pattern), style); + m_d->stylesOverlay << toQShared(patternOverlay); } { KisLayerStyleFilterProjectionPlane *gradientOverlay = new KisLayerStyleFilterProjectionPlane(sourceLayer); gradientOverlay->setStyle(new KisLsOverlayFilter(KisLsOverlayFilter::Gradient), style); - m_d->stylesAfter << toQShared(gradientOverlay); + m_d->stylesOverlay << toQShared(gradientOverlay); } { - KisLayerStyleFilterProjectionPlane *patternOverlay = + KisLayerStyleFilterProjectionPlane *colorOverlay = new KisLayerStyleFilterProjectionPlane(sourceLayer); - patternOverlay->setStyle(new KisLsOverlayFilter(KisLsOverlayFilter::Pattern), style); - m_d->stylesAfter << toQShared(patternOverlay); + colorOverlay->setStyle(new KisLsOverlayFilter(KisLsOverlayFilter::Color), style); + m_d->stylesOverlay << toQShared(colorOverlay); } { - KisLayerStyleFilterProjectionPlane *stroke = + KisLayerStyleFilterProjectionPlane *satin = new KisLayerStyleFilterProjectionPlane(sourceLayer); - stroke->setStyle(new KisLsStrokeFilter(), style); - m_d->stylesAfter << toQShared(stroke); + satin->setStyle(new KisLsSatinFilter(), style); + m_d->stylesOverlay << toQShared(satin); } { - KisLayerStyleFilterProjectionPlane *bevelEmboss = + KisLayerStyleFilterProjectionPlane *innerGlow = new KisLayerStyleFilterProjectionPlane(sourceLayer); - bevelEmboss->setStyle(new KisLsBevelEmbossFilter(), style); - m_d->stylesAfter << toQShared(bevelEmboss); + innerGlow->setStyle(new KisLsDropShadowFilter(KisLsDropShadowFilter::InnerGlow), style); + m_d->stylesOverlay << toQShared(innerGlow); + } + + { + KisLayerStyleFilterProjectionPlane *innerShadow = + new KisLayerStyleFilterProjectionPlane(sourceLayer); + innerShadow->setStyle(new KisLsDropShadowFilter(KisLsDropShadowFilter::InnerShadow), style); + m_d->stylesOverlay << toQShared(innerShadow); } } KisLayerStyleProjectionPlane::~KisLayerStyleProjectionPlane() { } KisAbstractProjectionPlaneSP KisLayerStyleProjectionPlane::factoryObject(KisLayer *sourceLayer) { Q_ASSERT(sourceLayer); return toQShared(new KisLayerStyleProjectionPlane(sourceLayer)); } QRect KisLayerStyleProjectionPlane::recalculate(const QRect& rect, KisNodeSP filthyNode) { KisAbstractProjectionPlaneSP sourcePlane = m_d->sourceProjectionPlane.toStrongRef(); - QRect result = sourcePlane->recalculate(stylesNeedRect(rect), filthyNode); + QRect result = rect; if (m_d->style->isEnabled()) { - Q_FOREACH (const KisAbstractProjectionPlaneSP plane, m_d->stylesBefore) { - plane->recalculate(rect, filthyNode); - } + result = sourcePlane->recalculate(stylesNeedRect(rect), filthyNode); - Q_FOREACH (const KisAbstractProjectionPlaneSP plane, m_d->stylesAfter) { + Q_FOREACH (const KisAbstractProjectionPlaneSP plane, m_d->allStyles()) { plane->recalculate(rect, filthyNode); } + } else { + result = sourcePlane->recalculate(rect, filthyNode); } return result; } -void KisLayerStyleProjectionPlane::apply(KisPainter *painter, const QRect &rect) +void KisLayerStyleProjectionPlane::Private::applyComplexPlane(KisPainter *painter, + KisLayerStyleFilterProjectionPlaneSP plane, + const QRect &rect, + KisPaintDeviceSP originalClone) { - KisAbstractProjectionPlaneSP sourcePlane = m_d->sourceProjectionPlane.toStrongRef(); + if (plane->isEmpty()) return; - if (m_d->style->isEnabled()) { - Q_FOREACH (const KisAbstractProjectionPlaneSP plane, m_d->stylesBefore) { - plane->apply(painter, rect); - } + if (!plane->knockoutBlower()->isEmpty()) { + KisCachedPaintDevice::Guard d1(originalClone, cachedPaintDevice); + KisPaintDeviceSP mergedStyle = d1.device(); + mergedStyle->makeCloneFromRough(originalClone, rect); - sourcePlane->apply(painter, rect); + KisPainter overlayPainter(mergedStyle); + plane->apply(&overlayPainter, rect); + plane->knockoutBlower()->apply(painter, mergedStyle, rect); + + } else { + plane->apply(painter, rect); + } +} - Q_FOREACH (const KisAbstractProjectionPlaneSP plane, m_d->stylesAfter) { - plane->apply(painter, rect); +void KisLayerStyleProjectionPlane::apply(KisPainter *painter, const QRect &rect) +{ + KisLayerProjectionPlaneSP sourcePlane = m_d->sourceProjectionPlane.toStrongRef(); + + if (m_d->style->isEnabled()) { + if (m_d->hasOverlayStyles() || m_d->hasKnockoutStyles()) { + KisCachedPaintDevice::Guard d1(painter->device(), m_d->cachedPaintDevice); + KisPaintDeviceSP originalClone = d1.device(); + originalClone->makeCloneFromRough(painter->device(), rect); + + Q_FOREACH (const KisLayerStyleFilterProjectionPlaneSP plane, m_d->stylesBefore) { + m_d->applyComplexPlane(painter, plane, rect, originalClone); + } + + if (m_d->hasOverlayStyles()) { + KisCachedSelection::Guard s1(m_d->cachedSelection); + KisSelectionSP knockoutSelection = s1.selection(); + KisLsUtils::selectionFromAlphaChannel(m_d->sourceLayer->projection(), knockoutSelection, rect); + + KisCachedPaintDevice::Guard d2(painter->device(), m_d->cachedPaintDevice); + KisPaintDeviceSP sourceProjection = d2.device(); + sourceProjection->makeCloneFromRough(painter->device(), rect); + + { + KisPainter overlayPainter(sourceProjection); + sourcePlane->applyMaxOutAlpha(&overlayPainter, rect); + + Q_FOREACH (const KisAbstractProjectionPlaneSP plane, m_d->stylesOverlay) { + plane->apply(&overlayPainter, rect); + } + } + + KisLayerStyleKnockoutBlower blower; + blower.setKnockoutSelection(knockoutSelection); + blower.apply(painter, sourceProjection, rect); + + blower.resetKnockoutSelection(); + } else { + sourcePlane->apply(painter, rect); + } + + Q_FOREACH (KisLayerStyleFilterProjectionPlaneSP plane, m_d->stylesAfter) { + m_d->applyComplexPlane(painter, plane, rect, originalClone); + } + } else { + Q_FOREACH (const KisAbstractProjectionPlaneSP plane, m_d->stylesBefore) { + plane->apply(painter, rect); + } + + sourcePlane->apply(painter, rect); + + Q_FOREACH (const KisAbstractProjectionPlaneSP plane, m_d->stylesAfter) { + plane->apply(painter, rect); + } } } else { sourcePlane->apply(painter, rect); } } KisPaintDeviceList KisLayerStyleProjectionPlane::getLodCapableDevices() const { KisPaintDeviceList list; KisAbstractProjectionPlaneSP sourcePlane = m_d->sourceProjectionPlane.toStrongRef(); if (m_d->style->isEnabled()) { - Q_FOREACH (const KisAbstractProjectionPlaneSP plane, m_d->stylesBefore) { + Q_FOREACH (const KisAbstractProjectionPlaneSP plane, m_d->allStyles()) { list << plane->getLodCapableDevices(); } list << sourcePlane->getLodCapableDevices(); - - Q_FOREACH (const KisAbstractProjectionPlaneSP plane, m_d->stylesAfter) { - list << plane->getLodCapableDevices(); - } } else { list << sourcePlane->getLodCapableDevices(); } return list; } QRect KisLayerStyleProjectionPlane::needRect(const QRect &rect, KisLayer::PositionToFilthy pos) const { /** * Need rect should also be adjust for the layers that generate their 'original' * based on the contents of the underlying layers like KisAdjustmentLayer * * \see bug 390299 */ QRect needRect = rect; const bool adjustmentAboveDirty = m_d->dependsOnLowerNodes && (pos & KisLayer::N_FILTHY || pos & KisLayer::N_ABOVE_FILTHY); if (m_d->style->isEnabled() && adjustmentAboveDirty) { needRect |= stylesNeedRect(rect); } KisAbstractProjectionPlaneSP sourcePlane = m_d->sourceProjectionPlane.toStrongRef(); needRect = sourcePlane->needRect(needRect, pos); return needRect; } QRect KisLayerStyleProjectionPlane::changeRect(const QRect &rect, KisLayer::PositionToFilthy pos) const { KisAbstractProjectionPlaneSP sourcePlane = m_d->sourceProjectionPlane.toStrongRef(); QRect layerChangeRect = sourcePlane->changeRect(rect, pos); QRect changeRect = layerChangeRect; if (m_d->style->isEnabled()) { - Q_FOREACH (const KisAbstractProjectionPlaneSP plane, m_d->stylesBefore) { - changeRect |= plane->changeRect(layerChangeRect, KisLayer::N_ABOVE_FILTHY); - } - - Q_FOREACH (const KisAbstractProjectionPlaneSP plane, m_d->stylesAfter) { + Q_FOREACH (const KisAbstractProjectionPlaneSP plane, m_d->allStyles()) { changeRect |= plane->changeRect(layerChangeRect, KisLayer::N_ABOVE_FILTHY); } } return changeRect; } QRect KisLayerStyleProjectionPlane::accessRect(const QRect &rect, KisLayer::PositionToFilthy pos) const { KisAbstractProjectionPlaneSP sourcePlane = m_d->sourceProjectionPlane.toStrongRef(); QRect accessRect = sourcePlane->accessRect(rect, pos); if (m_d->style->isEnabled()) { - Q_FOREACH (const KisAbstractProjectionPlaneSP plane, m_d->stylesBefore) { - accessRect |= plane->accessRect(rect, KisLayer::N_ABOVE_FILTHY); - } - - Q_FOREACH (const KisAbstractProjectionPlaneSP plane, m_d->stylesAfter) { + Q_FOREACH (const KisAbstractProjectionPlaneSP plane, m_d->allStyles()) { accessRect |= plane->accessRect(rect, KisLayer::N_ABOVE_FILTHY); } } return accessRect; } QRect KisLayerStyleProjectionPlane::needRectForOriginal(const QRect &rect) const { /** * Need rect should also be adjust for the layers that generate their 'original' * based on the contents of the child layers like KisGroupLayer * * \see bug 366419 */ QRect needRect = rect; if (m_d->style->isEnabled()) { needRect |= stylesNeedRect(needRect); } KisAbstractProjectionPlaneSP sourcePlane = m_d->sourceProjectionPlane.toStrongRef(); needRect = sourcePlane->needRectForOriginal(needRect); return needRect; } QRect KisLayerStyleProjectionPlane::stylesNeedRect(const QRect &rect) const { QRect needRect = rect; - Q_FOREACH (const KisAbstractProjectionPlaneSP plane, m_d->stylesBefore) { - needRect |= plane->needRect(rect, KisLayer::N_ABOVE_FILTHY); - } - - Q_FOREACH (const KisAbstractProjectionPlaneSP plane, m_d->stylesAfter) { + Q_FOREACH (const KisAbstractProjectionPlaneSP plane, m_d->allStyles()) { needRect |= plane->needRect(rect, KisLayer::N_ABOVE_FILTHY); } return needRect; } diff --git a/libs/image/layerstyles/kis_ls_bevel_emboss_filter.cpp b/libs/image/layerstyles/kis_ls_bevel_emboss_filter.cpp index 0702cd7e39..6dd739390c 100644 --- a/libs/image/layerstyles/kis_ls_bevel_emboss_filter.cpp +++ b/libs/image/layerstyles/kis_ls_bevel_emboss_filter.cpp @@ -1,502 +1,519 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * See LayerFX plugin for Gimp as a reference implementation of this style: * http://registry.gimp.org/node/186 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_ls_bevel_emboss_filter.h" #include #include #include #include #include #include "psd.h" #include "kis_convolution_kernel.h" #include "kis_convolution_painter.h" #include "kis_gaussian_kernel.h" #include "kis_pixel_selection.h" #include "kis_fill_painter.h" #include "kis_gradient_painter.h" #include "kis_iterator_ng.h" #include "kis_random_accessor_ng.h" #include "kis_psd_layer_style.h" #include "kis_layer_style_filter_environment.h" #include "kis_ls_utils.h" #include "gimp_bump_map.h" #include "kis_transaction.h" #include "kis_multiple_projection.h" +#include "kis_cached_paint_device.h" KisLsBevelEmbossFilter::KisLsBevelEmbossFilter() : KisLayerStyleFilter(KoID("lsstroke", i18n("Stroke (style)"))) { } KisLayerStyleFilter *KisLsBevelEmbossFilter::clone() const { return new KisLsBevelEmbossFilter(*this); } KisLsBevelEmbossFilter::KisLsBevelEmbossFilter(const KisLsBevelEmbossFilter &rhs) : KisLayerStyleFilter(rhs) { } void paintBevelSelection(KisPixelSelectionSP srcSelection, KisPixelSelectionSP dstSelection, const QRect &applyRect, int size, int initialSize, - bool invert) + bool invert, + KisLayerStyleFilterEnvironment *env) { - KisSelectionSP tmpBaseSelection = new KisSelection(new KisSelectionEmptyBounds(0)); + KisCachedSelection::Guard s1(*env->cachedSelection()); + KisSelectionSP tmpBaseSelection = s1.selection(); KisPixelSelectionSP tmpSelection = tmpBaseSelection->pixelSelection(); // NOTE: we are not using createCompositionSourceDevice() intentionally, // because the source device doesn't have alpha channel - KisPixelSelectionSP fillDevice = new KisPixelSelection(); + KisCachedSelection::Guard s2(*env->cachedSelection()); + KisPixelSelectionSP fillDevice = s2.selection()->pixelSelection(); KisPainter gc(dstSelection); gc.setCompositeOp(COMPOSITE_COPY); for (int i = 0; i < size; i++) { const int growSize = initialSize - i - 1; quint8 selectedness = invert ? qRound(qreal(size - i - 1) / size * 255.0) : qRound(qreal(i + 1) / size * 255.0); fillDevice->setDefaultPixel(KoColor(&selectedness, fillDevice->colorSpace())); tmpSelection->makeCloneFromRough(srcSelection, srcSelection->selectedRect()); QRect changeRect = KisLsUtils::growSelectionUniform(tmpSelection, growSize, applyRect); gc.setSelection(tmpBaseSelection); gc.bitBlt(changeRect.topLeft(), fillDevice, changeRect); } } struct ContrastOp { static const bool supportsCaching = false; ContrastOp(qreal contrast) : m_contrast(contrast) { } int operator() (int iValue) { qreal value = qreal(iValue - 127) / 127.0; qreal slant = std::tan ((m_contrast + 1) * M_PI_4); value = (value - 0.5) * slant + 0.5; return qRound(value * 255.0); } private: qreal m_contrast; }; struct HighlightsFetchOp { static const bool supportsCaching = true; int operator() (int value) { return qRound(qMax(0, value - 127) * (255.0 / (255 - 127))); } }; struct ShadowsFetchOp { static const bool supportsCaching = true; int operator() (int value) { return 255 - qRound(qMin(value, 127) * (255.0 / 127.0)); } }; template void mapPixelValues(KisPixelSelectionSP srcSelection, KisPixelSelectionSP dstSelection, MapOp mapOp, const QRect &applyRect) { static quint8 mapTable[256]; static bool mapInitialized = false; if (!MapOp::supportsCaching || !mapInitialized) { mapInitialized = true; for (int i = 0; i < 256; i++) { mapTable[i] = mapOp(i); } } KisSequentialConstIterator srcIt(srcSelection, applyRect); KisSequentialIterator dstIt(dstSelection, applyRect); while (srcIt.nextPixel() && dstIt.nextPixel()) { const quint8 *srcPtr = srcIt.rawDataConst(); quint8 *dstPtr = dstIt.rawData(); *dstPtr = mapTable[*srcPtr]; } } template void mapPixelValues(KisPixelSelectionSP dstSelection, MapOp mapOp, const QRect &applyRect) { static quint8 mapTable[256]; static bool mapInitialized = false; if (!MapOp::supportsCaching || !mapInitialized) { mapInitialized = true; for (int i = 0; i < 256; i++) { mapTable[i] = mapOp(i); } } KisSequentialIterator dstIt(dstSelection, applyRect); while (dstIt.nextPixel()) { quint8 *dstPtr = dstIt.rawData(); *dstPtr = mapTable[*dstPtr]; } } struct BevelEmbossRectCalculator { BevelEmbossRectCalculator(const QRect &applyRect, const psd_layer_effects_bevel_emboss *config) { shadowHighlightsFinalRect = applyRect; applyGaussianRect = shadowHighlightsFinalRect; applyGlossContourRect = KisLsUtils::growRectFromRadius(applyGaussianRect, config->soften()); applyBumpmapRect = applyGlossContourRect; applyContourRect = applyBumpmapRect; applyTextureRect = applyContourRect; applyBevelRect = calcBevelNeedRect(applyTextureRect, config); initialFetchRect = kisGrowRect(applyBevelRect, 1); } QRect totalChangeRect(const QRect &applyRect, const psd_layer_effects_bevel_emboss *config) { QRect changeRect = calcBevelChangeRect(applyRect, config); changeRect = kisGrowRect(changeRect, 1); // bumpmap method changeRect = KisLsUtils::growRectFromRadius(changeRect, config->soften()); return changeRect; } QRect totalNeedRect(const QRect &applyRect, const psd_layer_effects_bevel_emboss *config) { QRect changeRect = applyRect; changeRect = KisLsUtils::growRectFromRadius(changeRect, config->soften()); changeRect = kisGrowRect(changeRect, 1); // bumpmap method changeRect = calcBevelNeedRect(applyRect, config); return changeRect; } QRect initialFetchRect; QRect applyBevelRect; QRect applyTextureRect; QRect applyContourRect; QRect applyBumpmapRect; QRect applyGlossContourRect; QRect applyGaussianRect; QRect shadowHighlightsFinalRect; private: QRect calcBevelChangeRect(const QRect &applyRect, const psd_layer_effects_bevel_emboss *config) { const int size = config->size(); int limitingGrowSize = 0; switch (config->style()) { case psd_bevel_outer_bevel: limitingGrowSize = size; break; case psd_bevel_inner_bevel: limitingGrowSize = 0; break; case psd_bevel_emboss: { const int initialSize = std::ceil(qreal(size) / 2.0); limitingGrowSize = initialSize; break; } case psd_bevel_pillow_emboss: { const int halfSizeC = std::ceil(qreal(size) / 2.0); limitingGrowSize = halfSizeC; break; } case psd_bevel_stroke_emboss: warnKrita << "WARNING: Stroke Emboss style is not implemented yet!"; return applyRect; } return kisGrowRect(applyRect, limitingGrowSize); } QRect calcBevelNeedRect(const QRect &applyRect, const psd_layer_effects_bevel_emboss *config) { const int size = config->size(); int limitingGrowSize = size; return kisGrowRect(applyRect, limitingGrowSize); } }; void KisLsBevelEmbossFilter::applyBevelEmboss(KisPaintDeviceSP srcDevice, KisMultipleProjection *dst, const QRect &applyRect, const psd_layer_effects_bevel_emboss *config, KisLayerStyleFilterEnvironment *env) const { if (applyRect.isEmpty()) return; BevelEmbossRectCalculator d(applyRect, config); - KisSelectionSP baseSelection = KisLsUtils::selectionFromAlphaChannel(srcDevice, d.initialFetchRect); + KisCachedSelection::Guard s1(*env->cachedSelection()); + KisSelectionSP baseSelection = s1.selection(); + KisLsUtils::selectionFromAlphaChannel(srcDevice, baseSelection, d.initialFetchRect); + KisPixelSelectionSP selection = baseSelection->pixelSelection(); //selection->convertToQImage(0, QRect(0,0,300,300)).save("0_selection_initial.png"); const int size = config->size(); int limitingGrowSize = 0; - KisPixelSelectionSP bumpmapSelection = new KisPixelSelection(new KisSelectionEmptyBounds(0)); + KisCachedSelection::Guard s2(*env->cachedSelection()); + KisPixelSelectionSP bumpmapSelection = s2.selection()->pixelSelection(); switch (config->style()) { case psd_bevel_outer_bevel: - paintBevelSelection(selection, bumpmapSelection, d.applyBevelRect, size, size, false); + paintBevelSelection(selection, bumpmapSelection, d.applyBevelRect, size, size, false, env); limitingGrowSize = size; break; case psd_bevel_inner_bevel: - paintBevelSelection(selection, bumpmapSelection, d.applyBevelRect, size, 0, false); + paintBevelSelection(selection, bumpmapSelection, d.applyBevelRect, size, 0, false, env); limitingGrowSize = 0; break; case psd_bevel_emboss: { const int initialSize = std::ceil(qreal(size) / 2.0); - paintBevelSelection(selection, bumpmapSelection, d.applyBevelRect, size, initialSize, false); + paintBevelSelection(selection, bumpmapSelection, d.applyBevelRect, size, initialSize, false, env); limitingGrowSize = initialSize; break; } case psd_bevel_pillow_emboss: { const int halfSizeF = std::floor(qreal(size) / 2.0); const int halfSizeC = std::ceil(qreal(size) / 2.0); // TODO: probably not correct! - paintBevelSelection(selection, bumpmapSelection, d.applyBevelRect, halfSizeC, halfSizeC, false); - paintBevelSelection(selection, bumpmapSelection, d.applyBevelRect, halfSizeF, 0, true); + paintBevelSelection(selection, bumpmapSelection, d.applyBevelRect, halfSizeC, halfSizeC, false, env); + paintBevelSelection(selection, bumpmapSelection, d.applyBevelRect, halfSizeF, 0, true, env); limitingGrowSize = halfSizeC; break; } case psd_bevel_stroke_emboss: warnKrita << "WARNING: Stroke Emboss style is not implemented yet!"; return; } - KisPixelSelectionSP limitingSelection = new KisPixelSelection(*selection); + KisCachedSelection::Guard s3(*env->cachedSelection()); + KisPixelSelectionSP limitingSelection = s3.selection()->pixelSelection(); + limitingSelection->makeCloneFromRough(selection, selection->selectedRect()); { QRect changeRectUnused = KisLsUtils::growSelectionUniform(limitingSelection, limitingGrowSize, d.applyBevelRect); Q_UNUSED(changeRectUnused); } //bumpmapSelection->convertToQImage(0, QRect(0,0,300,300)).save("1_selection_xconv.png"); if (config->textureEnabled()) { - KisPixelSelectionSP textureSelection = new KisPixelSelection(new KisSelectionEmptyBounds(0)); + KisCachedSelection::Guard s4(*env->cachedSelection()); + KisPixelSelectionSP textureSelection = s4.selection()->pixelSelection(); KisLsUtils::fillPattern(textureSelection, d.applyTextureRect, env, config->textureScale(), config->texturePattern(), config->textureHorizontalPhase(), config->textureVerticalPhase(), config->textureAlignWithLayer()); int contrastadj = 0; { using namespace std; int tex_depth = config->textureDepth(); if (tex_depth >= 0.0) { if (tex_depth <= 100.0) { contrastadj = int(qRound((1-(tex_depth/100.0)) * -127)); } else { contrastadj = int(qRound(((tex_depth-100.0)/900.0) * 127)); } } else { textureSelection->invert(); if (tex_depth >= -100.0) { contrastadj = int(qRound((1-(abs(tex_depth)/100.0)) * -127)); } else { contrastadj = int(qRound(((abs(tex_depth)-100.0)/900.0) * 127)); } } } qreal contrast = qBound(-1.0, qreal(contrastadj) / 127.0, 1.0); mapPixelValues(textureSelection, ContrastOp(contrast), d.applyTextureRect); { KisPainter gc(bumpmapSelection); gc.setCompositeOp(COMPOSITE_MULT); gc.bitBlt(d.applyTextureRect.topLeft(), textureSelection, d.applyTextureRect); gc.end(); } } //bumpmapSelection->convertToQImage(0, QRect(0,0,300,300)).save("15_selection_texture.png"); if (config->contourEnabled()) { if (config->range() != KisLsUtils::FULL_PERCENT_RANGE) { KisLsUtils::adjustRange(bumpmapSelection, d.applyContourRect, config->range()); } KisLsUtils::applyContourCorrection(bumpmapSelection, d.applyContourRect, config->contourLookupTable(), config->antiAliased(), true); } bumpmap_vals_t bmvals; bmvals.azimuth = config->angle(); bmvals.elevation = config->altitude(); bmvals.depth = config->depth(); bmvals.ambient = 0; bmvals.compensate = true; bmvals.invert = config->direction() == psd_direction_down; bmvals.type = LINEAR; bumpmap(bumpmapSelection, d.applyBumpmapRect, bmvals); //bumpmapSelection->convertToQImage(0, QRect(0,0,300,300)).save("3_selection_bumpmap.png"); { // TODO: optimize! KisLsUtils::applyContourCorrection(bumpmapSelection, d.applyGlossContourRect, config->glossContourLookupTable(), config->glossAntiAliased(), true); } if (config->soften()) { KisLsUtils::applyGaussianWithTransaction(bumpmapSelection, d.applyGaussianRect, config->soften()); } if (config->textureEnabled() && config->textureInvert()) { bumpmapSelection->invert(); } selection->clear(); mapPixelValues(bumpmapSelection, selection, ShadowsFetchOp(), d.shadowHighlightsFinalRect); selection->applySelection(limitingSelection, SELECTION_INTERSECT); //dstDevice->convertToQImage(0, QRect(0,0,300,300)).save("4_dst_before_apply.png"); //selection->convertToQImage(0, QRect(0,0,300,300)).save("4_shadows_sel.png"); { KisPaintDeviceSP dstDevice = dst->getProjection("00_bevel_shadow", config->shadowBlendMode(), config->shadowOpacity(), QBitArray(), srcDevice); const KoColor fillColor(config->shadowColor(), dstDevice->colorSpace()); const QRect &fillRect = d.shadowHighlightsFinalRect; - KisPaintDeviceSP fillDevice = new KisPaintDevice(dstDevice->colorSpace()); + + KisCachedPaintDevice::Guard d1(dstDevice, *env->cachedPaintDevice()); + KisPaintDeviceSP fillDevice = d1.device(); fillDevice->setDefaultPixel(fillColor); KisPainter::copyAreaOptimized(fillRect.topLeft(), fillDevice, dstDevice, fillRect, baseSelection); } selection->clear(); mapPixelValues(bumpmapSelection, selection, HighlightsFetchOp(), d.shadowHighlightsFinalRect); selection->applySelection(limitingSelection, SELECTION_INTERSECT); //selection->convertToQImage(0, QRect(0,0,300,300)).save("5_highlights_sel.png"); { KisPaintDeviceSP dstDevice = dst->getProjection("01_bevel_highlight", config->highlightBlendMode(), config->highlightOpacity(), QBitArray(), srcDevice); const KoColor fillColor(config->highlightColor(), dstDevice->colorSpace()); const QRect &fillRect = d.shadowHighlightsFinalRect; - KisPaintDeviceSP fillDevice = new KisPaintDevice(dstDevice->colorSpace()); + + KisCachedPaintDevice::Guard d1(dstDevice, *env->cachedPaintDevice()); + KisPaintDeviceSP fillDevice = d1.device(); fillDevice->setDefaultPixel(fillColor); KisPainter::copyAreaOptimized(fillRect.topLeft(), fillDevice, dstDevice, fillRect, baseSelection); } } void KisLsBevelEmbossFilter::processDirectly(KisPaintDeviceSP src, KisMultipleProjection *dst, - const QRect &applyRect, - KisPSDLayerStyleSP style, - KisLayerStyleFilterEnvironment *env) const + KisLayerStyleKnockoutBlower *blower, + const QRect &applyRect, + KisPSDLayerStyleSP style, + KisLayerStyleFilterEnvironment *env) const { Q_UNUSED(env); + Q_UNUSED(blower); KIS_ASSERT_RECOVER_RETURN(style); const psd_layer_effects_bevel_emboss *config = style->bevelAndEmboss(); if (!KisLsUtils::checkEffectEnabled(config, dst)) return; KisLsUtils::LodWrapper w(env->currentLevelOfDetail(), config); applyBevelEmboss(src, dst, applyRect, w.config, env); } QRect KisLsBevelEmbossFilter::neededRect(const QRect &rect, KisPSDLayerStyleSP style, KisLayerStyleFilterEnvironment *env) const { const psd_layer_effects_bevel_emboss *config = style->bevelAndEmboss(); if (!config->effectEnabled()) return rect; KisLsUtils::LodWrapper w(env->currentLevelOfDetail(), config); BevelEmbossRectCalculator d(rect, w.config); return d.totalNeedRect(rect, w.config); } QRect KisLsBevelEmbossFilter::changedRect(const QRect &rect, KisPSDLayerStyleSP style, KisLayerStyleFilterEnvironment *env) const { const psd_layer_effects_bevel_emboss *config = style->bevelAndEmboss(); if (!config->effectEnabled()) return rect; KisLsUtils::LodWrapper w(env->currentLevelOfDetail(), config); BevelEmbossRectCalculator d(rect, w.config); return d.totalChangeRect(rect, w.config); } diff --git a/libs/image/layerstyles/kis_ls_bevel_emboss_filter.h b/libs/image/layerstyles/kis_ls_bevel_emboss_filter.h index 32787846ae..914f40c82f 100644 --- a/libs/image/layerstyles/kis_ls_bevel_emboss_filter.h +++ b/libs/image/layerstyles/kis_ls_bevel_emboss_filter.h @@ -1,57 +1,58 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_LS_BEVEL_EMBOSS_FILTER_H #define KIS_LS_BEVEL_EMBOSS_FILTER_H #include #include "kis_layer_style_filter.h" #include struct psd_layer_effects_bevel_emboss; class KRITAIMAGE_EXPORT KisLsBevelEmbossFilter : public KisLayerStyleFilter { public: KisLsBevelEmbossFilter(); KisLayerStyleFilter* clone() const override; void processDirectly(KisPaintDeviceSP src, KisMultipleProjection *dst, + KisLayerStyleKnockoutBlower *blower, const QRect &applyRect, KisPSDLayerStyleSP style, KisLayerStyleFilterEnvironment *env) const override; QRect neededRect(const QRect & rect, KisPSDLayerStyleSP style, KisLayerStyleFilterEnvironment *env) const override; QRect changedRect(const QRect & rect, KisPSDLayerStyleSP style, KisLayerStyleFilterEnvironment *env) const override; private: KisLsBevelEmbossFilter(const KisLsBevelEmbossFilter &rhs); void applyBevelEmboss(KisPaintDeviceSP srcDevice, KisMultipleProjection *dst, const QRect &applyRect, const psd_layer_effects_bevel_emboss *config, KisLayerStyleFilterEnvironment *env) const; }; #endif diff --git a/libs/image/layerstyles/kis_ls_drop_shadow_filter.cpp b/libs/image/layerstyles/kis_ls_drop_shadow_filter.cpp index 39581b8951..562df8a4b3 100644 --- a/libs/image/layerstyles/kis_ls_drop_shadow_filter.cpp +++ b/libs/image/layerstyles/kis_ls_drop_shadow_filter.cpp @@ -1,294 +1,299 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_ls_drop_shadow_filter.h" #include #include #include #include #include "psd.h" #include "kis_convolution_kernel.h" #include "kis_convolution_painter.h" #include "kis_gaussian_kernel.h" #include "kis_pixel_selection.h" #include "kis_fill_painter.h" #include "kis_iterator_ng.h" #include "kis_random_accessor_ng.h" #include "kis_psd_layer_style.h" #include "kis_multiple_projection.h" #include "kis_ls_utils.h" #include "kis_layer_style_filter_environment.h" +#include "kis_cached_paint_device.h" KisLsDropShadowFilter::KisLsDropShadowFilter(Mode mode) : KisLayerStyleFilter(KoID("lsdropshadow", i18n("Drop Shadow (style)"))) , m_mode(mode) { } KisLsDropShadowFilter::KisLsDropShadowFilter(const KisLsDropShadowFilter &rhs) : KisLayerStyleFilter(rhs), m_mode(rhs.m_mode) { } KisLayerStyleFilter *KisLsDropShadowFilter::clone() const { return new KisLsDropShadowFilter(*this); } struct ShadowRectsData { enum Direction { NEED_RECT, CHANGE_RECT }; ShadowRectsData(const QRect &applyRect, const psd_layer_effects_context *context, const psd_layer_effects_shadow_base *shadow, Direction direction) { spread_size = (shadow->spread() * shadow->size() + 50) / 100; blur_size = shadow->size() - spread_size; offset = shadow->calculateOffset(context); // need rect calculation in reverse order dstRect = applyRect; const int directionCoeff = direction == NEED_RECT ? -1 : 1; srcRect = dstRect.translated(directionCoeff * offset); noiseNeedRect = shadow->noise() > 0 ? kisGrowRect(srcRect, KisLsUtils::noiseNeedBorder) : srcRect; blurNeedRect = blur_size ? KisLsUtils::growRectFromRadius(noiseNeedRect, blur_size) : noiseNeedRect; spreadNeedRect = spread_size ? KisLsUtils::growRectFromRadius(blurNeedRect, spread_size) : blurNeedRect; // dbgKrita << ppVar(dstRect); // dbgKrita << ppVar(srcRect); // dbgKrita << ppVar(noiseNeedRect); // dbgKrita << ppVar(blurNeedRect); // dbgKrita << ppVar(spreadNeedRect); } inline QRect finalNeedRect() const { return spreadNeedRect; } inline QRect finalChangeRect() const { // TODO: is it correct? return spreadNeedRect; } qint32 spread_size; qint32 blur_size; QPoint offset; QRect srcRect; QRect dstRect; QRect noiseNeedRect; QRect blurNeedRect; QRect spreadNeedRect; }; -void applyDropShadow(KisPaintDeviceSP srcDevice, - KisMultipleProjection *dst, - const QRect &applyRect, - const psd_layer_effects_context *context, - const psd_layer_effects_shadow_base *shadow, - const KisLayerStyleFilterEnvironment *env) +void KisLsDropShadowFilter::applyDropShadow(KisPaintDeviceSP srcDevice, + KisMultipleProjection *dst, + const QRect &applyRect, + const psd_layer_effects_context *context, + const psd_layer_effects_shadow_base *shadow, + KisLayerStyleFilterEnvironment *env) const { if (applyRect.isEmpty()) return; ShadowRectsData d(applyRect, context, shadow, ShadowRectsData::NEED_RECT); - KisSelectionSP baseSelection = - KisLsUtils::selectionFromAlphaChannel(srcDevice, d.spreadNeedRect); + KisCachedSelection::Guard s1(*env->cachedSelection()); + KisSelectionSP baseSelection = s1.selection(); + KisLsUtils::selectionFromAlphaChannel(srcDevice, baseSelection, d.spreadNeedRect); KisPixelSelectionSP selection = baseSelection->pixelSelection(); //selection->convertToQImage(0, QRect(0,0,300,300)).save("0_selection_initial.png"); if (shadow->invertsSelection()) { selection->invert(); } /** * Copy selection which will be erased from the original later */ + KisCachedSelection::Guard s2(*env->cachedSelection()); KisPixelSelectionSP knockOutSelection; if (shadow->knocksOut()) { - knockOutSelection = new KisPixelSelection(*selection); + knockOutSelection = s2.selection()->pixelSelection(); + knockOutSelection->makeCloneFromRough(selection, selection->selectedRect()); } if (shadow->technique() == psd_technique_precise) { KisLsUtils::findEdge(selection, d.blurNeedRect, true); } /** * Spread and blur the selection */ if (d.spread_size) { KisLsUtils::applyGaussianWithTransaction(selection, d.blurNeedRect, d.spread_size); // TODO: find out why in libpsd we pass false here. If we do so, // the result is fully black, which is not expected KisLsUtils::findEdge(selection, d.blurNeedRect, true /*shadow->edgeHidden()*/); } //selection->convertToQImage(0, QRect(0,0,300,300)).save("1_selection_spread.png"); if (d.blur_size) { KisLsUtils::applyGaussianWithTransaction(selection, d.noiseNeedRect, d.blur_size); } //selection->convertToQImage(0, QRect(0,0,300,300)).save("2_selection_blur.png"); if (shadow->range() != KisLsUtils::FULL_PERCENT_RANGE) { KisLsUtils::adjustRange(selection, d.noiseNeedRect, shadow->range()); } const psd_layer_effects_inner_glow *iglow = 0; if ((iglow = dynamic_cast(shadow)) && iglow->source() == psd_glow_center) { selection->invert(); } /** * Contour correction */ KisLsUtils::applyContourCorrection(selection, d.noiseNeedRect, shadow->contourLookupTable(), shadow->antiAliased(), shadow->edgeHidden()); //selection->convertToQImage(0, QRect(0,0,300,300)).save("3_selection_contour.png"); /** * Noise */ if (shadow->noise() > 0) { KisLsUtils::applyNoise(selection, d.srcRect, shadow->noise(), context, env); } //selection->convertToQImage(0, QRect(0,0,300,300)).save("4_selection_noise.png"); if (!d.offset.isNull()) { - selection->setX(d.offset.x()); - selection->setY(d.offset.y()); + selection->moveTo(selection->offset() + d.offset); } /** * Knock-out original outline of the device from the resulting shade */ if (shadow->knocksOut()) { KIS_ASSERT_RECOVER_RETURN(knockOutSelection); QRect knockOutRect = !shadow->invertsSelection() ? d.srcRect : d.spreadNeedRect; knockOutRect &= d.dstRect; KisPainter gc(selection); gc.setCompositeOp(COMPOSITE_ERASE); gc.bitBlt(knockOutRect.topLeft(), knockOutSelection, knockOutRect); } //selection->convertToQImage(0, QRect(0,0,300,300)).save("5_selection_knockout.png"); KisLsUtils::applyFinalSelection(KisMultipleProjection::defaultProjectionId(), baseSelection, srcDevice, dst, d.srcRect, d.dstRect, context, shadow, env); } const psd_layer_effects_shadow_base* KisLsDropShadowFilter::getShadowStruct(KisPSDLayerStyleSP style) const { const psd_layer_effects_shadow_base *config = 0; if (m_mode == DropShadow) { config = style->dropShadow(); } else if (m_mode == InnerShadow) { config = style->innerShadow(); } else if (m_mode == OuterGlow) { config = style->outerGlow(); } else if (m_mode == InnerGlow) { config = style->innerGlow(); } return config; } void KisLsDropShadowFilter::processDirectly(KisPaintDeviceSP src, KisMultipleProjection *dst, + KisLayerStyleKnockoutBlower *blower, const QRect &applyRect, KisPSDLayerStyleSP style, KisLayerStyleFilterEnvironment *env) const { + Q_UNUSED(blower); KIS_ASSERT_RECOVER_RETURN(style); const psd_layer_effects_shadow_base *config = getShadowStruct(style); if (!KisLsUtils::checkEffectEnabled(config, dst)) return; KisLsUtils::LodWrapper w(env->currentLevelOfDetail(), config); applyDropShadow(src, dst, applyRect, style->context(), w.config, env); } QRect KisLsDropShadowFilter::neededRect(const QRect &rect, KisPSDLayerStyleSP style, KisLayerStyleFilterEnvironment *env) const { const psd_layer_effects_shadow_base *shadowStruct = getShadowStruct(style); if (!shadowStruct->effectEnabled()) return rect; KisLsUtils::LodWrapper w(env->currentLevelOfDetail(), shadowStruct); ShadowRectsData d(rect, style->context(), w.config, ShadowRectsData::NEED_RECT); return rect | d.finalNeedRect(); } QRect KisLsDropShadowFilter::changedRect(const QRect &rect, KisPSDLayerStyleSP style, KisLayerStyleFilterEnvironment *env) const { const psd_layer_effects_shadow_base *shadowStruct = getShadowStruct(style); if (!shadowStruct->effectEnabled()) return rect; KisLsUtils::LodWrapper w(env->currentLevelOfDetail(), shadowStruct); ShadowRectsData d(rect, style->context(), w.config, ShadowRectsData::CHANGE_RECT); return style->context()->keep_original ? d.finalChangeRect() : rect | d.finalChangeRect(); } diff --git a/libs/image/layerstyles/kis_ls_drop_shadow_filter.h b/libs/image/layerstyles/kis_ls_drop_shadow_filter.h index 30c5cc23df..5494f56572 100644 --- a/libs/image/layerstyles/kis_ls_drop_shadow_filter.h +++ b/libs/image/layerstyles/kis_ls_drop_shadow_filter.h @@ -1,62 +1,70 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_LS_DROP_SHADOW_FILTER_H #define KIS_LS_DROP_SHADOW_FILTER_H #include #include "kis_layer_style_filter.h" #include class psd_layer_effects_shadow_base; class KRITAIMAGE_EXPORT KisLsDropShadowFilter : public KisLayerStyleFilter { public: enum Mode { DropShadow, InnerShadow, OuterGlow, InnerGlow }; KisLsDropShadowFilter(Mode mode = DropShadow); KisLayerStyleFilter* clone() const override; void processDirectly(KisPaintDeviceSP src, KisMultipleProjection *dst, + KisLayerStyleKnockoutBlower *blower, const QRect &applyRect, KisPSDLayerStyleSP style, KisLayerStyleFilterEnvironment *env) const override; QRect neededRect(const QRect & rect, KisPSDLayerStyleSP style, KisLayerStyleFilterEnvironment *env) const override; QRect changedRect(const QRect & rect, KisPSDLayerStyleSP style, KisLayerStyleFilterEnvironment *env) const override; private: KisLsDropShadowFilter(const KisLsDropShadowFilter &rhs); const psd_layer_effects_shadow_base* getShadowStruct(KisPSDLayerStyleSP style) const; + void applyDropShadow(KisPaintDeviceSP srcDevice, + KisMultipleProjection *dst, + const QRect &applyRect, + const psd_layer_effects_context *context, + const psd_layer_effects_shadow_base *shadow, + KisLayerStyleFilterEnvironment *env) const; + private: const Mode m_mode; }; #endif diff --git a/libs/image/layerstyles/kis_ls_overlay_filter.cpp b/libs/image/layerstyles/kis_ls_overlay_filter.cpp index 688fbb439d..9a424b9ec9 100644 --- a/libs/image/layerstyles/kis_ls_overlay_filter.cpp +++ b/libs/image/layerstyles/kis_ls_overlay_filter.cpp @@ -1,142 +1,131 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_ls_overlay_filter.h" #include #include #include #include #include "psd.h" #include "kis_convolution_kernel.h" #include "kis_convolution_painter.h" #include "kis_gaussian_kernel.h" #include "kis_pixel_selection.h" #include "kis_fill_painter.h" #include "kis_gradient_painter.h" #include "kis_iterator_ng.h" #include "kis_random_accessor_ng.h" #include "kis_psd_layer_style.h" #include "kis_layer_style_filter_environment.h" #include "kis_ls_utils.h" #include "kis_multiple_projection.h" KisLsOverlayFilter::KisLsOverlayFilter(Mode mode) : KisLayerStyleFilter(KoID("lsoverlay", i18n("Overlay (style)"))), m_mode(mode) { } KisLsOverlayFilter::KisLsOverlayFilter(const KisLsOverlayFilter &rhs) : KisLayerStyleFilter(rhs), m_mode(rhs.m_mode) { } KisLayerStyleFilter *KisLsOverlayFilter::clone() const { return new KisLsOverlayFilter(*this); } void KisLsOverlayFilter::applyOverlay(KisPaintDeviceSP srcDevice, KisMultipleProjection *dst, const QRect &applyRect, const psd_layer_effects_overlay_base *config, KisLayerStyleFilterEnvironment *env) const { if (applyRect.isEmpty()) return; - KisPaintDeviceSP overlayDevice = m_cachedDevices.getDevice(srcDevice); - KisLsUtils::fillOverlayDevice(overlayDevice, applyRect, config, env); - const QString compositeOp = config->blendMode(); - const quint8 opacityU8 = 255.0 / 100.0 * config->opacity(); + const quint8 opacityU8 = quint8(qRound(255.0 / 100.0 * config->opacity())); KisPaintDeviceSP dstDevice = dst->getProjection(KisMultipleProjection::defaultProjectionId(), compositeOp, opacityU8, QBitArray(), srcDevice); - KisPainter::copyAreaOptimized(applyRect.topLeft(), srcDevice, dstDevice, applyRect); - - KisPainter gc(dstDevice); - gc.setCompositeOp(COMPOSITE_OVER); - - const QBitArray channelFlags = srcDevice->colorSpace()->channelFlags(true, false); - gc.setChannelFlags(channelFlags); - gc.bitBlt(applyRect.topLeft(), overlayDevice, applyRect); - gc.end(); - - m_cachedDevices.putDevice(overlayDevice); + KisLsUtils::fillOverlayDevice(dstDevice, applyRect, config, env); } const psd_layer_effects_overlay_base* KisLsOverlayFilter::getOverlayStruct(KisPSDLayerStyleSP style) const { const psd_layer_effects_overlay_base *config = 0; if (m_mode == Color) { config = style->colorOverlay(); } else if (m_mode == Gradient) { config = style->gradientOverlay(); } else if (m_mode == Pattern) { config = style->patternOverlay(); } return config; } void KisLsOverlayFilter::processDirectly(KisPaintDeviceSP src, KisMultipleProjection *dst, + KisLayerStyleKnockoutBlower *blower, const QRect &applyRect, KisPSDLayerStyleSP style, KisLayerStyleFilterEnvironment *env) const { Q_UNUSED(env); + Q_UNUSED(blower); KIS_ASSERT_RECOVER_RETURN(style); const psd_layer_effects_overlay_base *config = getOverlayStruct(style); if (!KisLsUtils::checkEffectEnabled(config, dst)) return; applyOverlay(src, dst, applyRect, config, env); } QRect KisLsOverlayFilter::neededRect(const QRect &rect, KisPSDLayerStyleSP style, KisLayerStyleFilterEnvironment *env) const { Q_UNUSED(style); Q_UNUSED(env); return rect; } QRect KisLsOverlayFilter::changedRect(const QRect &rect, KisPSDLayerStyleSP style, KisLayerStyleFilterEnvironment *env) const { Q_UNUSED(style); Q_UNUSED(env); return rect; } diff --git a/libs/image/layerstyles/kis_ls_overlay_filter.h b/libs/image/layerstyles/kis_ls_overlay_filter.h index 79cfc6e6c6..73a624831c 100644 --- a/libs/image/layerstyles/kis_ls_overlay_filter.h +++ b/libs/image/layerstyles/kis_ls_overlay_filter.h @@ -1,70 +1,69 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_LS_OVERLAY_FILTER_H #define KIS_LS_OVERLAY_FILTER_H #include #include "kis_layer_style_filter.h" #include -#include "kis_cached_paint_device.h" struct psd_layer_effects_overlay_base; class KRITAIMAGE_EXPORT KisLsOverlayFilter : public KisLayerStyleFilter { public: enum Mode { Color, Gradient, Pattern }; public: KisLsOverlayFilter(Mode mode); KisLayerStyleFilter* clone() const override; void processDirectly(KisPaintDeviceSP src, KisMultipleProjection *dst, + KisLayerStyleKnockoutBlower *blower, const QRect &applyRect, KisPSDLayerStyleSP style, KisLayerStyleFilterEnvironment *env) const override; QRect neededRect(const QRect & rect, KisPSDLayerStyleSP style, KisLayerStyleFilterEnvironment *env) const override; QRect changedRect(const QRect & rect, KisPSDLayerStyleSP style, KisLayerStyleFilterEnvironment *env) const override; private: KisLsOverlayFilter(const KisLsOverlayFilter &rhs); const psd_layer_effects_overlay_base* getOverlayStruct(KisPSDLayerStyleSP style) const; void applyOverlay(KisPaintDeviceSP srcDevice, KisMultipleProjection *dst, const QRect &applyRect, const psd_layer_effects_overlay_base *config, KisLayerStyleFilterEnvironment *env) const; private: Mode m_mode; - mutable KisCachedPaintDevice m_cachedDevices; }; #endif diff --git a/libs/image/layerstyles/kis_ls_satin_filter.cpp b/libs/image/layerstyles/kis_ls_satin_filter.cpp index 4b96d0be63..90887601fe 100644 --- a/libs/image/layerstyles/kis_ls_satin_filter.cpp +++ b/libs/image/layerstyles/kis_ls_satin_filter.cpp @@ -1,240 +1,228 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_ls_satin_filter.h" #include #include #include #include "psd.h" #include "kis_convolution_kernel.h" #include "kis_convolution_painter.h" #include "kis_gaussian_kernel.h" #include "kis_pixel_selection.h" #include "kis_fill_painter.h" #include "kis_iterator_ng.h" #include "kis_random_accessor_ng.h" #include "kis_psd_layer_style.h" #include "kis_multiple_projection.h" #include "kis_ls_utils.h" #include "kis_layer_style_filter_environment.h" +#include "kis_cached_paint_device.h" KisLsSatinFilter::KisLsSatinFilter() : KisLayerStyleFilter(KoID("lssatin", i18n("Satin (style)"))) { } KisLsSatinFilter::KisLsSatinFilter(const KisLsSatinFilter &rhs) : KisLayerStyleFilter(rhs) { } KisLayerStyleFilter *KisLsSatinFilter::clone() const { return new KisLsSatinFilter(*this); } struct SatinRectsData { enum Direction { NEED_RECT, CHANGE_RECT }; SatinRectsData(const QRect &applyRect, const psd_layer_effects_context *context, const psd_layer_effects_satin *shadow, Direction direction) { Q_UNUSED(direction); blur_size = shadow->size(); offset = shadow->calculateOffset(context); // need rect calculation in reverse order dstRect = applyRect; srcRect = dstRect; int xGrow = qAbs(offset.x()); int yGrow = qAbs(offset.y()); satinNeedRect = srcRect.adjusted(-xGrow, -yGrow, xGrow, yGrow); blurNeedRect = blur_size ? KisLsUtils::growRectFromRadius(satinNeedRect, blur_size) : satinNeedRect; } inline QRect finalNeedRect() const { return blurNeedRect; } inline QRect finalChangeRect() const { // TODO: is it correct? return blurNeedRect; } qint32 blur_size; QPoint offset; QRect srcRect; QRect dstRect; QRect satinNeedRect; QRect blurNeedRect; }; void blendAndOffsetSatinSelection(KisPixelSelectionSP dstSelection, KisPixelSelectionSP srcSelection, const bool invert, const QPoint &offset, const QRect &applyRect) { KisSequentialIterator srcIt1(srcSelection, applyRect.translated(offset)); KisSequentialIterator srcIt2(srcSelection, applyRect.translated(-offset)); KisSequentialIterator dstIt(dstSelection, applyRect); while(dstIt.nextPixel() && srcIt1.nextPixel() && srcIt2.nextPixel()) { quint8 *dstPixelPtr = dstIt.rawData(); quint8 *src1PixelPtr = srcIt1.rawData(); quint8 *src2PixelPtr = srcIt2.rawData(); if (!invert) { - *dstPixelPtr = *dstPixelPtr * qAbs(*src1PixelPtr - *src2PixelPtr) >> 8; + *dstPixelPtr = qAbs(*src1PixelPtr - *src2PixelPtr); } else { - *dstPixelPtr = *dstPixelPtr * (255 - qAbs(*src1PixelPtr - *src2PixelPtr)) >> 8; + *dstPixelPtr = (255 - qAbs(*src1PixelPtr - *src2PixelPtr)); } } } -void applySatin(KisPaintDeviceSP srcDevice, - KisMultipleProjection *dst, - const QRect &applyRect, - const psd_layer_effects_context *context, - const psd_layer_effects_satin *config, - const KisLayerStyleFilterEnvironment *env) +//#include "kis_paint_device_debug_utils.h" + +void KisLsSatinFilter::applySatin(KisPaintDeviceSP srcDevice, + KisMultipleProjection *dst, + const QRect &applyRect, + const psd_layer_effects_context *context, + const psd_layer_effects_satin *config, + KisLayerStyleFilterEnvironment *env) const { if (applyRect.isEmpty()) return; SatinRectsData d(applyRect, context, config, SatinRectsData::NEED_RECT); - KisSelectionSP baseSelection = - KisLsUtils::selectionFromAlphaChannel(srcDevice, d.blurNeedRect); + KisCachedSelection::Guard s1(*env->cachedSelection()); + KisSelectionSP baseSelection = s1.selection(); + KisLsUtils::selectionFromAlphaChannel(srcDevice, baseSelection, d.blurNeedRect); KisPixelSelectionSP selection = baseSelection->pixelSelection(); - //selection->convertToQImage(0, QRect(0,0,300,300)).save("0_selection_initial.png"); - - KisPixelSelectionSP knockOutSelection = new KisPixelSelection(*selection); - knockOutSelection->invert(); + KisCachedSelection::Guard s2(*env->cachedSelection()); + KisPixelSelectionSP tempSelection = s2.selection()->pixelSelection(); + tempSelection->makeCloneFromRough(selection, selection->selectedRect()); - //knockOutSelection->convertToQImage(0, QRect(0,0,300,300)).save("1_saved_knockout_selection.png"); - - KisPixelSelectionSP tempSelection = new KisPixelSelection(*selection); + //KIS_DUMP_DEVICE_2(tempSelection, QRect(0,0,64,64), "00_selection", "dd"); KisLsUtils::applyGaussianWithTransaction(tempSelection, d.satinNeedRect, d.blur_size); - //tempSelection->convertToQImage(0, QRect(0,0,300,300)).save("2_selection_blurred.png"); + //KIS_DUMP_DEVICE_2(tempSelection, QRect(0,0,64,64), "01_gauss", "dd"); /** * Contour correction */ KisLsUtils::applyContourCorrection(tempSelection, d.satinNeedRect, config->contourLookupTable(), config->antiAliased(), config->edgeHidden()); - //tempSelection->convertToQImage(0, QRect(0,0,300,300)).save("3_selection_contour.png"); + //KIS_DUMP_DEVICE_2(tempSelection, QRect(0,0,64,64), "02_contour", "dd"); blendAndOffsetSatinSelection(selection, tempSelection, config->invert(), d.offset, d.dstRect); - //selection->convertToQImage(0, QRect(0,0,300,300)).save("3_selection_satin_applied.png"); - - /** - * Knock-out original outline of the device from the resulting shade - */ - if (config->knocksOut()) { - KisLsUtils::knockOutSelection(selection, - knockOutSelection, - d.srcRect, - d.dstRect, - d.finalNeedRect(), - config->invertsSelection()); - } - //selection->convertToQImage(0, QRect(0,0,300,300)).save("5_selection_knocked_out.png"); + //KIS_DUMP_DEVICE_2(selection, QRect(0,0,64,64), "03_blended", "dd"); KisLsUtils::applyFinalSelection(KisMultipleProjection::defaultProjectionId(), baseSelection, srcDevice, dst, d.srcRect, d.dstRect, context, config, env); - - //dstDevice->convertToQImage(0, QRect(0,0,300,300)).save("6_dst_final.png"); } void KisLsSatinFilter::processDirectly(KisPaintDeviceSP src, KisMultipleProjection *dst, + KisLayerStyleKnockoutBlower *blower, const QRect &applyRect, KisPSDLayerStyleSP style, KisLayerStyleFilterEnvironment *env) const { + Q_UNUSED(blower); KIS_ASSERT_RECOVER_RETURN(style); const psd_layer_effects_satin *config = style->satin(); if (!KisLsUtils::checkEffectEnabled(config, dst)) return; KisLsUtils::LodWrapper w(env->currentLevelOfDetail(), config); applySatin(src, dst, applyRect, style->context(), w.config, env); } QRect KisLsSatinFilter::neededRect(const QRect &rect, KisPSDLayerStyleSP style, KisLayerStyleFilterEnvironment *env) const { const psd_layer_effects_satin *config = style->satin(); if (!config->effectEnabled()) return rect; KisLsUtils::LodWrapper w(env->currentLevelOfDetail(), config); SatinRectsData d(rect, style->context(), w.config, SatinRectsData::NEED_RECT); return rect | d.finalNeedRect(); } QRect KisLsSatinFilter::changedRect(const QRect &rect, KisPSDLayerStyleSP style, KisLayerStyleFilterEnvironment *env) const { const psd_layer_effects_satin *config = style->satin(); if (!config->effectEnabled()) return rect; KisLsUtils::LodWrapper w(env->currentLevelOfDetail(), config); SatinRectsData d(rect, style->context(), w.config, SatinRectsData::CHANGE_RECT); return style->context()->keep_original ? d.finalChangeRect() : rect | d.finalChangeRect(); } diff --git a/libs/image/layerstyles/kis_ls_satin_filter.h b/libs/image/layerstyles/kis_ls_satin_filter.h index bb3c170e95..205eac0335 100644 --- a/libs/image/layerstyles/kis_ls_satin_filter.h +++ b/libs/image/layerstyles/kis_ls_satin_filter.h @@ -1,49 +1,58 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_LS_SATIN_FILTER_H #define KIS_LS_SATIN_FILTER_H #include #include "kis_layer_style_filter.h" #include class KRITAIMAGE_EXPORT KisLsSatinFilter : public KisLayerStyleFilter { public: KisLsSatinFilter(); KisLayerStyleFilter* clone() const override; void processDirectly(KisPaintDeviceSP src, KisMultipleProjection *dst, + KisLayerStyleKnockoutBlower *blower, const QRect &applyRect, KisPSDLayerStyleSP style, KisLayerStyleFilterEnvironment *env) const override; QRect neededRect(const QRect & rect, KisPSDLayerStyleSP style, KisLayerStyleFilterEnvironment *env) const override; QRect changedRect(const QRect & rect, KisPSDLayerStyleSP style, KisLayerStyleFilterEnvironment *env) const override; private: KisLsSatinFilter(const KisLsSatinFilter &rhs); + + + void applySatin(KisPaintDeviceSP srcDevice, + KisMultipleProjection *dst, + const QRect &applyRect, + const psd_layer_effects_context *context, + const psd_layer_effects_satin *config, + KisLayerStyleFilterEnvironment *env) const; }; #endif diff --git a/libs/image/layerstyles/kis_ls_stroke_filter.cpp b/libs/image/layerstyles/kis_ls_stroke_filter.cpp index db8d3890e6..db94fec07c 100644 --- a/libs/image/layerstyles/kis_ls_stroke_filter.cpp +++ b/libs/image/layerstyles/kis_ls_stroke_filter.cpp @@ -1,161 +1,171 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_ls_stroke_filter.h" #include #include #include #include #include "psd.h" #include "kis_convolution_kernel.h" #include "kis_convolution_painter.h" #include "kis_gaussian_kernel.h" #include "kis_pixel_selection.h" #include "kis_fill_painter.h" #include "kis_gradient_painter.h" #include "kis_iterator_ng.h" #include "kis_random_accessor_ng.h" #include "kis_psd_layer_style.h" #include "kis_layer_style_filter_environment.h" #include "kis_ls_utils.h" #include "kis_multiple_projection.h" +#include "kis_cached_paint_device.h" +#include "krita_utils.h" +#include "KisLayerStyleKnockoutBlower.h" + namespace { int borderSize(psd_stroke_position position, int size) { int border = 0; switch (position) { case psd_stroke_outside: - border = 2 * size + 1; + border = size + 1; break; case psd_stroke_center: - border = size + 1; + border = qCeil(0.5 * size) + 1; break; case psd_stroke_inside: border = 1; break; } return border; } } KisLsStrokeFilter::KisLsStrokeFilter() : KisLayerStyleFilter(KoID("lsstroke", i18n("Stroke (style)"))) { } KisLsStrokeFilter::KisLsStrokeFilter(const KisLsStrokeFilter &rhs) : KisLayerStyleFilter(rhs) { } KisLayerStyleFilter *KisLsStrokeFilter::clone() const { return new KisLsStrokeFilter(*this); } void KisLsStrokeFilter::applyStroke(KisPaintDeviceSP srcDevice, KisMultipleProjection *dst, + KisLayerStyleKnockoutBlower *blower, const QRect &applyRect, const psd_layer_effects_stroke *config, KisLayerStyleFilterEnvironment *env) const { if (applyRect.isEmpty()) return; const QRect needRect = kisGrowRect(applyRect, borderSize(config->position(), config->size())); - KisSelectionSP baseSelection = KisLsUtils::selectionFromAlphaChannel(srcDevice, needRect); + KisSelectionSP baseSelection = blower->knockoutSelectionLazy(); + + KisLsUtils::selectionFromAlphaChannel(srcDevice, baseSelection, needRect); KisPixelSelectionSP selection = baseSelection->pixelSelection(); +// KritaUtils::filterAlpha8Device(selection, needRect, +// [](quint8 pixel) { +// return pixel > 0 ? 255 : 0; +// }); +// KisGaussianKernel::applyGaussian(selection, needRect, 1.0, 1.0, QBitArray(), 0); + { - KisPixelSelectionSP knockOutSelection = new KisPixelSelection(new KisSelectionEmptyBounds(0)); + KisCachedSelection::Guard s2(*env->cachedSelection()); + KisPixelSelectionSP knockOutSelection = s2.selection()->pixelSelection(); knockOutSelection->makeCloneFromRough(selection, needRect); if (config->position() == psd_stroke_outside) { - KisGaussianKernel::applyDilate(selection, needRect, 2 * config->size(), QBitArray(), 0, true); - } else if (config->position() == psd_stroke_inside) { - KisGaussianKernel::applyErodeU8(knockOutSelection, needRect, 2 * config->size(), QBitArray(), 0, true); - } else if (config->position() == psd_stroke_center) { KisGaussianKernel::applyDilate(selection, needRect, config->size(), QBitArray(), 0, true); + } else if (config->position() == psd_stroke_inside) { KisGaussianKernel::applyErodeU8(knockOutSelection, needRect, config->size(), QBitArray(), 0, true); + } else if (config->position() == psd_stroke_center) { + KisGaussianKernel::applyDilate(selection, needRect, 0.5 * config->size(), QBitArray(), 0, true); + KisGaussianKernel::applyErodeU8(knockOutSelection, needRect, 0.5 * config->size(), QBitArray(), 0, true); } KisPainter gc(selection); gc.setCompositeOp(COMPOSITE_ERASE); gc.bitBlt(needRect.topLeft(), knockOutSelection, needRect); gc.end(); } - KisPaintDeviceSP fillDevice = new KisPaintDevice(srcDevice->colorSpace()); - KisLsUtils::fillOverlayDevice(fillDevice, applyRect, config, env); - - const QString compositeOp = config->blendMode(); - const quint8 opacityU8 = 255.0 / 100.0 * config->opacity(); + const quint8 opacityU8 = quint8(qRound(255.0 / 100.0 * config->opacity())); KisPaintDeviceSP dstDevice = dst->getProjection(KisMultipleProjection::defaultProjectionId(), compositeOp, opacityU8, QBitArray(), srcDevice); - - KisPainter::copyAreaOptimized(applyRect.topLeft(), fillDevice, dstDevice, applyRect, baseSelection); + KisLsUtils::fillOverlayDevice(dstDevice, applyRect, config, env); } void KisLsStrokeFilter::processDirectly(KisPaintDeviceSP src, KisMultipleProjection *dst, - const QRect &applyRect, - KisPSDLayerStyleSP style, - KisLayerStyleFilterEnvironment *env) const + KisLayerStyleKnockoutBlower *blower, + const QRect &applyRect, + KisPSDLayerStyleSP style, + KisLayerStyleFilterEnvironment *env) const { Q_UNUSED(env); KIS_ASSERT_RECOVER_RETURN(style); const psd_layer_effects_stroke *config = style->stroke(); if (!KisLsUtils::checkEffectEnabled(config, dst)) return; KisLsUtils::LodWrapper w(env->currentLevelOfDetail(), config); - applyStroke(src, dst, applyRect, w.config, env); + applyStroke(src, dst, blower, applyRect, w.config, env); } QRect KisLsStrokeFilter::neededRect(const QRect &rect, KisPSDLayerStyleSP style, KisLayerStyleFilterEnvironment *env) const { const psd_layer_effects_stroke *config = style->stroke(); if (!config->effectEnabled()) return rect; KisLsUtils::LodWrapper w(env->currentLevelOfDetail(), config); return kisGrowRect(rect, borderSize(w.config->position(), w.config->size())); } QRect KisLsStrokeFilter::changedRect(const QRect &rect, KisPSDLayerStyleSP style, KisLayerStyleFilterEnvironment *env) const { return neededRect(rect, style, env); } diff --git a/libs/image/layerstyles/kis_ls_stroke_filter.h b/libs/image/layerstyles/kis_ls_stroke_filter.h index 48a9c893ba..a9df43f64f 100644 --- a/libs/image/layerstyles/kis_ls_stroke_filter.h +++ b/libs/image/layerstyles/kis_ls_stroke_filter.h @@ -1,56 +1,58 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_LS_STROKE_FILTER_H #define KIS_LS_STROKE_FILTER_H #include #include #include "kis_layer_style_filter.h" struct psd_layer_effects_stroke; class KRITAIMAGE_EXPORT KisLsStrokeFilter : public KisLayerStyleFilter { public: KisLsStrokeFilter(); KisLayerStyleFilter* clone() const override; void processDirectly(KisPaintDeviceSP src, KisMultipleProjection *dst, + KisLayerStyleKnockoutBlower *blower, const QRect &applyRect, KisPSDLayerStyleSP style, KisLayerStyleFilterEnvironment *env) const override; QRect neededRect(const QRect & rect, KisPSDLayerStyleSP style, KisLayerStyleFilterEnvironment *env) const override; QRect changedRect(const QRect & rect, KisPSDLayerStyleSP style, KisLayerStyleFilterEnvironment *env) const override; private: KisLsStrokeFilter(const KisLsStrokeFilter &rhs); void applyStroke(KisPaintDeviceSP srcDevice, KisMultipleProjection *dst, + KisLayerStyleKnockoutBlower *blower, const QRect &applyRect, const psd_layer_effects_stroke *config, KisLayerStyleFilterEnvironment *env) const; }; #endif diff --git a/libs/image/layerstyles/kis_ls_utils.cpp b/libs/image/layerstyles/kis_ls_utils.cpp index 9cdae6b0ca..93b606ef05 100644 --- a/libs/image/layerstyles/kis_ls_utils.cpp +++ b/libs/image/layerstyles/kis_ls_utils.cpp @@ -1,588 +1,591 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_ls_utils.h" #include #include #include #include "psd.h" #include "kis_default_bounds.h" #include "kis_pixel_selection.h" #include "kis_random_accessor_ng.h" #include "kis_iterator_ng.h" #include "kis_convolution_kernel.h" #include "kis_convolution_painter.h" #include "kis_gaussian_kernel.h" #include "kis_fill_painter.h" #include "kis_gradient_painter.h" #include "kis_layer_style_filter_environment.h" #include "kis_selection_filters.h" #include "kis_multiple_projection.h" #include "kis_default_bounds_base.h" - +#include "kis_cached_paint_device.h" namespace KisLsUtils { QRect growSelectionUniform(KisPixelSelectionSP selection, int growSize, const QRect &applyRect) { QRect changeRect = applyRect; if (growSize > 0) { KisGrowSelectionFilter filter(growSize, growSize); changeRect = filter.changeRect(applyRect, selection->defaultBounds()); filter.process(selection, applyRect); } else if (growSize < 0) { KisShrinkSelectionFilter filter(qAbs(growSize), qAbs(growSize), false); changeRect = filter.changeRect(applyRect, selection->defaultBounds()); filter.process(selection, applyRect); } return changeRect; } - KisSelectionSP selectionFromAlphaChannel(KisPaintDeviceSP device, - const QRect &srcRect) + void selectionFromAlphaChannel(KisPaintDeviceSP srcDevice, + KisSelectionSP dstSelection, + const QRect &srcRect) { - const KoColorSpace *cs = device->colorSpace(); + const KoColorSpace *cs = srcDevice->colorSpace(); + - KisSelectionSP baseSelection = new KisSelection(new KisSelectionEmptyBounds(0)); - KisPixelSelectionSP selection = baseSelection->pixelSelection(); + KisPixelSelectionSP selection = dstSelection->pixelSelection(); - KisSequentialConstIterator srcIt(device, srcRect); + KisSequentialConstIterator srcIt(srcDevice, srcRect); KisSequentialIterator dstIt(selection, srcRect); while (srcIt.nextPixel() && dstIt.nextPixel()) { quint8 *dstPtr = dstIt.rawData(); const quint8* srcPtr = srcIt.rawDataConst(); *dstPtr = cs->opacityU8(srcPtr); } - return baseSelection; } void findEdge(KisPixelSelectionSP selection, const QRect &applyRect, const bool edgeHidden) { KisSequentialIterator dstIt(selection, applyRect); if (edgeHidden) { while(dstIt.nextPixel()) { quint8 *pixelPtr = dstIt.rawData(); *pixelPtr = (*pixelPtr < 24) ? *pixelPtr * 10 : 0xFF; } } else { while(dstIt.nextPixel()) { quint8 *pixelPtr = dstIt.rawData(); *pixelPtr = 0xFF; } } } QRect growRectFromRadius(const QRect &rc, int radius) { int halfSize = KisGaussianKernel::kernelSizeFromRadius(radius) / 2; return rc.adjusted(-halfSize, -halfSize, halfSize, halfSize); } void applyGaussianWithTransaction(KisPixelSelectionSP selection, const QRect &applyRect, qreal radius) { KisGaussianKernel::applyGaussian(selection, applyRect, radius, radius, QBitArray(), 0, true); } namespace Private { void getGradientTable(const KoAbstractGradient *gradient, QVector *table, const KoColorSpace *colorSpace) { KIS_ASSERT_RECOVER_RETURN(table->size() == 256); for (int i = 0; i < 256; i++) { gradient->colorAt(((*table)[i]), qreal(i) / 255.0); (*table)[i].convertTo(colorSpace); } } struct LinearGradientIndex { int popOneIndex(int selectionAlpha) { return 255 - selectionAlpha; } bool nextPixel() { return true; } }; struct JitterGradientIndex { JitterGradientIndex(const QRect &applyRect, int jitter, const KisLayerStyleFilterEnvironment *env) : randomSelection(env->cachedRandomSelection(applyRect)), noiseIt(randomSelection, applyRect), m_jitterCoeff(jitter * 255 / 100) { } int popOneIndex(int selectionAlpha) { int gradientIndex = 255 - selectionAlpha; gradientIndex += m_jitterCoeff * *noiseIt.rawDataConst() >> 8; gradientIndex &= 0xFF; return gradientIndex; } bool nextPixel() { return noiseIt.nextPixel(); } private: KisPixelSelectionSP randomSelection; KisSequentialConstIterator noiseIt; int m_jitterCoeff; }; template void applyGradientImpl(KisPaintDeviceSP device, KisPixelSelectionSP selection, const QRect &applyRect, const QVector &table, bool edgeHidden, IndexFetcher &indexFetcher) { KIS_ASSERT_RECOVER_RETURN( *table.first().colorSpace() == *device->colorSpace()); const KoColorSpace *cs = device->colorSpace(); const int pixelSize = cs->pixelSize(); KisSequentialConstIterator selIt(selection, applyRect); KisSequentialIterator dstIt(device, applyRect); if (edgeHidden) { while (selIt.nextPixel() && dstIt.nextPixel() && indexFetcher.nextPixel()) { quint8 selAlpha = *selIt.rawDataConst(); int gradientIndex = indexFetcher.popOneIndex(selAlpha); const KoColor &color = table[gradientIndex]; quint8 tableAlpha = color.opacityU8(); memcpy(dstIt.rawData(), color.data(), pixelSize); if (selAlpha < 24 && tableAlpha == 255) { tableAlpha = int(selAlpha) * 10 * tableAlpha >> 8; cs->setOpacity(dstIt.rawData(), tableAlpha, 1); } } } else { while (selIt.nextPixel() && dstIt.nextPixel() && indexFetcher.nextPixel()) { int gradientIndex = indexFetcher.popOneIndex(*selIt.rawDataConst()); const KoColor &color = table[gradientIndex]; memcpy(dstIt.rawData(), color.data(), pixelSize); } } } void applyGradient(KisPaintDeviceSP device, KisPixelSelectionSP selection, const QRect &applyRect, const QVector &table, bool edgeHidden, int jitter, const KisLayerStyleFilterEnvironment *env) { if (!jitter) { LinearGradientIndex fetcher; applyGradientImpl(device, selection, applyRect, table, edgeHidden, fetcher); } else { JitterGradientIndex fetcher(applyRect, jitter, env); applyGradientImpl(device, selection, applyRect, table, edgeHidden, fetcher); } } } const int noiseNeedBorder = 8; void applyNoise(KisPixelSelectionSP selection, const QRect &applyRect, int noise, const psd_layer_effects_context *context, - const KisLayerStyleFilterEnvironment *env) + KisLayerStyleFilterEnvironment *env) { Q_UNUSED(context); const QRect overlayRect = kisGrowRect(applyRect, noiseNeedBorder); KisPixelSelectionSP randomSelection = env->cachedRandomSelection(overlayRect); - KisPixelSelectionSP randomOverlay = new KisPixelSelection(); + + + KisCachedSelection::Guard s1(*env->cachedSelection()); + KisPixelSelectionSP randomOverlay = s1.selection()->pixelSelection(); KisSequentialConstIterator noiseIt(randomSelection, overlayRect); KisSequentialConstIterator srcIt(selection, overlayRect); KisRandomAccessorSP dstIt = randomOverlay->createRandomAccessorNG(overlayRect.x(), overlayRect.y()); while (noiseIt.nextPixel() && srcIt.nextPixel()) { int itX = noiseIt.x(); int itY = noiseIt.y(); int x = itX + (*noiseIt.rawDataConst() >> 4) - 8; int y = itY + (*noiseIt.rawDataConst() & 0x0F) - 8; x = (x + itX) >> 1; y = (y + itY) >> 1; dstIt->moveTo(x, y); quint8 dstAlpha = *dstIt->rawData(); quint8 srcAlpha = *srcIt.rawDataConst(); int value = qMin(255, dstAlpha + srcAlpha); *dstIt->rawData() = value; } noise = noise * 255 / 100; KisPainter gc(selection); gc.setOpacity(noise); gc.setCompositeOp(COMPOSITE_COPY); gc.bitBlt(applyRect.topLeft(), randomOverlay, applyRect); } //const int FULL_PERCENT_RANGE = 100; void adjustRange(KisPixelSelectionSP selection, const QRect &applyRect, const int range) { KIS_ASSERT_RECOVER_RETURN(range >= 1 && range <= 100); quint8 rangeTable[256]; for(int i = 0; i < 256; i ++) { quint8 value = i * 100 / range; rangeTable[i] = qMin(value, quint8(255)); } KisSequentialIterator dstIt(selection, applyRect); while (dstIt.nextPixel()) { quint8 *pixelPtr = dstIt.rawData(); *pixelPtr = rangeTable[*pixelPtr]; } } void applyContourCorrection(KisPixelSelectionSP selection, const QRect &applyRect, const quint8 *lookup_table, bool antiAliased, bool edgeHidden) { quint8 contour[PSD_LOOKUP_TABLE_SIZE] = { 0x00, 0x0b, 0x16, 0x21, 0x2c, 0x37, 0x42, 0x4d, 0x58, 0x63, 0x6e, 0x79, 0x84, 0x8f, 0x9a, 0xa5, 0xb0, 0xbb, 0xc6, 0xd1, 0xdc, 0xf2, 0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; if (edgeHidden) { if (antiAliased) { for (int i = 0; i < PSD_LOOKUP_TABLE_SIZE; i++) { contour[i] = contour[i] * lookup_table[i] >> 8; } } else { for (int i = 0; i < PSD_LOOKUP_TABLE_SIZE; i++) { contour[i] = contour[i] * lookup_table[(int)((int)(i / 2.55) * 2.55 + 0.5)] >> 8; } } } else { if (antiAliased) { for (int i = 0; i < PSD_LOOKUP_TABLE_SIZE; i++) { contour[i] = lookup_table[i]; } } else { for (int i = 0; i < PSD_LOOKUP_TABLE_SIZE; i++) { contour[i] = lookup_table[(int)((int)(i / 2.55) * 2.55 + 0.5)]; } } } KisSequentialIterator dstIt(selection, applyRect); while (dstIt.nextPixel()) { quint8 *pixelPtr = dstIt.rawData(); *pixelPtr = contour[*pixelPtr]; } } void knockOutSelection(KisPixelSelectionSP selection, KisPixelSelectionSP knockOutSelection, const QRect &srcRect, const QRect &dstRect, const QRect &totalNeedRect, const bool knockOutInverted) { KIS_ASSERT_RECOVER_RETURN(knockOutSelection); QRect knockOutRect = !knockOutInverted ? srcRect : totalNeedRect; knockOutRect &= dstRect; KisPainter gc(selection); gc.setCompositeOp(COMPOSITE_ERASE); gc.bitBlt(knockOutRect.topLeft(), knockOutSelection, knockOutRect); } void fillPattern(KisPaintDeviceSP fillDevice, const QRect &applyRect, KisLayerStyleFilterEnvironment *env, int scale, KoPattern *pattern, int horizontalPhase, int verticalPhase, bool alignWithLayer) { if (scale != 100) { warnKrita << "KisLsOverlayFilter::applyOverlay(): Pattern scaling is NOT implemented!"; } KIS_SAFE_ASSERT_RECOVER_RETURN(pattern); QSize psize(pattern->width(), pattern->height()); QPoint patternOffset(qreal(psize.width()) * horizontalPhase / 100, qreal(psize.height()) * verticalPhase / 100); const QRect boundsRect = alignWithLayer ? env->layerBounds() : env->defaultBounds(); patternOffset += boundsRect.topLeft(); patternOffset.rx() %= psize.width(); patternOffset.ry() %= psize.height(); QRect fillRect = applyRect | applyRect.translated(patternOffset); KisFillPainter gc(fillDevice); gc.fillRect(fillRect.x(), fillRect.y(), fillRect.width(), fillRect.height(), pattern, -patternOffset); gc.end(); } void fillOverlayDevice(KisPaintDeviceSP fillDevice, const QRect &applyRect, const psd_layer_effects_overlay_base *config, KisLayerStyleFilterEnvironment *env) { if (config->fillType() == psd_fill_solid_color) { KoColor color(config->color(), fillDevice->colorSpace()); fillDevice->setDefaultPixel(color); } else if (config->fillType() == psd_fill_pattern) { fillPattern(fillDevice, applyRect, env, config->scale(), config->pattern(), config->horizontalPhase(), config->verticalPhase(), config->alignWithLayer()); } else if (config->fillType() == psd_fill_gradient) { const QRect boundsRect = config->alignWithLayer() ? env->layerBounds() : env->defaultBounds(); QPoint center = boundsRect.center(); center += QPoint(boundsRect.width() * config->gradientXOffset() / 100, boundsRect.height() * config->gradientYOffset() / 100); int width = (boundsRect.width() * config->scale() + 100) / 200; int height = (boundsRect.height() * config->scale() + 100) / 200; /* copy paste from libpsd */ int angle = config->angle(); int corner_angle = (int)(atan((qreal)boundsRect.height() / boundsRect.width()) * 180 / M_PI + 0.5); int sign_x = 1; int sign_y = 1; if(angle < 0) { angle += 360; } if (angle >= 90 && angle < 180) { angle = 180 - angle; sign_x = -1; } else if (angle >= 180 && angle < 270) { angle = angle - 180; sign_x = -1; sign_y = -1; } else if (angle >= 270 && angle <= 360) { angle = 360 - angle; sign_y = -1; } int radius_x = 0; int radius_y = 0; if (angle <= corner_angle) { radius_x = width; radius_y = (int)(radius_x * tan(kisDegreesToRadians(qreal(angle))) + 0.5); } else { radius_y = height; radius_x = (int)(radius_y / tan(kisDegreesToRadians(qreal(angle))) + 0.5); } int radius_corner = (int)(std::sqrt((qreal)(radius_x * radius_x + radius_y * radius_y)) + 0.5); /* end of copy paste from libpsd */ KisGradientPainter gc(fillDevice); gc.setGradient(config->gradient().data()); QPointF gradStart; QPointF gradEnd; KisGradientPainter::enumGradientRepeat repeat = KisGradientPainter::GradientRepeatNone; QPoint rectangularOffset(sign_x * radius_x, -sign_y * radius_y); switch(config->style()) { case psd_gradient_style_linear: gc.setGradientShape(KisGradientPainter::GradientShapeLinear); repeat = KisGradientPainter::GradientRepeatNone; gradStart = center - rectangularOffset; gradEnd = center + rectangularOffset; break; case psd_gradient_style_radial: gc.setGradientShape(KisGradientPainter::GradientShapeRadial); repeat = KisGradientPainter::GradientRepeatNone; gradStart = center; gradEnd = center + QPointF(radius_corner, 0); break; case psd_gradient_style_angle: gc.setGradientShape(KisGradientPainter::GradientShapeConical); repeat = KisGradientPainter::GradientRepeatNone; gradStart = center; gradEnd = center + rectangularOffset; break; case psd_gradient_style_reflected: gc.setGradientShape(KisGradientPainter::GradientShapeLinear); repeat = KisGradientPainter::GradientRepeatAlternate; gradStart = center - rectangularOffset; gradEnd = center; break; case psd_gradient_style_diamond: gc.setGradientShape(KisGradientPainter::GradientShapeBiLinear); repeat = KisGradientPainter::GradientRepeatNone; gradStart = center - rectangularOffset; gradEnd = center + rectangularOffset; break; default: qFatal("Gradient Overlay: unknown switch case!"); break; } gc.paintGradient(gradStart, gradEnd, repeat, 0.0, config->reverse(), applyRect); } } void applyFinalSelection(const QString &projectionId, KisSelectionSP baseSelection, KisPaintDeviceSP srcDevice, KisMultipleProjection *dst, const QRect &/*srcRect*/, const QRect &dstRect, const psd_layer_effects_context */*context*/, const psd_layer_effects_shadow_base *config, const KisLayerStyleFilterEnvironment *env) { const KoColor effectColor(config->color(), srcDevice->colorSpace()); const QRect effectRect(dstRect); const QString compositeOp = config->blendMode(); - const quint8 opacityU8 = 255.0 / 100.0 * config->opacity(); + const quint8 opacityU8 = quint8(qRound(255.0 / 100.0 * config->opacity())); KisPaintDeviceSP dstDevice = dst->getProjection(projectionId, compositeOp, opacityU8, QBitArray(), srcDevice); if (config->fillType() == psd_fill_solid_color) { KisFillPainter gc(dstDevice); gc.setCompositeOp(COMPOSITE_COPY); gc.setSelection(baseSelection); gc.fillSelection(effectRect, effectColor); gc.end(); } else if (config->fillType() == psd_fill_gradient) { if (!config->gradient()) { warnKrita << "KisLsUtils::applyFinalSelection: Gradient object is null! Skipping..."; return; } QVector table(256); Private::getGradientTable(config->gradient().data(), &table, dstDevice->colorSpace()); Private::applyGradient(dstDevice, baseSelection->pixelSelection(), effectRect, table, true, config->jitter(), env); } //dstDevice->convertToQImage(0, QRect(0,0,300,300)).save("6_device_shadow.png"); } bool checkEffectEnabled(const psd_layer_effects_shadow_base *config, KisMultipleProjection *dst) { bool result = config->effectEnabled(); if (!result) { dst->freeAllProjections(); } return result; } } diff --git a/libs/image/layerstyles/kis_ls_utils.h b/libs/image/layerstyles/kis_ls_utils.h index 4dacfc3e37..bd6c7e5ac7 100644 --- a/libs/image/layerstyles/kis_ls_utils.h +++ b/libs/image/layerstyles/kis_ls_utils.h @@ -1,126 +1,129 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_LS_UTILS_H #define __KIS_LS_UTILS_H #include "kis_types.h" +#include "kritaimage_export.h" #include "kis_lod_transform.h" struct psd_layer_effects_context; class psd_layer_effects_shadow_base; struct psd_layer_effects_overlay_base; class KisLayerStyleFilterEnvironment; class KoPattern; class KisMultipleProjection; +class KisCachedSelection; namespace KisLsUtils { QRect growSelectionUniform(KisPixelSelectionSP selection, int growSize, const QRect &applyRect); - KisSelectionSP selectionFromAlphaChannel(KisPaintDeviceSP device, - const QRect &srcRect); + KRITAIMAGE_EXPORT void selectionFromAlphaChannel(KisPaintDeviceSP srcDevice, + KisSelectionSP dstSelection, + const QRect &srcRect); void findEdge(KisPixelSelectionSP selection, const QRect &applyRect, const bool edgeHidden); QRect growRectFromRadius(const QRect &rc, int radius); void applyGaussianWithTransaction(KisPixelSelectionSP selection, const QRect &applyRect, qreal radius); static const int FULL_PERCENT_RANGE = 100; void adjustRange(KisPixelSelectionSP selection, const QRect &applyRect, const int range); void applyContourCorrection(KisPixelSelectionSP selection, const QRect &applyRect, const quint8 *lookup_table, bool antiAliased, bool edgeHidden); extern const int noiseNeedBorder; void applyNoise(KisPixelSelectionSP selection, const QRect &applyRect, int noise, const psd_layer_effects_context *context, - const KisLayerStyleFilterEnvironment *env); + KisLayerStyleFilterEnvironment *env); void knockOutSelection(KisPixelSelectionSP selection, KisPixelSelectionSP knockOutSelection, const QRect &srcRect, const QRect &dstRect, const QRect &totalNeedRect, const bool knockOutInverted); void fillPattern(KisPaintDeviceSP fillDevice, const QRect &applyRect, KisLayerStyleFilterEnvironment *env, int scale, KoPattern *pattern, int horizontalPhase, int verticalPhase, bool alignWithLayer); void fillOverlayDevice(KisPaintDeviceSP fillDevice, const QRect &applyRect, const psd_layer_effects_overlay_base *config, KisLayerStyleFilterEnvironment *env); void applyFinalSelection(const QString &projectionId, KisSelectionSP baseSelection, KisPaintDeviceSP srcDevice, KisMultipleProjection *dst, const QRect &srcRect, const QRect &dstRect, const psd_layer_effects_context *context, const psd_layer_effects_shadow_base *config, const KisLayerStyleFilterEnvironment *env); bool checkEffectEnabled(const psd_layer_effects_shadow_base *config, KisMultipleProjection *dst); template struct LodWrapper { LodWrapper(int lod, const ConfigStruct *srcStruct) { if (lod > 0) { storage.reset(new ConfigStruct(*srcStruct)); const qreal lodScale = KisLodTransform::lodToScale(lod); storage->scaleLinearSizes(lodScale); config = storage.data(); } else { config = srcStruct; } } const ConfigStruct *config; private: QScopedPointer storage; }; } #endif /* __KIS_LS_UTILS_H */ diff --git a/libs/image/layerstyles/kis_multiple_projection.cpp b/libs/image/layerstyles/kis_multiple_projection.cpp index 49905ec58f..9e4247e913 100644 --- a/libs/image/layerstyles/kis_multiple_projection.cpp +++ b/libs/image/layerstyles/kis_multiple_projection.cpp @@ -1,178 +1,183 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_multiple_projection.h" #include #include #include #include "kis_painter.h" #include "kis_paint_device.h" #include "kis_layer_style_filter_environment.h" struct ProjectionStruct { KisPaintDeviceSP device; QString compositeOpId; quint8 opacity = OPACITY_OPAQUE_U8; QBitArray channelFlags; }; typedef QMap PlanesMap; struct KisMultipleProjection::Private { QReadWriteLock lock; PlanesMap planes; }; KisMultipleProjection::KisMultipleProjection() : m_d(new Private) { } KisMultipleProjection::KisMultipleProjection(const KisMultipleProjection &rhs) : m_d(new Private) { QReadLocker readLocker(&rhs.m_d->lock); auto it = rhs.m_d->planes.constBegin(); for (; it != rhs.m_d->planes.constEnd(); ++it) { ProjectionStruct proj; proj.device = new KisPaintDevice(*it->device); proj.compositeOpId = it->compositeOpId; proj.opacity = it->opacity; proj.channelFlags = it->channelFlags; m_d->planes.insert(it.key(), proj); } } KisMultipleProjection::~KisMultipleProjection() { } QString KisMultipleProjection::defaultProjectionId() { return "00_default"; } KisPaintDeviceSP KisMultipleProjection::getProjection(const QString &id, const QString &compositeOpId, quint8 opacity, const QBitArray &channelFlags, KisPaintDeviceSP prototype) { QReadLocker readLocker(&m_d->lock); PlanesMap::const_iterator constIt = m_d->planes.constFind(id); if (constIt == m_d->planes.constEnd() || constIt->compositeOpId != compositeOpId || constIt->opacity != opacity || constIt->channelFlags != channelFlags || *constIt->device->colorSpace() != *prototype->colorSpace()) { readLocker.unlock(); { QWriteLocker writeLocker(&m_d->lock); PlanesMap::iterator writeIt = m_d->planes.find(id); if (writeIt == m_d->planes.end()) { ProjectionStruct plane; plane.device = new KisPaintDevice(prototype->colorSpace()); plane.device->prepareClone(prototype); plane.compositeOpId = compositeOpId; plane.opacity = opacity; plane.channelFlags = channelFlags; writeIt = m_d->planes.insert(id, plane); } else if (writeIt->compositeOpId != compositeOpId || *writeIt->device->colorSpace() != *prototype->colorSpace()) { writeIt->device->prepareClone(prototype); writeIt->compositeOpId = compositeOpId; writeIt->opacity = opacity; writeIt->channelFlags = channelFlags; } return writeIt->device; } } return constIt->device; } void KisMultipleProjection::freeProjection(const QString &id) { QWriteLocker writeLocker(&m_d->lock); m_d->planes.remove(id); } void KisMultipleProjection::freeAllProjections() { QWriteLocker writeLocker(&m_d->lock); m_d->planes.clear(); } void KisMultipleProjection::clear(const QRect &rc) { QReadLocker readLocker(&m_d->lock); PlanesMap::const_iterator it = m_d->planes.constBegin(); PlanesMap::const_iterator end = m_d->planes.constEnd(); for (; it != end; ++it) { const_cast(it->device.data())->clear(rc); } } void KisMultipleProjection::apply(KisPaintDeviceSP dstDevice, const QRect &rect, KisLayerStyleFilterEnvironment *env) { QReadLocker readLocker(&m_d->lock); PlanesMap::const_iterator it = m_d->planes.constBegin(); PlanesMap::const_iterator end = m_d->planes.constEnd(); for (; it != end; ++it) { KisPainter gc(dstDevice); gc.setCompositeOp(it->compositeOpId); env->setupFinalPainter(&gc, it->opacity, it->channelFlags); gc.bitBlt(rect.topLeft(), it->device, rect); } } KisPaintDeviceList KisMultipleProjection::getLodCapableDevices() const { QReadLocker readLocker(&m_d->lock); PlanesMap::const_iterator it = m_d->planes.constBegin(); PlanesMap::const_iterator end = m_d->planes.constEnd(); KisPaintDeviceList list; for (; it != end; ++it) { list << it->device; } return list; } +bool KisMultipleProjection::isEmpty() const +{ + return m_d->planes.isEmpty(); +} + diff --git a/libs/image/layerstyles/kis_multiple_projection.h b/libs/image/layerstyles/kis_multiple_projection.h index 8ce98d7689..6c8c948f1a 100644 --- a/libs/image/layerstyles/kis_multiple_projection.h +++ b/libs/image/layerstyles/kis_multiple_projection.h @@ -1,52 +1,54 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_MULTIPLE_PROJECTION_H #define __KIS_MULTIPLE_PROJECTION_H #include #include "kis_types.h" #include "kritaimage_export.h" class KisLayerStyleFilterEnvironment; class KRITAIMAGE_EXPORT KisMultipleProjection { public: KisMultipleProjection(); KisMultipleProjection(const KisMultipleProjection &rhs); ~KisMultipleProjection(); static QString defaultProjectionId(); KisPaintDeviceSP getProjection(const QString &id, const QString &compositeOpId, quint8 opacity, const QBitArray &channelFlags, KisPaintDeviceSP prototype); void freeProjection(const QString &id); void freeAllProjections(); void clear(const QRect &rc); void apply(KisPaintDeviceSP dstDevice, const QRect &rect, KisLayerStyleFilterEnvironment *env); KisPaintDeviceList getLodCapableDevices() const; + bool isEmpty() const; + private: struct Private; const QScopedPointer m_d; }; #endif /* __KIS_MULTIPLE_PROJECTION_H */ diff --git a/libs/image/lazybrush/kis_colorize_mask.cpp b/libs/image/lazybrush/kis_colorize_mask.cpp index d5437bf9c4..f59a3eca84 100644 --- a/libs/image/lazybrush/kis_colorize_mask.cpp +++ b/libs/image/lazybrush/kis_colorize_mask.cpp @@ -1,1176 +1,1172 @@ /* * 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_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), 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), 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; 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(); m_d->fakePaintDevice->convertTo(dstColorSpace, renderingIntent, conversionFlags, composite); m_d->coloringProjection->convertTo(dstColorSpace, renderingIntent, conversionFlags, composite); 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() { if (!parent()) { // When the colorize mask is being merged, // the update is performed for all the layers, // so the invisible areas around the canvas are included in the merged layer. // Colorize Mask gets the info that its parent is "dirty" (needs updating), // but when it arrives, the parent doesn't exists anymore and is set to null. // Colorize Mask doesn't work outside of the canvas anyway (at least in time of writing). return; } 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(); + KisCachedSelection::Guard s1(m_d->cachedSelection); + KisCachedSelection::Guard s2(m_d->cachedSelection); + + KisSelectionSP selection = s1.selection(); + KisPixelSelectionSP tempSelection = s2.selection()->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 partA() override { m_list->insert(m_index, m_stroke); m_node->setNeedsUpdate(true); emit m_node->sigKeyStrokesListChanged(); } void partB() 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(); + KisCachedSelection::Guard s1(m_d->cachedSelection); + KisPixelSelectionSP tempSelection = s1.selection()->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(); + KisCachedSelection::Guard s1(m_d->cachedSelection); + KisSelectionSP selection = s1.selection(); 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(); m_d->updateIsRunning = false; } 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/lazybrush/kis_lazy_fill_graph.h b/libs/image/lazybrush/kis_lazy_fill_graph.h index 410cf27127..ec30531818 100644 --- a/libs/image/lazybrush/kis_lazy_fill_graph.h +++ b/libs/image/lazybrush/kis_lazy_fill_graph.h @@ -1,1028 +1,1030 @@ /* * 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. */ #ifndef __KIS_LAZY_FILL_GRAPH_H #define __KIS_LAZY_FILL_GRAPH_H #include #include #include #include #include #include #include #include //#define USE_LAZY_FILL_SANITY_CHECKS 1 #ifdef USE_LAZY_FILL_SANITY_CHECKS #define LF_SANITY_ASSERT(x) KIS_ASSERT(x) #define LF_SANITY_ASSERT_RECOVER(x) KIS_ASSERT_RECOVER(x) #else #define LF_SANITY_ASSERT(x) #define LF_SANITY_ASSERT_RECOVER(x) if (0) #endif /* USE_LAZY_FILL_SANITY_CHECKS */ using namespace boost; /* BOOST_CONCEPT_ASSERT(( ReadablePropertyMapConcept )); //read flow-values from vertices */ class KisLazyFillGraph; //=================== // Index Property Map //=================== template struct lazy_fill_graph_index_map { public: typedef Index value_type; typedef Index reference_type; typedef reference_type reference; typedef Descriptor key_type; typedef readable_property_map_tag category; lazy_fill_graph_index_map() { } lazy_fill_graph_index_map(const Graph& graph) : m_graph(&graph) { } value_type operator[](key_type key) const { value_type index = m_graph->index_of(key); LF_SANITY_ASSERT(index >= 0); return index; } friend inline Index get(const lazy_fill_graph_index_map& index_map, const typename lazy_fill_graph_index_map::key_type& key) { return (index_map[key]); } protected: const Graph* m_graph; }; //========================== // Reverse Edge Property Map //========================== template struct lazy_fill_graph_reverse_edge_map { public: typedef Descriptor value_type; typedef Descriptor reference_type; typedef reference_type reference; typedef Descriptor key_type; typedef readable_property_map_tag category; lazy_fill_graph_reverse_edge_map() { } value_type operator[](const key_type& key) const { return (value_type(key.second, key.first)); } friend inline Descriptor get(const lazy_fill_graph_reverse_edge_map& rev_map, const typename lazy_fill_graph_reverse_edge_map::key_type& key) { return (rev_map[key]); } }; //================= // Function Objects //================= namespace kis_detail { // vertex_at template struct lazy_fill_graph_vertex_at { typedef typename graph_traits::vertex_descriptor result_type; lazy_fill_graph_vertex_at() : m_graph(0) {} lazy_fill_graph_vertex_at(const Graph* graph) : m_graph(graph) { } result_type operator() (typename graph_traits::vertices_size_type vertex_index) const { return (vertex(vertex_index, *m_graph)); } private: const Graph* m_graph; }; // out_edge_at template struct lazy_fill_graph_out_edge_at { private: typedef typename graph_traits::vertex_descriptor vertex_descriptor; public: typedef typename graph_traits::edge_descriptor result_type; lazy_fill_graph_out_edge_at() : m_vertex(), m_graph(0) {} lazy_fill_graph_out_edge_at(vertex_descriptor source_vertex, const Graph* graph) : m_vertex(source_vertex), m_graph(graph) { } result_type operator() (typename graph_traits::degree_size_type out_edge_index) const { return (out_edge_at(m_vertex, out_edge_index, *m_graph)); } private: vertex_descriptor m_vertex; const Graph* m_graph; }; // in_edge_at template struct lazy_fill_graph_in_edge_at { private: typedef typename graph_traits::vertex_descriptor vertex_descriptor; public: typedef typename graph_traits::edge_descriptor result_type; lazy_fill_graph_in_edge_at() : m_vertex(), m_graph(0) {} lazy_fill_graph_in_edge_at(vertex_descriptor target_vertex, const Graph* graph) : m_vertex(target_vertex), m_graph(graph) { } result_type operator() (typename graph_traits::degree_size_type in_edge_index) const { return (in_edge_at(m_vertex, in_edge_index, *m_graph)); } private: vertex_descriptor m_vertex; const Graph* m_graph; }; // edge_at template struct lazy_fill_graph_edge_at { typedef typename graph_traits::edge_descriptor result_type; lazy_fill_graph_edge_at() : m_graph(0) {} lazy_fill_graph_edge_at(const Graph* graph) : m_graph(graph) { } result_type operator() (typename graph_traits::edges_size_type edge_index) const { return (edge_at(edge_index, *m_graph)); } private: const Graph* m_graph; }; // adjacent_vertex_at template struct lazy_fill_graph_adjacent_vertex_at { public: typedef typename graph_traits::vertex_descriptor result_type; lazy_fill_graph_adjacent_vertex_at(result_type source_vertex, const Graph* graph) : m_vertex(source_vertex), m_graph(graph) { } result_type operator() (typename graph_traits::degree_size_type adjacent_index) const { return (target(out_edge_at(m_vertex, adjacent_index, *m_graph), *m_graph)); } private: result_type m_vertex; const Graph* m_graph; }; } // namespace kis_detail class KisLazyFillGraph { public: typedef KisLazyFillGraph type; typedef long VertexIndex; typedef long EdgeIndex; // sizes typedef VertexIndex vertices_size_type; typedef EdgeIndex edges_size_type; typedef EdgeIndex degree_size_type; struct VertexDescriptor : public equality_comparable { enum VertexType { NORMAL = 0, LABEL_A, LABEL_B }; - vertices_size_type x; vertices_size_type y; VertexType type; VertexDescriptor(vertices_size_type _x, vertices_size_type _y, VertexType _type = NORMAL) : x(_x), y(_y), type(_type) {} + // TODO: Extra constructors look unnecessary, ask Dmitry before removing VertexDescriptor(VertexType _type) : x(0), y(0), type(_type) {} VertexDescriptor() : x(0), y(0), type(NORMAL) {} bool operator ==(const VertexDescriptor &rhs) const { return rhs.x == x && rhs.y == y && rhs.type == type; } }; // descriptors typedef VertexDescriptor vertex_descriptor; typedef std::pair edge_descriptor; friend QDebug operator<<(QDebug dbg, const KisLazyFillGraph::edge_descriptor &e); friend QDebug operator<<(QDebug dbg, const KisLazyFillGraph::vertex_descriptor &v); // vertex_iterator typedef counting_iterator vertex_index_iterator; typedef kis_detail::lazy_fill_graph_vertex_at vertex_function; typedef transform_iterator vertex_iterator; // edge_iterator typedef counting_iterator edge_index_iterator; typedef kis_detail::lazy_fill_graph_edge_at edge_function; typedef transform_iterator edge_iterator; // out_edge_iterator typedef counting_iterator degree_iterator; typedef kis_detail::lazy_fill_graph_out_edge_at out_edge_function; typedef transform_iterator out_edge_iterator; // adjacency_iterator typedef kis_detail::lazy_fill_graph_adjacent_vertex_at adjacent_vertex_function; typedef transform_iterator adjacency_iterator; // categories typedef directed_tag directed_category; typedef disallow_parallel_edge_tag edge_parallel_category; struct traversal_category : virtual public incidence_graph_tag, virtual public adjacency_graph_tag, virtual public vertex_list_graph_tag, virtual public edge_list_graph_tag, virtual public adjacency_matrix_tag { }; static inline vertex_descriptor null_vertex() { vertex_descriptor maxed_out_vertex( std::numeric_limits::max(), std::numeric_limits::max(), vertex_descriptor::NORMAL); return (maxed_out_vertex); } KisLazyFillGraph() {} KisLazyFillGraph(const QRect &mainRect, const QRegion &aLabelRegion, const QRegion &bLabelRegion) : m_x(mainRect.x()), m_y(mainRect.y()), m_width(mainRect.width()), m_height(mainRect.height()) { m_mainArea = mainRect; m_aLabelArea = aLabelRegion.boundingRect(); m_bLabelArea = bLabelRegion.boundingRect(); + + //QRegion::rects() is deprecated, https://doc.qt.io/qt-5/qregion-obsolete.html#rects m_aLabelRects = aLabelRegion.rects(); m_bLabelRects = bLabelRegion.rects(); KIS_ASSERT(m_mainArea.contains(m_aLabelArea)); KIS_ASSERT(m_mainArea.contains(m_bLabelArea)); m_numVertices = m_width * m_height + 2; m_edgeBins << EdgeIndexBin(0, m_mainArea.adjusted(0, 0, -1, 0), HORIZONTAL); m_edgeBins << EdgeIndexBin(m_edgeBins.last(), m_mainArea.adjusted(0, 0, -1, 0), HORIZONTAL_REVERSED); m_edgeBins << EdgeIndexBin(m_edgeBins.last(), m_mainArea.adjusted(0, 0, 0, -1), VERTICAL); m_edgeBins << EdgeIndexBin(m_edgeBins.last(), m_mainArea.adjusted(0, 0, 0, -1), VERTICAL_REVERSED); Q_FOREACH (const QRect &rc, m_aLabelRects) { m_edgeBins << EdgeIndexBin(m_edgeBins.last(), rc, LABEL_A); } // out_edge_at relies on the sequential layout of reversed edges of one type m_aReversedEdgesStart = m_edgeBins.last().last() + 1; Q_FOREACH (const QRect &rc, m_aLabelRects) { m_edgeBins << EdgeIndexBin(m_edgeBins.last(), rc, LABEL_A_REVERSED); } m_numAEdges = m_edgeBins.last().last() + 1 - m_aReversedEdgesStart; Q_FOREACH (const QRect &rc, m_bLabelRects) { m_edgeBins << EdgeIndexBin(m_edgeBins.last(), rc, LABEL_B); } // out_edge_at relies on the sequential layout of reversed edges of one type m_bReversedEdgesStart = m_edgeBins.last().last() + 1; Q_FOREACH (const QRect &rc, m_bLabelRects) { m_edgeBins << EdgeIndexBin(m_edgeBins.last(), rc, LABEL_B_REVERSED); } m_numBEdges = m_edgeBins.last().last() + 1 - m_bReversedEdgesStart; m_numEdges = m_edgeBins.last().last() + 1; } ~KisLazyFillGraph() { } QSize size() const { return QSize(m_width, m_height); } QRect rect() const { return QRect(m_x, m_y, m_width, m_height); } vertices_size_type m_x; vertices_size_type m_y; vertices_size_type m_width; vertices_size_type m_height; vertices_size_type m_numVertices; vertices_size_type m_numEdges; vertices_size_type m_aReversedEdgesStart; vertices_size_type m_bReversedEdgesStart; vertices_size_type m_numAEdges; vertices_size_type m_numBEdges; enum EdgeIndexBinId { HORIZONTAL, HORIZONTAL_REVERSED, VERTICAL, VERTICAL_REVERSED, LABEL_A, LABEL_A_REVERSED, LABEL_B, LABEL_B_REVERSED, }; struct EdgeIndexBin { EdgeIndexBin() : start(0), stride(0), size(0) {} EdgeIndexBin(edges_size_type _start, const QRect &_rect, EdgeIndexBinId _binId) : start(_start), stride(_rect.width()), size(_rect.width() * _rect.height()), xOffset(_rect.x()), yOffset(_rect.y()), binId(_binId), isReversed(int(_binId) & 0x1), rect(_rect) {} EdgeIndexBin(const EdgeIndexBin &putAfter, const QRect &_rect, EdgeIndexBinId _binId) : start(putAfter.last() + 1), stride(_rect.width()), size(_rect.width() * _rect.height()), xOffset(_rect.x()), yOffset(_rect.y()), binId(_binId), isReversed(int(_binId) & 0x1), rect(_rect) {} edges_size_type last() const { return start + size - 1; } bool contains(edges_size_type index) const { index -= start; return index >= 0 && index < size; } bool contains(const edge_descriptor &edge) const { return indexOf(edge) >= 0; } edges_size_type indexOf(const edge_descriptor &edge) const { vertex_descriptor src_vertex = source(edge, *this); vertex_descriptor dst_vertex = target(edge, *this); const bool srcColoredA = src_vertex.type == vertex_descriptor::LABEL_A; const bool dstColoredA = dst_vertex.type == vertex_descriptor::LABEL_A; const bool srcColoredB = src_vertex.type == vertex_descriptor::LABEL_B; const bool dstColoredB = dst_vertex.type == vertex_descriptor::LABEL_B; if (srcColoredA || dstColoredA) { const bool edgeReversed = srcColoredA; if (isReversed != edgeReversed || (binId != LABEL_A && binId != LABEL_A_REVERSED) || (srcColoredA && (dst_vertex.type != vertex_descriptor::NORMAL)) || (dstColoredA && (src_vertex.type != vertex_descriptor::NORMAL))) { return -1; } } else if (srcColoredB || dstColoredB) { const bool edgeReversed = srcColoredB; if (isReversed != edgeReversed || (binId != LABEL_B && binId != LABEL_B_REVERSED) || (srcColoredB && (dst_vertex.type != vertex_descriptor::NORMAL)) || (dstColoredB && (src_vertex.type != vertex_descriptor::NORMAL))) { return -1; } } else { const vertices_size_type xDiff = dst_vertex.x - src_vertex.x; const vertices_size_type yDiff = dst_vertex.y - src_vertex.y; const vertices_size_type xAbsDiff = qAbs(xDiff); const vertices_size_type yAbsDiff = qAbs(yDiff); const bool edgeReversed = xDiff < 0 || yDiff < 0; if (isReversed != edgeReversed || (xDiff && binId != HORIZONTAL && binId != HORIZONTAL_REVERSED) || (yDiff && binId != VERTICAL && binId != VERTICAL_REVERSED) || xAbsDiff > 1 || yAbsDiff > 1 || xAbsDiff == yAbsDiff) { return -1; } } if (isReversed) { std::swap(src_vertex, dst_vertex); } // using direct QRect::contains makes the code 30% slower const int x = src_vertex.x; const int y = src_vertex.y; if (x < rect.x() || x > rect.right() || y < rect.y() || y > rect.bottom()) { return -1; } edges_size_type internalIndex = (src_vertex.x - xOffset) + (src_vertex.y - yOffset) * stride; LF_SANITY_ASSERT_RECOVER(internalIndex >= 0 && internalIndex < size) { return -1; } return internalIndex + start; } edge_descriptor edgeAt(edges_size_type index) const { edges_size_type localOffset = index - start; if (localOffset < 0 || localOffset >= size) { return edge_descriptor(); } const edges_size_type x = localOffset % stride + xOffset; const edges_size_type y = localOffset / stride + yOffset; vertex_descriptor src_vertex(x, y, vertex_descriptor::NORMAL); vertex_descriptor dst_vertex; switch (binId) { case HORIZONTAL: case HORIZONTAL_REVERSED: dst_vertex.x = x + 1; dst_vertex.y = y; dst_vertex.type = vertex_descriptor::NORMAL; break; case VERTICAL: case VERTICAL_REVERSED: dst_vertex.x = x; dst_vertex.y = y + 1; dst_vertex.type = vertex_descriptor::NORMAL; break; case LABEL_A: case LABEL_A_REVERSED: dst_vertex.type = vertex_descriptor::LABEL_A; break; case LABEL_B: case LABEL_B_REVERSED: dst_vertex.type = vertex_descriptor::LABEL_B; break; } if (isReversed) { std::swap(src_vertex, dst_vertex); } return std::make_pair(src_vertex, dst_vertex); } edges_size_type start; edges_size_type stride; edges_size_type size; edges_size_type xOffset; edges_size_type yOffset; EdgeIndexBinId binId; bool isReversed; QRect rect; }; QVector m_edgeBins; QRect m_aLabelArea; QRect m_bLabelArea; QRect m_mainArea; QVector m_aLabelRects; QVector m_bLabelRects; public: // Returns the number of vertices in the graph inline vertices_size_type num_vertices() const { return (m_numVertices); } // Returns the number of edges in the graph inline edges_size_type num_edges() const { return (m_numEdges); } // Returns the index of [vertex] (See also vertex_at) vertices_size_type index_of(vertex_descriptor vertex) const { vertices_size_type vertex_index = -1; switch (vertex.type) { case vertex_descriptor::NORMAL: vertex_index = vertex.x - m_x + (vertex.y - m_y) * m_width; break; case vertex_descriptor::LABEL_A: vertex_index = m_numVertices - 2; break; case vertex_descriptor::LABEL_B: vertex_index = m_numVertices - 1; break; } return vertex_index; } // Returns the vertex whose index is [vertex_index] (See also // index_of(vertex_descriptor)) vertex_descriptor vertex_at (vertices_size_type vertex_index) const { vertex_descriptor vertex; if (vertex_index == m_numVertices - 2) { vertex.type = vertex_descriptor::LABEL_A; } else if (vertex_index == m_numVertices - 1) { vertex.type = vertex_descriptor::LABEL_B; } else if (vertex_index >= 0) { vertex.x = vertex_index % m_width + m_x; vertex.y = vertex_index / m_width + m_y; vertex.type = vertex_descriptor::NORMAL; } return vertex; } // Returns the edge whose index is [edge_index] (See also // index_of(edge_descriptor)). NOTE: The index mapping is // dependent upon dimension wrapping. edge_descriptor edge_at(edges_size_type edge_index) const { int binIndex = 0; while (binIndex < m_edgeBins.size() && !m_edgeBins[binIndex].contains(edge_index)) { binIndex++; } if (binIndex >= m_edgeBins.size()) { return edge_descriptor(); } return m_edgeBins[binIndex].edgeAt(edge_index); } // Returns the index for [edge] (See also edge_at) edges_size_type index_of(edge_descriptor edge) const { edges_size_type index = -1; auto it = m_edgeBins.constBegin(); for (; it != m_edgeBins.constEnd(); ++it) { index = it->indexOf(edge); if (index >= 0) break; } return index; } private: static vertices_size_type numVacantEdges(const vertex_descriptor &vertex, const QRect &rc) { vertices_size_type vacantEdges = 4; if (vertex.x == rc.x()) { vacantEdges--; } if (vertex.y == rc.y()) { vacantEdges--; } if (vertex.x == rc.right()) { vacantEdges--; } if (vertex.y == rc.bottom()) { vacantEdges--; } return vacantEdges; } static inline bool findInRects(const QVector &rects, const QPoint &pt) { bool result = false; auto it = rects.constBegin(); for (; it != rects.constEnd(); ++it) { if (it->contains(pt)) { result = true; break; } } return result; } public: // Returns the number of out-edges for [vertex] degree_size_type out_degree(vertex_descriptor vertex) const { degree_size_type out_edge_count = 0; if (index_of(vertex) < 0) return out_edge_count; switch (vertex.type) { case vertex_descriptor::NORMAL: { out_edge_count = numVacantEdges(vertex, m_mainArea); const QPoint pt = QPoint(vertex.x, vertex.y); if (m_aLabelArea.contains(pt) && findInRects(m_aLabelRects, pt)) { out_edge_count++; } if (m_bLabelArea.contains(pt) && findInRects(m_bLabelRects, pt)) { out_edge_count++; } break; } case vertex_descriptor::LABEL_A: out_edge_count = m_numAEdges; break; case vertex_descriptor::LABEL_B: out_edge_count = m_numBEdges; break; } return (out_edge_count); } // Returns an out-edge for [vertex] by index. Indices are in the // range [0, out_degree(vertex)). edge_descriptor out_edge_at (vertex_descriptor vertex, degree_size_type out_edge_index) const { const QPoint pt = QPoint(vertex.x, vertex.y); vertex_descriptor dst_vertex = vertex; switch (vertex.type) { case vertex_descriptor::NORMAL: if (vertex.x > m_mainArea.x() && !out_edge_index--) { dst_vertex.x--; } else if (vertex.y > m_mainArea.y() && !out_edge_index--) { dst_vertex.y--; } else if (vertex.x < m_mainArea.right() && !out_edge_index--) { dst_vertex.x++; } else if (vertex.y < m_mainArea.bottom() && !out_edge_index--) { dst_vertex.y++; } else if (m_aLabelArea.contains(pt) && findInRects(m_aLabelRects, pt) && !out_edge_index--) { dst_vertex = vertex_descriptor(0, 0, vertex_descriptor::LABEL_A); } else if (m_bLabelArea.contains(pt) && findInRects(m_bLabelRects, pt) && !out_edge_index--) { dst_vertex = vertex_descriptor(0, 0, vertex_descriptor::LABEL_B); } else { dbgImage << ppVar(vertex) << ppVar(out_edge_index) << ppVar(out_degree(vertex)); qFatal("Wrong edge sub-index"); } break; case vertex_descriptor::LABEL_A: { edge_descriptor edge = edge_at(m_aReversedEdgesStart + out_edge_index); dst_vertex = edge.second; break; } case vertex_descriptor::LABEL_B: { edge_descriptor edge = edge_at(m_bReversedEdgesStart + out_edge_index); dst_vertex = edge.second; break; } } return std::make_pair(vertex, dst_vertex); } public: //================ // VertexListGraph //================ friend inline std::pair vertices(const type& graph) { typedef typename type::vertex_iterator vertex_iterator; typedef typename type::vertex_function vertex_function; typedef typename type::vertex_index_iterator vertex_index_iterator; return (std::make_pair (vertex_iterator(vertex_index_iterator(0), vertex_function(&graph)), vertex_iterator(vertex_index_iterator(graph.num_vertices()), vertex_function(&graph)))); } friend inline typename type::vertices_size_type num_vertices(const type& graph) { return (graph.num_vertices()); } friend inline typename type::vertex_descriptor vertex(typename type::vertices_size_type vertex_index, const type& graph) { return (graph.vertex_at(vertex_index)); } //=============== // IncidenceGraph //=============== friend inline std::pair out_edges(typename type::vertex_descriptor vertex, const type& graph) { typedef typename type::degree_iterator degree_iterator; typedef typename type::out_edge_function out_edge_function; typedef typename type::out_edge_iterator out_edge_iterator; return (std::make_pair (out_edge_iterator(degree_iterator(0), out_edge_function(vertex, &graph)), out_edge_iterator(degree_iterator(graph.out_degree(vertex)), out_edge_function(vertex, &graph)))); } friend inline typename type::degree_size_type out_degree (typename type::vertex_descriptor vertex, const type& graph) { return (graph.out_degree(vertex)); } friend inline typename type::edge_descriptor out_edge_at(typename type::vertex_descriptor vertex, typename type::degree_size_type out_edge_index, const type& graph) { return (graph.out_edge_at(vertex, out_edge_index)); } //=============== // AdjacencyGraph //=============== friend typename std::pair adjacent_vertices (typename type::vertex_descriptor vertex, const type& graph) { typedef typename type::degree_iterator degree_iterator; typedef typename type::adjacent_vertex_function adjacent_vertex_function; typedef typename type::adjacency_iterator adjacency_iterator; return (std::make_pair (adjacency_iterator(degree_iterator(0), adjacent_vertex_function(vertex, &graph)), adjacency_iterator(degree_iterator(graph.out_degree(vertex)), adjacent_vertex_function(vertex, &graph)))); } //================== // Adjacency Matrix //================== friend std::pair edge (typename type::vertex_descriptor source_vertex, typename type::vertex_descriptor destination_vertex, const type& graph) { std::pair edge_exists = std::make_pair(std::make_pair(source_vertex, destination_vertex), false); const edges_size_type index = graph.index_of(edge_exists.first); edge_exists.second = index >= 0; return edge_exists; } //============== // EdgeListGraph //============== friend inline typename type::edges_size_type num_edges(const type& graph) { return (graph.num_edges()); } friend inline typename type::edge_descriptor edge_at(typename type::edges_size_type edge_index, const type& graph) { return (graph.edge_at(edge_index)); } friend inline std::pair edges(const type& graph) { typedef typename type::edge_index_iterator edge_index_iterator; typedef typename type::edge_function edge_function; typedef typename type::edge_iterator edge_iterator; return (std::make_pair (edge_iterator(edge_index_iterator(0), edge_function(&graph)), edge_iterator(edge_index_iterator(graph.num_edges()), edge_function(&graph)))); } //============================= // Index Property Map Functions //============================= friend inline typename type::vertices_size_type get(vertex_index_t, const type& graph, typename type::vertex_descriptor vertex) { type::vertices_size_type index = graph.index_of(vertex); LF_SANITY_ASSERT(index >= 0); return index; } friend inline typename type::edges_size_type get(edge_index_t, const type& graph, typename type::edge_descriptor edge) { type::edges_size_type index = graph.index_of(edge); LF_SANITY_ASSERT(index >= 0); return index; } friend inline lazy_fill_graph_index_map< type, typename type::vertex_descriptor, typename type::vertices_size_type> get(vertex_index_t, const type& graph) { return (lazy_fill_graph_index_map< type, typename type::vertex_descriptor, typename type::vertices_size_type>(graph)); } friend inline lazy_fill_graph_index_map< type, typename type::edge_descriptor, typename type::edges_size_type> get(edge_index_t, const type& graph) { return (lazy_fill_graph_index_map< type, typename type::edge_descriptor, typename type::edges_size_type>(graph)); } friend inline lazy_fill_graph_reverse_edge_map< typename type::edge_descriptor> get(edge_reverse_t, const type& graph) { Q_UNUSED(graph); return (lazy_fill_graph_reverse_edge_map< typename type::edge_descriptor>()); } template friend struct lazy_fill_graph_index_map; template friend struct lazy_fill_graph_reverse_edge_map; }; namespace boost { template <> struct property_map { typedef lazy_fill_graph_index_map::vertex_descriptor, typename graph_traits::vertices_size_type> type; typedef type const_type; }; template<> struct property_map { typedef lazy_fill_graph_index_map::edge_descriptor, typename graph_traits::edges_size_type> type; typedef type const_type; }; } namespace boost { template <> struct property_map { typedef lazy_fill_graph_reverse_edge_map::edge_descriptor> type; typedef type const_type; }; } QDebug operator<<(QDebug dbg, const KisLazyFillGraph::vertex_descriptor &v) { const QString type = v.type == KisLazyFillGraph::vertex_descriptor::NORMAL ? "normal" : v.type == KisLazyFillGraph::vertex_descriptor::LABEL_A ? "label_A" : v.type == KisLazyFillGraph::vertex_descriptor::LABEL_B ? "label_B" : ""; dbg.nospace() << "(" << v.x << ", " << v.y << ", " << type << ")"; return dbg.space(); } QDebug operator<<(QDebug dbg, const KisLazyFillGraph::edge_descriptor &e) { KisLazyFillGraph::vertex_descriptor src = e.first; KisLazyFillGraph::vertex_descriptor dst = e.second; dbg.nospace() << "(" << src << " -> " << dst << ")"; return dbg.space(); } #endif /* __KIS_LAZY_FILL_GRAPH_H */ diff --git a/libs/image/tests/kis_image_test.cpp b/libs/image/tests/kis_image_test.cpp index b4f64c4a85..d3c7ff9449 100644 --- a/libs/image/tests/kis_image_test.cpp +++ b/libs/image/tests/kis_image_test.cpp @@ -1,1239 +1,1239 @@ /* * Copyright (c) 2005 Adrian Page * * 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_test.h" #include #include #include #include #include "filter/kis_filter.h" #include "filter/kis_filter_configuration.h" #include "filter/kis_filter_registry.h" #include "kis_image.h" #include "kis_paint_layer.h" #include "kis_group_layer.h" #include "kis_adjustment_layer.h" #include "kis_selection.h" #include #include #include "kis_keyframe_channel.h" #include "kis_selection_mask.h" #include "kis_layer_utils.h" #include "kis_annotation.h" #include "KisProofingConfiguration.h" #include "kis_undo_stores.h" #define IMAGE_WIDTH 128 #define IMAGE_HEIGHT 128 void KisImageTest::layerTests() { KisImageSP image = new KisImage(0, IMAGE_WIDTH, IMAGE_WIDTH, 0, "layer tests"); QVERIFY(image->rootLayer() != 0); QVERIFY(image->rootLayer()->firstChild() == 0); KisLayerSP layer = new KisPaintLayer(image, "layer 1", OPACITY_OPAQUE_U8); image->addNode(layer); QVERIFY(image->rootLayer()->firstChild()->objectName() == layer->objectName()); } void KisImageTest::benchmarkCreation() { const QRect imageRect(0,0,3000,2000); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); QList images; QList stores; QBENCHMARK { for (int i = 0; i < 10; i++) { stores << new KisSurrogateUndoStore(); } for (int i = 0; i < 10; i++) { KisImageSP image = new KisImage(stores.takeLast(), imageRect.width(), imageRect.height(), cs, "test image"); images << image; } } } #include "testutil.h" #include "kis_stroke_strategy.h" #include class ForbiddenLodStrokeStrategy : public KisStrokeStrategy { public: ForbiddenLodStrokeStrategy(std::function lodCallback) : m_lodCallback(lodCallback) { } KisStrokeStrategy* createLodClone(int levelOfDetail) override { Q_UNUSED(levelOfDetail); m_lodCallback(); return 0; } private: std::function m_lodCallback; }; void notifyVar(bool *value) { *value = true; } void KisImageTest::testBlockLevelOfDetail() { TestUtil::MaskParent p; QCOMPARE(p.image->currentLevelOfDetail(), 0); p.image->setDesiredLevelOfDetail(1); p.image->waitForDone(); QCOMPARE(p.image->currentLevelOfDetail(), 0); { bool lodCreated = false; KisStrokeId id = p.image->startStroke( new ForbiddenLodStrokeStrategy( std::bind(¬ifyVar, &lodCreated))); p.image->endStroke(id); p.image->waitForDone(); QVERIFY(lodCreated); } p.image->setLevelOfDetailBlocked(true); { bool lodCreated = false; KisStrokeId id = p.image->startStroke( new ForbiddenLodStrokeStrategy( std::bind(¬ifyVar, &lodCreated))); p.image->endStroke(id); p.image->waitForDone(); QVERIFY(!lodCreated); } p.image->setLevelOfDetailBlocked(false); p.image->setDesiredLevelOfDetail(1); { bool lodCreated = false; KisStrokeId id = p.image->startStroke( new ForbiddenLodStrokeStrategy( std::bind(¬ifyVar, &lodCreated))); p.image->endStroke(id); p.image->waitForDone(); QVERIFY(lodCreated); } } void KisImageTest::testConvertImageColorSpace() { const KoColorSpace *cs8 = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, 1000, 1000, cs8, "stest"); KisPaintDeviceSP device1 = new KisPaintDevice(cs8); KisLayerSP paint1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8, device1); KisFilterSP filter = KisFilterRegistry::instance()->value("blur"); Q_ASSERT(filter); KisFilterConfigurationSP configuration = filter->defaultConfiguration(); Q_ASSERT(configuration); KisLayerSP blur1 = new KisAdjustmentLayer(image, "blur1", configuration, 0); image->addNode(paint1, image->root()); image->addNode(blur1, image->root()); image->refreshGraph(); const KoColorSpace *cs16 = KoColorSpaceRegistry::instance()->rgb16(); image->lock(); image->convertImageColorSpace(cs16, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); image->unlock(); QVERIFY(*cs16 == *image->colorSpace()); QVERIFY(*cs16 == *image->root()->colorSpace()); QVERIFY(*cs16 == *paint1->colorSpace()); QVERIFY(*cs16 == *blur1->colorSpace()); QVERIFY(!image->root()->compositeOp()); QVERIFY(*cs16 == *paint1->compositeOp()->colorSpace()); QVERIFY(*cs16 == *blur1->compositeOp()->colorSpace()); image->refreshGraph(); } void KisImageTest::testGlobalSelection() { const KoColorSpace *cs8 = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, 1000, 1000, cs8, "stest"); QCOMPARE(image->globalSelection(), KisSelectionSP(0)); QCOMPARE(image->canReselectGlobalSelection(), false); QCOMPARE(image->root()->childCount(), 0U); KisSelectionSP selection1 = new KisSelection(new KisDefaultBounds(image)); KisSelectionSP selection2 = new KisSelection(new KisDefaultBounds(image)); image->setGlobalSelection(selection1); QCOMPARE(image->globalSelection(), selection1); QCOMPARE(image->canReselectGlobalSelection(), false); QCOMPARE(image->root()->childCount(), 1U); image->setGlobalSelection(selection2); QCOMPARE(image->globalSelection(), selection2); QCOMPARE(image->canReselectGlobalSelection(), false); QCOMPARE(image->root()->childCount(), 1U); image->deselectGlobalSelection(); QCOMPARE(image->globalSelection(), KisSelectionSP(0)); QCOMPARE(image->canReselectGlobalSelection(), true); QCOMPARE(image->root()->childCount(), 0U); image->reselectGlobalSelection(); QCOMPARE(image->globalSelection(), selection2); QCOMPARE(image->canReselectGlobalSelection(), false); QCOMPARE(image->root()->childCount(), 1U); // mixed deselecting/setting/reselecting image->deselectGlobalSelection(); QCOMPARE(image->globalSelection(), KisSelectionSP(0)); QCOMPARE(image->canReselectGlobalSelection(), true); QCOMPARE(image->root()->childCount(), 0U); image->setGlobalSelection(selection1); QCOMPARE(image->globalSelection(), selection1); QCOMPARE(image->canReselectGlobalSelection(), false); QCOMPARE(image->root()->childCount(), 1U); } void KisImageTest::testCloneImage() { KisImageSP image = new KisImage(0, IMAGE_WIDTH, IMAGE_WIDTH, 0, "layer tests"); QVERIFY(image->rootLayer() != 0); QVERIFY(image->rootLayer()->firstChild() == 0); KisAnnotationSP annotation = new KisAnnotation("mytype", "mydescription", QByteArray()); image->addAnnotation(annotation); QVERIFY(image->annotation("mytype")); KisProofingConfigurationSP proofing = toQShared(new KisProofingConfiguration()); image->setProofingConfiguration(proofing); QVERIFY(image->proofingConfiguration()); const KoColor defaultColor(Qt::green, image->colorSpace()); image->setDefaultProjectionColor(defaultColor); QCOMPARE(image->defaultProjectionColor(), defaultColor); KisLayerSP layer = new KisPaintLayer(image, "layer1", OPACITY_OPAQUE_U8); image->addNode(layer); KisLayerSP layer2 = new KisPaintLayer(image, "layer2", OPACITY_OPAQUE_U8); image->addNode(layer2); QVERIFY(layer->visible()); QVERIFY(layer2->visible()); QVERIFY(TestUtil::findNode(image->root(), "layer1")); QVERIFY(TestUtil::findNode(image->root(), "layer2")); QUuid uuid1 = layer->uuid(); QUuid uuid2 = layer2->uuid(); { KisImageSP newImage = image->clone(); KisNodeSP newLayer1 = TestUtil::findNode(newImage->root(), "layer1"); KisNodeSP newLayer2 = TestUtil::findNode(newImage->root(), "layer2"); QVERIFY(newLayer1); QVERIFY(newLayer2); QVERIFY(newLayer1->uuid() != uuid1); QVERIFY(newLayer2->uuid() != uuid2); KisAnnotationSP newAnnotation = newImage->annotation("mytype"); QVERIFY(newAnnotation); QVERIFY(newAnnotation != annotation); KisProofingConfigurationSP newProofing = newImage->proofingConfiguration(); QVERIFY(newProofing); QVERIFY(newProofing != proofing); QCOMPARE(newImage->defaultProjectionColor(), defaultColor); } { KisImageSP newImage = image->clone(true); KisNodeSP newLayer1 = TestUtil::findNode(newImage->root(), "layer1"); KisNodeSP newLayer2 = TestUtil::findNode(newImage->root(), "layer2"); QVERIFY(newLayer1); QVERIFY(newLayer2); QVERIFY(newLayer1->uuid() == uuid1); QVERIFY(newLayer2->uuid() == uuid2); } } void KisImageTest::testLayerComposition() { KisImageSP image = new KisImage(0, IMAGE_WIDTH, IMAGE_WIDTH, 0, "layer tests"); QVERIFY(image->rootLayer() != 0); QVERIFY(image->rootLayer()->firstChild() == 0); KisLayerSP layer = new KisPaintLayer(image, "layer1", OPACITY_OPAQUE_U8); image->addNode(layer); KisLayerSP layer2 = new KisPaintLayer(image, "layer2", OPACITY_OPAQUE_U8); image->addNode(layer2); QVERIFY(layer->visible()); QVERIFY(layer2->visible()); KisLayerComposition comp(image, "comp 1"); comp.store(); layer2->setVisible(false); QVERIFY(layer->visible()); QVERIFY(!layer2->visible()); KisLayerComposition comp2(image, "comp 2"); comp2.store(); KisLayerCompositionSP comp3 = toQShared(new KisLayerComposition(image, "comp 3")); comp3->store(); image->addComposition(comp3); comp.apply(); QVERIFY(layer->visible()); QVERIFY(layer2->visible()); comp2.apply(); QVERIFY(layer->visible()); QVERIFY(!layer2->visible()); comp.apply(); QVERIFY(layer->visible()); QVERIFY(layer2->visible()); KisImageSP newImage = image->clone(); KisNodeSP newLayer1 = TestUtil::findNode(newImage->root(), "layer1"); KisNodeSP newLayer2 = TestUtil::findNode(newImage->root(), "layer2"); QVERIFY(newLayer1); QVERIFY(newLayer2); QVERIFY(newLayer1->visible()); QVERIFY(newLayer2->visible()); KisLayerComposition newComp1(comp, newImage); newComp1.apply(); QVERIFY(newLayer1->visible()); QVERIFY(newLayer2->visible()); KisLayerComposition newComp2(comp2, newImage); newComp2.apply(); QVERIFY(newLayer1->visible()); QVERIFY(!newLayer2->visible()); newComp1.apply(); QVERIFY(newLayer1->visible()); QVERIFY(newLayer2->visible()); QVERIFY(!newImage->compositions().isEmpty()); KisLayerCompositionSP newComp3 = newImage->compositions().first(); newComp3->apply(); QVERIFY(newLayer1->visible()); QVERIFY(!newLayer2->visible()); } #include "kis_transparency_mask.h" #include "kis_psd_layer_style.h" struct FlattenTestImage { FlattenTestImage() : refRect(0,0,512,512) , p(refRect) { image = p.image; undoStore = p.undoStore; layer1 = p.layer; layer5 = new KisPaintLayer(p.image, "paint5", 0.4 * OPACITY_OPAQUE_U8); layer5->disableAlphaChannel(true); layer2 = new KisPaintLayer(p.image, "paint2", OPACITY_OPAQUE_U8); tmask = new KisTransparencyMask(); // check channel flags // make addition composite op group1 = new KisGroupLayer(p.image, "group1", OPACITY_OPAQUE_U8); layer3 = new KisPaintLayer(p.image, "paint3", OPACITY_OPAQUE_U8); layer4 = new KisPaintLayer(p.image, "paint4", OPACITY_OPAQUE_U8); layer6 = new KisPaintLayer(p.image, "paint6", OPACITY_OPAQUE_U8); layer7 = new KisPaintLayer(p.image, "paint7", OPACITY_OPAQUE_U8); layer8 = new KisPaintLayer(p.image, "paint8", OPACITY_OPAQUE_U8); layer7->setCompositeOpId(COMPOSITE_ADD); layer8->setCompositeOpId(COMPOSITE_ADD); QRect rect1(100, 100, 100, 100); QRect rect2(150, 150, 150, 150); QRect tmaskRect(200,200,100,100); QRect rect3(400, 100, 100, 100); QRect rect4(500, 100, 100, 100); QRect rect5(50, 50, 100, 100); QRect rect6(50, 250, 100, 100); QRect rect7(50, 350, 50, 50); QRect rect8(50, 400, 50, 50); layer1->paintDevice()->fill(rect1, KoColor(Qt::red, p.image->colorSpace())); layer2->paintDevice()->fill(rect2, KoColor(Qt::green, p.image->colorSpace())); tmask->testingInitSelection(tmaskRect, layer2); layer3->paintDevice()->fill(rect3, KoColor(Qt::blue, p.image->colorSpace())); layer4->paintDevice()->fill(rect4, KoColor(Qt::yellow, p.image->colorSpace())); layer5->paintDevice()->fill(rect5, KoColor(Qt::green, p.image->colorSpace())); layer6->paintDevice()->fill(rect6, KoColor(Qt::cyan, p.image->colorSpace())); layer7->paintDevice()->fill(rect7, KoColor(Qt::red, p.image->colorSpace())); layer8->paintDevice()->fill(rect8, KoColor(Qt::green, p.image->colorSpace())); KisPSDLayerStyleSP style(new KisPSDLayerStyle()); style->dropShadow()->setEffectEnabled(true); style->dropShadow()->setDistance(10.0); style->dropShadow()->setSpread(80.0); style->dropShadow()->setSize(10); style->dropShadow()->setNoise(0); style->dropShadow()->setKnocksOut(false); style->dropShadow()->setOpacity(80.0); layer2->setLayerStyle(style); layer2->setCompositeOpId(COMPOSITE_ADD); group1->setCompositeOpId(COMPOSITE_ADD); p.image->addNode(layer5); p.image->addNode(layer2); p.image->addNode(tmask, layer2); p.image->addNode(group1); p.image->addNode(layer3, group1); p.image->addNode(layer4, group1); p.image->addNode(layer6); p.image->addNode(layer7); p.image->addNode(layer8); p.image->initialRefreshGraph(); // dbgKrita << ppVar(layer1->exactBounds()); // dbgKrita << ppVar(layer5->exactBounds()); // dbgKrita << ppVar(layer2->exactBounds()); // dbgKrita << ppVar(group1->exactBounds()); // dbgKrita << ppVar(layer3->exactBounds()); // dbgKrita << ppVar(layer4->exactBounds()); TestUtil::ReferenceImageChecker chk("flatten", "imagetest"); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); } QRect refRect; TestUtil::MaskParent p; KisImageSP image; KisSurrogateUndoStore *undoStore; KisPaintLayerSP layer1; KisPaintLayerSP layer2; KisTransparencyMaskSP tmask; KisGroupLayerSP group1; KisPaintLayerSP layer3; KisPaintLayerSP layer4; KisPaintLayerSP layer5; KisPaintLayerSP layer6; KisPaintLayerSP layer7; KisPaintLayerSP layer8; }; template KisLayerSP flattenLayerHelper(ContainerTest &p, KisLayerSP layer, bool nothingHappens = false) { QSignalSpy spy(p.image.data(), SIGNAL(sigNodeAddedAsync(KisNodeSP))); //p.image->flattenLayer(layer); KisLayerUtils::flattenLayer(p.image, layer); p.image->waitForDone(); if (nothingHappens) { Q_ASSERT(!spy.count()); return layer; } Q_ASSERT(spy.count() == 1); QList arguments = spy.takeFirst(); KisNodeSP newNode = arguments.first().value(); KisLayerSP newLayer = qobject_cast(newNode.data()); return newLayer; } void KisImageTest::testFlattenLayer() { FlattenTestImage p; TestUtil::ReferenceImageChecker chk("flatten", "imagetest"); { QCOMPARE(p.layer2->compositeOpId(), COMPOSITE_ADD); KisLayerSP newLayer = flattenLayerHelper(p, p.layer2); //KisLayerSP newLayer = p.image->flattenLayer(p.layer2); //p.image->waitForDone(); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer2_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); } { QCOMPARE(p.group1->compositeOpId(), COMPOSITE_ADD); KisLayerSP newLayer = flattenLayerHelper(p, p.group1); //KisLayerSP newLayer = p.image->flattenLayer(p.group1); //p.image->waitForDone(); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "02_group1_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_ADD); QCOMPARE(newLayer->exactBounds(), QRect(400, 100, 200, 100)); } { QCOMPARE(p.layer5->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.layer5->alphaChannelDisabled(), true); KisLayerSP newLayer = flattenLayerHelper(p, p.layer5, true); //KisLayerSP newLayer = p.image->flattenLayer(p.layer5); //p.image->waitForDone(); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "03_layer5_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(50, 50, 100, 100)); QCOMPARE(newLayer->alphaChannelDisabled(), true); } } #include template KisLayerSP mergeHelper(ContainerTest &p, KisLayerSP layer) { KisNodeSP parent = layer->parent(); const int newIndex = parent->index(layer) - 1; p.image->mergeDown(layer, KisMetaData::MergeStrategyRegistry::instance()->get("Drop")); //KisLayerUtils::mergeDown(p.image, layer, KisMetaData::MergeStrategyRegistry::instance()->get("Drop")); p.image->waitForDone(); KisLayerSP newLayer = qobject_cast(parent->at(newIndex).data()); return newLayer; } void KisImageTest::testMergeDown() { FlattenTestImage p; TestUtil::ReferenceImageChecker img("flatten", "imagetest"); TestUtil::ReferenceImageChecker chk("mergedown_simple", "imagetest"); { QCOMPARE(p.layer5->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.layer5->alphaChannelDisabled(), true); KisLayerSP newLayer = mergeHelper(p, p.layer5); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer5_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->alphaChannelDisabled(), false); } { QCOMPARE(p.layer2->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.layer2->alphaChannelDisabled(), false); KisLayerSP newLayer = mergeHelper(p, p.layer2); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "02_layer2_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(100, 100, 213, 217)); QCOMPARE(newLayer->alphaChannelDisabled(), false); } { QCOMPARE(p.group1->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.group1->alphaChannelDisabled(), false); KisLayerSP newLayer = mergeHelper(p, p.group1); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "03_group1_mergedown_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(100, 100, 500, 217)); QCOMPARE(newLayer->alphaChannelDisabled(), false); } } void KisImageTest::testMergeDownDestinationInheritsAlpha() { FlattenTestImage p; TestUtil::ReferenceImageChecker img("flatten", "imagetest"); TestUtil::ReferenceImageChecker chk("mergedown_dst_inheritsalpha", "imagetest"); { QCOMPARE(p.layer2->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.layer2->alphaChannelDisabled(), false); KisLayerSP newLayer = mergeHelper(p, p.layer2); // WARN: this check is suspicious! QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_proj_merged_layer2_over_layer5_IA")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer2_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(50,50, 263, 267)); QCOMPARE(newLayer->alphaChannelDisabled(), false); } } void KisImageTest::testMergeDownDestinationCustomCompositeOp() { FlattenTestImage p; TestUtil::ReferenceImageChecker img("flatten", "imagetest"); TestUtil::ReferenceImageChecker chk("mergedown_dst_customop", "imagetest"); { QCOMPARE(p.layer6->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.layer6->alphaChannelDisabled(), false); QCOMPARE(p.group1->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.group1->alphaChannelDisabled(), false); KisLayerSP newLayer = mergeHelper(p, p.layer6); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer6_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(50, 100, 550, 250)); QCOMPARE(newLayer->alphaChannelDisabled(), false); } } void KisImageTest::testMergeDownDestinationSameCompositeOpLayerStyle() { FlattenTestImage p; TestUtil::ReferenceImageChecker img("flatten", "imagetest"); TestUtil::ReferenceImageChecker chk("mergedown_sameop_ls", "imagetest"); { QCOMPARE(p.group1->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.group1->alphaChannelDisabled(), false); QCOMPARE(p.layer2->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.layer2->alphaChannelDisabled(), false); KisLayerSP newLayer = mergeHelper(p, p.group1); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_group1_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(197, 100, 403, 217)); QCOMPARE(newLayer->alphaChannelDisabled(), false); } } void KisImageTest::testMergeDownDestinationSameCompositeOp() { FlattenTestImage p; TestUtil::ReferenceImageChecker img("flatten", "imagetest"); TestUtil::ReferenceImageChecker chk("mergedown_sameop_fastpath", "imagetest"); { QCOMPARE(p.layer8->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.layer8->alphaChannelDisabled(), false); QCOMPARE(p.layer7->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.layer7->alphaChannelDisabled(), false); KisLayerSP newLayer = mergeHelper(p, p.layer8); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer8_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_ADD); QCOMPARE(newLayer->exactBounds(), QRect(50, 350, 50, 100)); QCOMPARE(newLayer->alphaChannelDisabled(), false); } } #include "kis_image_animation_interface.h" void KisImageTest::testMergeDownMultipleFrames() { FlattenTestImage p; TestUtil::ReferenceImageChecker img("flatten", "imagetest"); TestUtil::ReferenceImageChecker chk("mergedown_simple", "imagetest"); QSet initialFrames; { KisLayerSP l = p.layer5; l->enableAnimation(); KisKeyframeChannel *channel = l->getKeyframeChannel(KisKeyframeChannel::Content.id(), true); channel->addKeyframe(10); channel->addKeyframe(20); channel->addKeyframe(30); QCOMPARE(channel->keyframeCount(), 4); initialFrames = KisLayerUtils::fetchLayerFramesRecursive(l); QCOMPARE(initialFrames.size(), 4); } { QCOMPARE(p.layer5->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.layer5->alphaChannelDisabled(), true); KisLayerSP newLayer = mergeHelper(p, p.layer5); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer5_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->alphaChannelDisabled(), false); QVERIFY(newLayer->isAnimated()); QSet newFrames = KisLayerUtils::fetchLayerFramesRecursive(newLayer); QCOMPARE(newFrames, initialFrames); foreach (int frame, newFrames) { KisImageAnimationInterface *interface = p.image->animationInterface(); int savedSwitchedTime = 0; interface->saveAndResetCurrentTime(frame, &savedSwitchedTime); QCOMPARE(newLayer->exactBounds(), QRect(100,100,100,100)); interface->restoreCurrentTime(&savedSwitchedTime); } p.undoStore->undo(); p.image->waitForDone(); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); } } template KisNodeSP mergeMultipleHelper(ContainerTest &p, QList selectedNodes, KisNodeSP putAfter) { QSignalSpy spy(p.image.data(), SIGNAL(sigNodeAddedAsync(KisNodeSP))); p.image->mergeMultipleLayers(selectedNodes, putAfter); //KisLayerUtils::mergeMultipleLayers(p.image, selectedNodes, putAfter); p.image->waitForDone(); Q_ASSERT(spy.count() == 1); QList arguments = spy.takeFirst(); KisNodeSP newNode = arguments.first().value(); return newNode; } void KisImageTest::testMergeMultiple() { FlattenTestImage p; TestUtil::ReferenceImageChecker img("flatten", "imagetest"); TestUtil::ReferenceImageChecker chk("mergemultiple", "imagetest"); { QList selectedNodes; selectedNodes << p.layer2 << p.group1 << p.layer6; { KisNodeSP newLayer = mergeMultipleHelper(p, selectedNodes, 0); //KisNodeSP newLayer = p.image->mergeMultipleLayers(selectedNodes, 0); //p.image->waitForDone(); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer8_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(50, 100, 550, 250)); } } p.p.undoStore->undo(); p.image->waitForDone(); // Test reversed order, the result must be the same { QList selectedNodes; selectedNodes << p.layer6 << p.group1 << p.layer2; { KisNodeSP newLayer = mergeMultipleHelper(p, selectedNodes, 0); //KisNodeSP newLayer = p.image->mergeMultipleLayers(selectedNodes, 0); //p.image->waitForDone(); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer8_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(50, 100, 550, 250)); } } } void testMergeCrossColorSpaceImpl(bool useProjectionColorSpace, bool swapSpaces) { TestUtil::MaskParent p; KisPaintLayerSP layer1; KisPaintLayerSP layer2; KisPaintLayerSP layer3; const KoColorSpace *cs2 = useProjectionColorSpace ? p.image->colorSpace() : KoColorSpaceRegistry::instance()->lab16(); const KoColorSpace *cs3 = KoColorSpaceRegistry::instance()->rgb16(); if (swapSpaces) { std::swap(cs2, cs3); } dbgKrita << "Testing testMergeCrossColorSpaceImpl:"; dbgKrita << " " << ppVar(cs2); dbgKrita << " " << ppVar(cs3); layer1 = p.layer; layer2 = new KisPaintLayer(p.image, "paint2", OPACITY_OPAQUE_U8, cs2); layer3 = new KisPaintLayer(p.image, "paint3", OPACITY_OPAQUE_U8, cs3); QRect rect1(100, 100, 100, 100); QRect rect2(150, 150, 150, 150); QRect rect3(250, 250, 200, 200); layer1->paintDevice()->fill(rect1, KoColor(Qt::red, layer1->colorSpace())); layer2->paintDevice()->fill(rect2, KoColor(Qt::green, layer2->colorSpace())); layer3->paintDevice()->fill(rect3, KoColor(Qt::blue, layer3->colorSpace())); p.image->addNode(layer2); p.image->addNode(layer3); p.image->initialRefreshGraph(); { KisLayerSP newLayer = mergeHelper(p, layer3); QCOMPARE(newLayer->colorSpace(), p.image->colorSpace()); p.undoStore->undo(); p.image->waitForDone(); } { layer2->disableAlphaChannel(true); KisLayerSP newLayer = mergeHelper(p, layer3); QCOMPARE(newLayer->colorSpace(), p.image->colorSpace()); p.undoStore->undo(); p.image->waitForDone(); } } void KisImageTest::testMergeCrossColorSpace() { testMergeCrossColorSpaceImpl(true, false); testMergeCrossColorSpaceImpl(true, true); testMergeCrossColorSpaceImpl(false, false); testMergeCrossColorSpaceImpl(false, true); } void KisImageTest::testMergeSelectionMasks() { TestUtil::MaskParent p; QRect rect1(100, 100, 100, 100); QRect rect2(150, 150, 150, 150); QRect rect3(50, 50, 100, 100); KisPaintLayerSP layer1 = p.layer; layer1->paintDevice()->fill(rect1, KoColor(Qt::red, layer1->colorSpace())); p.image->initialRefreshGraph(); KisSelectionSP sel = new KisSelection(layer1->paintDevice()->defaultBounds()); sel->pixelSelection()->select(rect2, MAX_SELECTED); KisSelectionMaskSP mask1 = new KisSelectionMask(p.image); mask1->initSelection(sel, layer1); p.image->addNode(mask1, layer1); QVERIFY(!layer1->selection()); mask1->setActive(true); QCOMPARE(layer1->selection()->selectedExactRect(), QRect(150,150,150,150)); sel->pixelSelection()->select(rect3, MAX_SELECTED); KisSelectionMaskSP mask2 = new KisSelectionMask(p.image); mask2->initSelection(sel, layer1); p.image->addNode(mask2, layer1); QCOMPARE(layer1->selection()->selectedExactRect(), QRect(150,150,150,150)); mask2->setActive(true); QCOMPARE(layer1->selection()->selectedExactRect(), QRect(50,50,250,250)); QList selectedNodes; selectedNodes << mask2 << mask1; { KisNodeSP newLayer = mergeMultipleHelper(p, selectedNodes, 0); QCOMPARE(newLayer->parent(), KisNodeSP(layer1)); QCOMPARE((int)layer1->childCount(), 1); QCOMPARE(layer1->selection()->selectedExactRect(), QRect(50,50,250,250)); } } void KisImageTest::testFlattenImage() { FlattenTestImage p; KisImageSP image = p.image; TestUtil::ReferenceImageChecker img("flatten", "imagetest"); { KisLayerUtils::flattenImage(p.image, 0); p.image->waitForDone(); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); p.undoStore->undo(); p.image->waitForDone(); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); } { KisLayerUtils::flattenImage(p.image, p.layer5); // flatten with active layer just under the root (not inside any group) p.image->waitForDone(); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); p.undoStore->undo(); p.image->waitForDone(); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); } { KisLayerUtils::flattenImage(p.image, p.layer2); // flatten with active layer just under the root (not inside any group), but with a mask p.image->waitForDone(); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); p.undoStore->undo(); p.image->waitForDone(); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); } { KisLayerUtils::flattenImage(p.image, p.layer3); // flatten with active layer inside of a group p.image->waitForDone(); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); p.undoStore->undo(); p.image->waitForDone(); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); } } struct FlattenPassThroughTestImage { FlattenPassThroughTestImage() : refRect(0,0,512,512) , p(refRect) { image = p.image; undoStore = p.undoStore; group1 = new KisGroupLayer(p.image, "group1", OPACITY_OPAQUE_U8); layer2 = new KisPaintLayer(p.image, "paint2", OPACITY_OPAQUE_U8); layer3 = new KisPaintLayer(p.image, "paint3", OPACITY_OPAQUE_U8); group4 = new KisGroupLayer(p.image, "group4", OPACITY_OPAQUE_U8); layer5 = new KisPaintLayer(p.image, "paint5", OPACITY_OPAQUE_U8); layer6 = new KisPaintLayer(p.image, "paint6", OPACITY_OPAQUE_U8); QRect rect2(100, 100, 100, 100); QRect rect3(150, 150, 100, 100); QRect rect5(200, 200, 100, 100); QRect rect6(250, 250, 100, 100); group1->setPassThroughMode(true); layer2->paintDevice()->fill(rect2, KoColor(Qt::red, p.image->colorSpace())); layer3->paintDevice()->fill(rect3, KoColor(Qt::green, p.image->colorSpace())); group4->setPassThroughMode(true); layer5->paintDevice()->fill(rect5, KoColor(Qt::blue, p.image->colorSpace())); layer6->paintDevice()->fill(rect6, KoColor(Qt::yellow, p.image->colorSpace())); p.image->addNode(group1); p.image->addNode(layer2, group1); p.image->addNode(layer3, group1); p.image->addNode(group4); p.image->addNode(layer5, group4); p.image->addNode(layer6, group4); p.image->initialRefreshGraph(); TestUtil::ReferenceImageChecker chk("passthrough", "imagetest"); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); } QRect refRect; TestUtil::MaskParent p; KisImageSP image; KisSurrogateUndoStore *undoStore; KisGroupLayerSP group1; KisPaintLayerSP layer2; KisPaintLayerSP layer3; KisGroupLayerSP group4; KisPaintLayerSP layer5; KisPaintLayerSP layer6; }; void KisImageTest::testFlattenPassThroughLayer() { FlattenPassThroughTestImage p; TestUtil::ReferenceImageChecker chk("passthrough", "imagetest"); { QCOMPARE(p.group1->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.group1->passThroughMode(), true); KisLayerSP newLayer = flattenLayerHelper(p, p.group1); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_group1_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QVERIFY(newLayer->inherits("KisPaintLayer")); } } void KisImageTest::testMergeTwoPassThroughLayers() { FlattenPassThroughTestImage p; TestUtil::ReferenceImageChecker chk("passthrough", "imagetest"); { QCOMPARE(p.group1->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.group1->passThroughMode(), true); KisLayerSP newLayer = mergeHelper(p, p.group4); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QVERIFY(newLayer->inherits("KisGroupLayer")); } } void KisImageTest::testMergePaintOverPassThroughLayer() { FlattenPassThroughTestImage p; TestUtil::ReferenceImageChecker chk("passthrough", "imagetest"); { QCOMPARE(p.group1->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.group1->passThroughMode(), true); KisLayerSP newLayer = flattenLayerHelper(p, p.group4); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(newLayer->inherits("KisPaintLayer")); newLayer = mergeHelper(p, newLayer); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(newLayer->inherits("KisPaintLayer")); } } void KisImageTest::testMergePassThroughOverPaintLayer() { FlattenPassThroughTestImage p; TestUtil::ReferenceImageChecker chk("passthrough", "imagetest"); { QCOMPARE(p.group1->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.group1->passThroughMode(), true); KisLayerSP newLayer = flattenLayerHelper(p, p.group1); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(newLayer->inherits("KisPaintLayer")); newLayer = mergeHelper(p, p.group4); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(newLayer->inherits("KisPaintLayer")); } } #include "kis_paint_device_debug_utils.h" #include "kis_algebra_2d.h" void KisImageTest::testPaintOverlayMask() { QRect refRect(0, 0, 512, 512); TestUtil::MaskParent p(refRect); QRect fillRect(50, 50, 412, 412); QRect selectionRect(200, 200, 100, 50); KisPaintLayerSP layer1 = p.layer; layer1->paintDevice()->fill(fillRect, KoColor(Qt::yellow, layer1->colorSpace())); KisSelectionMaskSP mask = new KisSelectionMask(p.image); - KisSelectionSP selection = new KisSelection(new KisSelectionDefaultBounds(layer1->paintDevice(), p.image)); + KisSelectionSP selection = new KisSelection(new KisSelectionDefaultBounds(layer1->paintDevice())); selection->pixelSelection()->select(selectionRect, 128); selection->pixelSelection()->select(KisAlgebra2D::blowRect(selectionRect,-0.3), 255); mask->setSelection(selection); //mask->setVisible(false); //mask->setActive(false); p.image->addNode(mask, layer1); // a simple layer to disable oblidge child mechanism KisPaintLayerSP layer2 = new KisPaintLayer(p.image, "layer2", OPACITY_OPAQUE_U8); p.image->addNode(layer2); p.image->initialRefreshGraph(); KIS_DUMP_DEVICE_2(p.image->projection(), refRect, "00_initial", "dd"); p.image->setOverlaySelectionMask(mask); p.image->waitForDone(); KIS_DUMP_DEVICE_2(p.image->projection(), refRect, "01_activated", "dd"); p.image->setOverlaySelectionMask(0); p.image->waitForDone(); KIS_DUMP_DEVICE_2(p.image->projection(), refRect, "02_deactivated", "dd"); } QTEST_MAIN(KisImageTest) diff --git a/libs/image/tests/kis_layer_style_projection_plane_test.cpp b/libs/image/tests/kis_layer_style_projection_plane_test.cpp index 52105b92b5..095c1f56f7 100644 --- a/libs/image/tests/kis_layer_style_projection_plane_test.cpp +++ b/libs/image/tests/kis_layer_style_projection_plane_test.cpp @@ -1,499 +1,617 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_layer_style_projection_plane_test.h" #include #include "testutil.h" #include #include #include #include #include "kis_transparency_mask.h" #include "kis_paint_layer.h" #include "kis_image.h" #include "kis_painter.h" #include "kis_selection.h" #include "kis_pixel_selection.h" #include "layerstyles/kis_layer_style_projection_plane.h" #include "kis_psd_layer_style.h" #include "kis_paint_device_debug_utils.h" void KisLayerStyleProjectionPlaneTest::test(KisPSDLayerStyleSP style, const QString testName) { const QRect imageRect(0, 0, 200, 200); const QRect rFillRect(10, 10, 100, 100); const QRect tMaskRect(50, 50, 20, 20); const QRect partialSelectionRect(90, 50, 20, 20); const QRect updateRect1(10, 10, 50, 100); const QRect updateRect2(60, 10, 50, 100); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, imageRect.width(), imageRect.height(), cs, "styles test"); KisPaintLayerSP layer = new KisPaintLayer(image, "test", OPACITY_OPAQUE_U8); image->addNode(layer); KisLayerStyleProjectionPlane plane(layer.data(), style); KIS_DUMP_DEVICE_2(layer->projection(), imageRect, "00L_initial", testName); //layer->paintDevice()->fill(rFillRect, KoColor(Qt::red, cs)); { KisPainter gc(layer->paintDevice()); gc.setPaintColor(KoColor(Qt::red, cs)); gc.setFillStyle(KisPainter::FillStyleForegroundColor); gc.paintEllipse(rFillRect); } KIS_DUMP_DEVICE_2(layer->projection(), imageRect, "01L_fill", testName); KisPaintDeviceSP projection = new KisPaintDevice(cs); { const QRect changeRect = plane.changeRect(rFillRect, KisLayer::N_FILTHY); dbgKrita << ppVar(rFillRect) << ppVar(changeRect); plane.recalculate(changeRect, layer); KIS_DUMP_DEVICE_2(layer->projection(), imageRect, "02L_recalculate_fill", testName); KisPainter painter(projection); plane.apply(&painter, changeRect); KIS_DUMP_DEVICE_2(projection, imageRect, "03P_apply_on_fill", testName); } //return; KisTransparencyMaskSP transparencyMask = new KisTransparencyMask(); KisSelectionSP selection = new KisSelection(); selection->pixelSelection()->select(tMaskRect, OPACITY_OPAQUE_U8); transparencyMask->setSelection(selection); image->addNode(transparencyMask, layer); KIS_DUMP_DEVICE_2(layer->projection(), imageRect, "04L_mask_added", testName); plane.recalculate(imageRect, layer); KIS_DUMP_DEVICE_2(layer->projection(), imageRect, "05L_mask_added_recalculated", testName); { projection->clear(); KisPainter painter(projection); plane.apply(&painter, imageRect); KIS_DUMP_DEVICE_2(projection, imageRect, "06P_apply_on_mask", testName); } selection->pixelSelection()->select(partialSelectionRect, OPACITY_OPAQUE_U8); { const QRect changeRect = plane.changeRect(partialSelectionRect, KisLayer::N_FILTHY); projection->clear(changeRect); dbgKrita << ppVar(partialSelectionRect) << ppVar(changeRect); plane.recalculate(changeRect, layer); KIS_DUMP_DEVICE_2(layer->projection(), imageRect, "07L_recalculate_partial", testName); KisPainter painter(projection); plane.apply(&painter, changeRect); KIS_DUMP_DEVICE_2(projection, imageRect, "08P_apply_partial", testName); } // half updates transparencyMask->setVisible(false); { const QRect changeRect = plane.changeRect(updateRect1, KisLayer::N_FILTHY); projection->clear(changeRect); dbgKrita << ppVar(updateRect1) << ppVar(changeRect); plane.recalculate(changeRect, layer); KIS_DUMP_DEVICE_2(layer->projection(), imageRect, "09L_recalculate_half1", testName); KisPainter painter(projection); plane.apply(&painter, changeRect); KIS_DUMP_DEVICE_2(projection, imageRect, "10P_apply_half1", testName); } { const QRect changeRect = plane.changeRect(updateRect2, KisLayer::N_FILTHY); projection->clear(changeRect); dbgKrita << ppVar(updateRect2) << ppVar(changeRect); plane.recalculate(changeRect, layer); KIS_DUMP_DEVICE_2(layer->projection(), imageRect, "09L_recalculate_half1", testName); KisPainter painter(projection); plane.apply(&painter, changeRect); KIS_DUMP_DEVICE_2(projection, imageRect, "10P_apply_half2", testName); } } void KisLayerStyleProjectionPlaneTest::testShadow() { KisPSDLayerStyleSP style(new KisPSDLayerStyle()); style->dropShadow()->setSize(15); style->dropShadow()->setDistance(15); style->dropShadow()->setOpacity(70); style->dropShadow()->setNoise(30); style->dropShadow()->setEffectEnabled(true); style->innerShadow()->setSize(10); style->innerShadow()->setSpread(10); style->innerShadow()->setDistance(5); style->innerShadow()->setOpacity(70); style->innerShadow()->setNoise(30); style->innerShadow()->setEffectEnabled(true); test(style, "shadow"); } void KisLayerStyleProjectionPlaneTest::testGlow() { KisPSDLayerStyleSP style(new KisPSDLayerStyle()); style->outerGlow()->setSize(15); style->outerGlow()->setSpread(10); style->outerGlow()->setOpacity(70); style->outerGlow()->setNoise(30); style->outerGlow()->setEffectEnabled(true); style->outerGlow()->setColor(Qt::green); test(style, "glow_outer"); } #include void KisLayerStyleProjectionPlaneTest::testGlowGradient() { KisPSDLayerStyleSP style(new KisPSDLayerStyle()); style->outerGlow()->setSize(15); style->outerGlow()->setSpread(10); style->outerGlow()->setOpacity(70); style->outerGlow()->setNoise(10); style->outerGlow()->setEffectEnabled(true); style->outerGlow()->setColor(Qt::green); QLinearGradient testGradient; testGradient.setColorAt(0.0, Qt::white); testGradient.setColorAt(0.5, Qt::green); testGradient.setColorAt(1.0, Qt::black); testGradient.setSpread(QGradient::ReflectSpread); QSharedPointer gradient( KoStopGradient::fromQGradient(&testGradient)); style->outerGlow()->setGradient(gradient); style->outerGlow()->setFillType(psd_fill_gradient); test(style, "glow_outer_grad"); } void KisLayerStyleProjectionPlaneTest::testGlowGradientJitter() { KisPSDLayerStyleSP style(new KisPSDLayerStyle()); style->outerGlow()->setSize(15); style->outerGlow()->setSpread(10); style->outerGlow()->setOpacity(70); style->outerGlow()->setNoise(0); style->outerGlow()->setEffectEnabled(true); style->outerGlow()->setColor(Qt::green); QLinearGradient testGradient; testGradient.setColorAt(0.0, Qt::white); testGradient.setColorAt(0.5, Qt::green); testGradient.setColorAt(1.0, Qt::black); testGradient.setSpread(QGradient::ReflectSpread); QSharedPointer gradient( KoStopGradient::fromQGradient(&testGradient)); style->outerGlow()->setGradient(gradient); style->outerGlow()->setFillType(psd_fill_gradient); style->outerGlow()->setJitter(20); test(style, "glow_outer_grad_jit"); } void KisLayerStyleProjectionPlaneTest::testGlowInnerGradient() { KisPSDLayerStyleSP style(new KisPSDLayerStyle()); style->innerGlow()->setSize(15); style->innerGlow()->setSpread(10); style->innerGlow()->setOpacity(80); style->innerGlow()->setNoise(10); style->innerGlow()->setEffectEnabled(true); style->innerGlow()->setColor(Qt::white); QLinearGradient testGradient; testGradient.setColorAt(0.0, Qt::white); testGradient.setColorAt(0.5, Qt::green); testGradient.setColorAt(1.0, Qt::black); testGradient.setSpread(QGradient::ReflectSpread); QSharedPointer gradient( KoStopGradient::fromQGradient(&testGradient)); style->innerGlow()->setGradient(gradient); style->innerGlow()->setFillType(psd_fill_gradient); test(style, "glow_inner_grad"); style->innerGlow()->setFillType(psd_fill_solid_color); style->innerGlow()->setSource(psd_glow_center); test(style, "glow_inner_grad_center"); } #include void KisLayerStyleProjectionPlaneTest::testSatin() { KisPSDLayerStyleSP style(new KisPSDLayerStyle()); style->satin()->setSize(15); style->satin()->setOpacity(80); style->satin()->setAngle(180); style->satin()->setEffectEnabled(true); style->satin()->setColor(Qt::white); style->satin()->setBlendMode(COMPOSITE_LINEAR_DODGE); test(style, "satin"); } void KisLayerStyleProjectionPlaneTest::testColorOverlay() { KisPSDLayerStyleSP style(new KisPSDLayerStyle()); style->colorOverlay()->setOpacity(80); style->colorOverlay()->setEffectEnabled(true); style->colorOverlay()->setColor(Qt::white); style->colorOverlay()->setBlendMode(COMPOSITE_LINEAR_DODGE); test(style, "color_overlay"); } void KisLayerStyleProjectionPlaneTest::testGradientOverlay() { KisPSDLayerStyleSP style(new KisPSDLayerStyle()); style->gradientOverlay()->setAngle(90); style->gradientOverlay()->setOpacity(80); style->gradientOverlay()->setEffectEnabled(true); style->gradientOverlay()->setBlendMode(COMPOSITE_LINEAR_DODGE); style->gradientOverlay()->setAlignWithLayer(true); style->gradientOverlay()->setScale(100); style->gradientOverlay()->setStyle(psd_gradient_style_diamond); QLinearGradient testGradient; testGradient.setColorAt(0.0, Qt::white); testGradient.setColorAt(0.5, Qt::green); testGradient.setColorAt(1.0, Qt::black); testGradient.setSpread(QGradient::ReflectSpread); QSharedPointer gradient( KoStopGradient::fromQGradient(&testGradient)); style->gradientOverlay()->setGradient(gradient); test(style, "grad_overlay"); } void KisLayerStyleProjectionPlaneTest::testPatternOverlay() { KisPSDLayerStyleSP style(new KisPSDLayerStyle()); style->patternOverlay()->setOpacity(80); style->patternOverlay()->setEffectEnabled(true); style->patternOverlay()->setBlendMode(COMPOSITE_LINEAR_DODGE); style->patternOverlay()->setScale(100); style->patternOverlay()->setAlignWithLayer(false); QString fileName(TestUtil::fetchDataFileLazy("pattern.pat")); KoPattern pattern(fileName); QVERIFY(pattern.load()); style->patternOverlay()->setPattern(&pattern); test(style, "pat_overlay"); } void KisLayerStyleProjectionPlaneTest::testStroke() { KisPSDLayerStyleSP style(new KisPSDLayerStyle()); style->stroke()->setColor(Qt::blue); style->stroke()->setOpacity(80); style->stroke()->setEffectEnabled(true); style->stroke()->setBlendMode(COMPOSITE_OVER); style->stroke()->setSize(3); style->stroke()->setPosition(psd_stroke_center); test(style, "stroke_col_ctr"); style->stroke()->setPosition(psd_stroke_outside); test(style, "stroke_col_out"); style->stroke()->setPosition(psd_stroke_inside); test(style, "stroke_col_in"); QString fileName(TestUtil::fetchDataFileLazy("pattern.pat")); KoPattern pattern(fileName); QVERIFY(pattern.load()); style->stroke()->setPattern(&pattern); style->stroke()->setFillType(psd_fill_pattern); test(style, "stroke_pat"); QLinearGradient testGradient; testGradient.setColorAt(0.0, Qt::white); testGradient.setColorAt(0.5, Qt::green); testGradient.setColorAt(1.0, Qt::black); testGradient.setSpread(QGradient::ReflectSpread); QSharedPointer gradient( KoStopGradient::fromQGradient(&testGradient)); style->stroke()->setGradient(gradient); style->stroke()->setFillType(psd_fill_gradient); test(style, "stroke_grad"); } #include "layerstyles/gimp_bump_map.h" void KisLayerStyleProjectionPlaneTest::testBumpmap() { KisPixelSelectionSP device = new KisPixelSelection(); const int numCycles = 30; const int step = 5; QRect applyRect(200, 100, 100, 100); QRect fillRect(210, 110, 80, 80); quint8 selectedness = 256 - numCycles * step; for (int i = 0; i < numCycles; i++) { device->select(fillRect, selectedness); fillRect = kisGrowRect(fillRect, -1); selectedness += step; } KIS_DUMP_DEVICE_2(device, applyRect, "00_initial", "bumpmap"); bumpmap_vals_t bmvals; bmvals.azimuth = 240; bmvals.elevation = 30; bmvals.depth = 50; bmvals.ambient = 128; bmvals.compensate = false; bmvals.invert = false; bmvals.type = 0; bumpmap(device, applyRect, bmvals); KIS_DUMP_DEVICE_2(device, applyRect, "01_bumpmapped", "bumpmap"); } void KisLayerStyleProjectionPlaneTest::testBevel() { KisPSDLayerStyleSP style(new KisPSDLayerStyle()); style->bevelAndEmboss()->setEffectEnabled(true); style->bevelAndEmboss()->setAngle(135); style->bevelAndEmboss()->setAltitude(45); style->bevelAndEmboss()->setDepth(100); style->bevelAndEmboss()->setHighlightColor(Qt::white); style->bevelAndEmboss()->setHighlightBlendMode(COMPOSITE_OVER); style->bevelAndEmboss()->setHighlightOpacity(100); style->bevelAndEmboss()->setShadowColor(Qt::black); style->bevelAndEmboss()->setShadowBlendMode(COMPOSITE_OVER); style->bevelAndEmboss()->setShadowOpacity(100); QString fileName(TestUtil::fetchDataFileLazy("pattern.pat")); KoPattern pattern(fileName); QVERIFY(pattern.load()); style->bevelAndEmboss()->setTexturePattern(&pattern); style->bevelAndEmboss()->setTextureEnabled(true); style->bevelAndEmboss()->setTextureDepth(-10); style->bevelAndEmboss()->setTextureInvert(false); style->bevelAndEmboss()->setStyle(psd_bevel_outer_bevel); style->bevelAndEmboss()->setDirection(psd_direction_up); style->bevelAndEmboss()->setSoften(0); test(style, "bevel_outer_up"); style->bevelAndEmboss()->setTextureInvert(true); style->bevelAndEmboss()->setStyle(psd_bevel_outer_bevel); style->bevelAndEmboss()->setDirection(psd_direction_up); style->bevelAndEmboss()->setSoften(0); test(style, "bevel_outer_up_invert_texture"); style->bevelAndEmboss()->setTextureInvert(false); style->bevelAndEmboss()->setStyle(psd_bevel_outer_bevel); style->bevelAndEmboss()->setDirection(psd_direction_down); style->bevelAndEmboss()->setSoften(0); test(style, "bevel_outer_down"); style->bevelAndEmboss()->setStyle(psd_bevel_emboss); style->bevelAndEmboss()->setDirection(psd_direction_up); style->bevelAndEmboss()->setSoften(0); test(style, "bevel_emboss_up"); style->bevelAndEmboss()->setStyle(psd_bevel_pillow_emboss); style->bevelAndEmboss()->setDirection(psd_direction_up); style->bevelAndEmboss()->setSoften(0); test(style, "bevel_pillow_up"); style->bevelAndEmboss()->setStyle(psd_bevel_pillow_emboss); style->bevelAndEmboss()->setDirection(psd_direction_down); style->bevelAndEmboss()->setSoften(0); test(style, "bevel_pillow_down"); style->bevelAndEmboss()->setStyle(psd_bevel_pillow_emboss); style->bevelAndEmboss()->setDirection(psd_direction_up); style->bevelAndEmboss()->setSoften(3); test(style, "bevel_pillow_up_soft"); } + +#include "kis_ls_utils.h" + +void KisLayerStyleProjectionPlaneTest::testBlending() +{ + const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); + KisPaintDeviceSP layer = new KisPaintDevice(cs); + KisPaintDeviceSP overlay = new KisPaintDevice(cs); + KisPaintDeviceSP bg = new KisPaintDevice(cs); + KisPaintDeviceSP result = new KisPaintDevice(cs); + + const int width = 20; + KoColor color(Qt::transparent, cs); + + QVector layerColors; + QVector overlayColors; + QVector bgColors; + + layerColors << QColor(0, 255, 0); + layerColors << QColor(128, 255, 64); + + overlayColors << QColor(255, 0, 0); + overlayColors << QColor(255, 128, 64); + + bgColors << QColor(0, 0, 0, 0); + bgColors << QColor(0, 0, 0, 255); + bgColors << QColor(255, 255, 255, 255); + bgColors << QColor(64, 128, 255, 255); + + bgColors << QColor(0, 0, 0, 128); + bgColors << QColor(255, 255, 255, 128); + bgColors << QColor(64, 128, 255, 128); + + const int overlayOpacity = 255; + const int layerOpacity = 255; + int y = 1; + Q_FOREACH(const QColor &layerColor, layerColors) { + Q_FOREACH(const QColor &overlayColor, overlayColors) { + Q_FOREACH(const QColor &bgColor, bgColors) { + bg->setPixel(0, y, layerColor); + bg->setPixel(1, y, overlayColor); + bg->setPixel(2, y, bgColor); + bg->setPixel(3, y, QColor(layerOpacity, layerOpacity, layerOpacity, 255)); + bg->setPixel(4, y, QColor(overlayOpacity, overlayOpacity, overlayOpacity, 255)); + + for (int i = 5; i < width; i++) { + bg->setPixel(i, y, bgColor); + } + + for (int i = 0; i <= 10; i++) { + const quint8 alpha = i == 0 ? 71 : qRound(255 * qreal(i) / 10); + + { + QColor c(layerColor); + c.setAlpha(alpha); + layer->setPixel(7 + i, y, c); + } + + { + QColor c(overlayColor); + c.setAlpha(alpha); + overlay->setPixel(7 + i, y, c); + } + } + + y++; + } + } + } + + const QRect rc = bg->exactBounds() | layer->exactBounds(); + + + KIS_DUMP_DEVICE_2(layer, rc, "00_layer", "dd"); + KIS_DUMP_DEVICE_2(overlay, rc, "01_overlay", "dd"); + KIS_DUMP_DEVICE_2(bg, rc, "02_bg", "dd"); + + KisPaintDeviceSP originalBg = new KisPaintDevice(*bg); + + KisSelectionSP selection = new KisSelection(); + KisLsUtils::selectionFromAlphaChannel(layer, selection, rc); + + { + KisSequentialIterator it(layer, rc); + while (it.nextPixel()) { + cs->setOpacity(it.rawData(), quint8(255), 1); + } + } + + { + KisSequentialIterator it(overlay, rc); + while (it.nextPixel()) { + cs->setOpacity(it.rawData(), quint8(255), 1); + } + } + + KisPainter painter(bg); + + painter.setOpacity(layerOpacity); + painter.setCompositeOp(COMPOSITE_OVER); + + painter.bitBlt(rc.topLeft(), layer, rc); + + painter.setOpacity(overlayOpacity); + painter.setCompositeOp(COMPOSITE_ADD); + + painter.bitBlt(rc.topLeft(), overlay, rc); + + KIS_DUMP_DEVICE_2(bg, rc, "03_result", "dd"); + + KisPainter bgPainter(originalBg); + bgPainter.setCompositeOp(COMPOSITE_COPY); + bgPainter.setSelection(selection); + bgPainter.bitBlt(rc.topLeft(), bg, rc); + + KIS_DUMP_DEVICE_2(originalBg, rc, "04_knockout", "dd"); +} + QTEST_MAIN(KisLayerStyleProjectionPlaneTest) diff --git a/libs/image/tests/kis_layer_style_projection_plane_test.h b/libs/image/tests/kis_layer_style_projection_plane_test.h index ffbc4c5ad5..ab46f7cdd1 100644 --- a/libs/image/tests/kis_layer_style_projection_plane_test.h +++ b/libs/image/tests/kis_layer_style_projection_plane_test.h @@ -1,53 +1,55 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_LAYER_STYLE_PROJECTION_PLANE_TEST_H #define __KIS_LAYER_STYLE_PROJECTION_PLANE_TEST_H #include #include "kis_types.h" #include class KisLayerStyleProjectionPlaneTest : public QObject { Q_OBJECT private Q_SLOTS: void testShadow(); void testGlow(); void testGlowGradient(); void testGlowGradientJitter(); void testGlowInnerGradient(); void testSatin(); void testColorOverlay(); void testGradientOverlay(); void testPatternOverlay(); void testStroke(); void testBumpmap(); void testBevel(); + void testBlending(); + private: void test(KisPSDLayerStyleSP style, const QString testName); }; #endif /* __KIS_LAYER_STYLE_PROJECTION_PLANE_TEST_H */ diff --git a/libs/image/tests/kis_layer_styles_test.cpp b/libs/image/tests/kis_layer_styles_test.cpp index 318984e647..c875ceaac8 100644 --- a/libs/image/tests/kis_layer_styles_test.cpp +++ b/libs/image/tests/kis_layer_styles_test.cpp @@ -1,279 +1,284 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_layer_styles_test.h" #include #include "kis_transaction.h" #include "testutil.h" #include #include "layerstyles/kis_layer_style_filter.h" #include "layerstyles/kis_layer_style_filter_environment.h" #include "layerstyles/kis_ls_drop_shadow_filter.h" #include "kis_psd_layer_style.h" #include "layerstyles/kis_multiple_projection.h" +#include "layerstyles/KisLayerStyleKnockoutBlower.h" struct TestConfig { TestConfig() : distance(0), angle(0), spread(0), size(0), noise(0), knocks_out(false), keep_original(false) { } int distance; int angle; int spread; int size; int noise; int knocks_out; int opacity; bool keep_original; void writeProperties(KisPSDLayerStyleSP style) const { style->context()->keep_original = keep_original; style->dropShadow()->setEffectEnabled(true); style->dropShadow()->setDistance(distance); style->dropShadow()->setSpread(spread); style->dropShadow()->setSize(size); style->dropShadow()->setNoise(noise); style->dropShadow()->setKnocksOut(knocks_out); style->dropShadow()->setOpacity(opacity); } QString genTestname(const QString &prefix) const { return QString("%1_d_%2_an_%3_sz_%4_spr_%5_nz_%6_ko_%7_keep_%8") .arg(prefix) .arg(distance) .arg(angle) .arg(size) .arg(spread) .arg(noise) .arg(knocks_out) .arg(keep_original); } }; void testDropShadowImpl(const TestConfig &config, const QVector &applyRects, const QString &testName, bool useSeparateDevices) { Q_UNUSED(useSeparateDevices); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); QRect srcRect(50, 50, 100, 100); QRect dstRect(0, 0, 200, 200); KisPaintDeviceSP dev = new KisPaintDevice(cs); dev->fill(srcRect, KoColor(Qt::red, cs)); KisMultipleProjection projection; KisLsDropShadowFilter lsFilter; KisPSDLayerStyleSP style(new KisPSDLayerStyle()); config.writeProperties(style); TestUtil::MaskParent parent; KisLayerStyleFilterEnvironment env(parent.layer.data()); + KisLayerStyleKnockoutBlower blower; Q_FOREACH (const QRect &rc, applyRects) { - lsFilter.processDirectly(dev, &projection, rc, style, &env); + lsFilter.processDirectly(dev, &projection, &blower, rc, style, &env); } + // drop shadow doesn't use global knockout + QVERIFY(blower.isEmpty()); + KisPaintDeviceSP dst = new KisPaintDevice(cs); projection.apply(dst, dstRect, &env); QImage resultImage = dst->convertToQImage(0, dstRect); TestUtil::checkQImage(resultImage, "layer_styles_test", "common", config.genTestname(testName)); } void KisLayerStylesTest::testLayerStylesFull() { TestConfig c; c.distance = 20; c.angle = 135; c.spread = 50; c.size = 10; c.noise = 30; c.knocks_out = false; c.opacity = 50; c.keep_original = false; testDropShadowImpl(c, QVector() << QRect(0,0,200,200), "full", false); } void KisLayerStylesTest::testLayerStylesPartial() { QVector rects; for (int y = 0; y < 200; y += 50) { for (int x = 0; x < 200; x += 50) { rects << QRect(x, y, 50, 50); } } TestConfig c; c.distance = 20; c.angle = 135; c.spread = 50; c.size = 10; c.noise = 30; c.knocks_out = false; c.opacity = 50; c.keep_original = false; testDropShadowImpl(c, rects, "partial", true); } void KisLayerStylesTest::testLayerStylesPartialVary() { QVector rects; for (int y = 0; y < 200; y += 50) { for (int x = 0; x < 200; x += 50) { rects << QRect(x, y, 50, 50); } } TestConfig c; c.distance = 20; c.angle = 135; c.spread = 50; c.size = 10; c.noise = 30; c.knocks_out = false; c.opacity = 50; c.keep_original = true; testDropShadowImpl(c, rects, "partial", true); c.noise = 90; testDropShadowImpl(c, rects, "partial", true); c.noise = 0; testDropShadowImpl(c, rects, "partial", true); c.noise = 10; testDropShadowImpl(c, rects, "partial", true); c.angle = 90; testDropShadowImpl(c, rects, "partial", true); c.angle = 45; testDropShadowImpl(c, rects, "partial", true); c.knocks_out = true; testDropShadowImpl(c, rects, "partial", true); c.spread = 90; testDropShadowImpl(c, rects, "partial", true); } void testDropShadowNeedChangeRects(int distance, int noise, int size, int spread, const QRect &applyRect, const QRect &needRect, const QRect &changeRect) { TestConfig c; c.distance = distance; c.spread = spread; c.size = size; c.noise = noise; c.angle = 90; c.knocks_out = false; c.opacity = 50; KisLsDropShadowFilter lsFilter; KisPSDLayerStyleSP style(new KisPSDLayerStyle()); c.writeProperties(style); TestUtil::MaskParent parent; KisLayerStyleFilterEnvironment env(parent.layer.data()); QCOMPARE(lsFilter.neededRect(applyRect, style, &env), needRect); QCOMPARE(lsFilter.changedRect(applyRect, style, &env), changeRect); } void KisLayerStylesTest::testLayerStylesRects() { QRect applyRect; QRect needRect; QRect changeRect; applyRect = QRect(10,10,10,10); needRect = applyRect; changeRect = applyRect; testDropShadowNeedChangeRects(0, 0, 0, 0, applyRect, needRect, changeRect); applyRect = QRect(10,10,10,10); needRect = QRect(10,0,10,20); changeRect = QRect(10,10,10,20); testDropShadowNeedChangeRects(10, 0, 0, 0, applyRect, needRect, changeRect); applyRect = QRect(10,10,10,10); needRect = QRect(2,2,26,26); changeRect = QRect(2,2,26,26); testDropShadowNeedChangeRects(0, 30, 0, 0, applyRect, needRect, changeRect); applyRect = QRect(10,10,10,10); needRect = QRect(-2,-2,34,34); changeRect = QRect(-2,-2,34,34); testDropShadowNeedChangeRects(0, 0, 10, 0, applyRect, needRect, changeRect); applyRect = QRect(10,10,10,10); needRect = QRect(-2,-2,34,34); changeRect = QRect(-2,-2,34,34); testDropShadowNeedChangeRects(0, 0, 10, 50, applyRect, needRect, changeRect); applyRect = QRect(10,10,10,10); needRect = QRect(-2,-2,34,34); changeRect = QRect(-2,-2,34,34); testDropShadowNeedChangeRects(0, 0, 10, 75, applyRect, needRect, changeRect); } QTEST_MAIN(KisLayerStylesTest) diff --git a/libs/image/tests/kis_projection_leaf_test.cpp b/libs/image/tests/kis_projection_leaf_test.cpp index 37835b458d..c8dd8842c0 100644 --- a/libs/image/tests/kis_projection_leaf_test.cpp +++ b/libs/image/tests/kis_projection_leaf_test.cpp @@ -1,468 +1,468 @@ /* * 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_projection_leaf_test.h" #include #define USE_DOCUMENT 0 #include "qimage_based_test.h" #include "kis_projection_leaf.h" #include "kis_group_layer.h" struct TestImage : TestUtil::QImageBasedTest { TestImage() : TestUtil::QImageBasedTest("") { undoStore = new KisSurrogateUndoStore(); image = createImage(undoStore); addGlobalSelection(image); } KisSurrogateUndoStore *undoStore; KisImageSP image; KisNodeSP findBlur1() { return findNode(image->root(), "blur1"); } KisNodeSP findClone1() { return findNode(image->root(), "clone1"); } KisNodeSP findPaint1() { return findNode(image->root(), "paint1"); } }; bool safeCompare(KisProjectionLeafSP leaf, KisNodeSP node) { if (node && node->inherits("KisSelectionMask")) { return !leaf; } return (!leaf && !node) || (leaf->node() == node); } void checkNode(KisNodeSP node, const QString &prefix) { qDebug() << prefix << node->name(); if (!node->inherits("KisSelectionMask")) { safeCompare(node->projectionLeaf()->parent(), node->parent()); safeCompare(node->projectionLeaf()->prevSibling(), node->prevSibling()); safeCompare(node->projectionLeaf()->nextSibling(), node->nextSibling()); } safeCompare(node->projectionLeaf()->firstChild(), node->firstChild()); safeCompare(node->projectionLeaf()->lastChild(), node->lastChild()); QCOMPARE(node->projectionLeaf()->node(), node); KisNodeSP prevNode = node->lastChild(); while(prevNode) { checkNode(prevNode, QString("\"\"%1").arg(prefix)); prevNode = prevNode->prevSibling(); } } void printNodes(KisNodeSP node, const QString &prefix = "") { qDebug() << prefix << node->name(); KisNodeSP prevNode = node->lastChild(); while(prevNode) { printNodes(prevNode, QString("\"\"%1").arg(prefix)); prevNode = prevNode->prevSibling(); } } void printLeafsBackward(KisProjectionLeafSP leaf, QList &refNodes, const QString &prefix = "") { qDebug() << prefix << leaf->node()->name(); QCOMPARE(leaf->node()->name(), refNodes.takeFirst()); KisProjectionLeafSP prevLeaf = leaf->lastChild(); while(prevLeaf) { printLeafsBackward(prevLeaf, refNodes, QString("\"\"%1").arg(prefix)); prevLeaf = prevLeaf->prevSibling(); } if (prefix == "") { QVERIFY(refNodes.isEmpty()); } } void printLeafsForward(KisProjectionLeafSP leaf, QList &refNodes, const QString &prefix = "") { qDebug() << prefix << leaf->node()->name(); QCOMPARE(leaf->node()->name(), refNodes.takeFirst()); KisProjectionLeafSP prevLeaf = leaf->firstChild(); while(prevLeaf) { printLeafsForward(prevLeaf, refNodes, QString("\"\"%1").arg(prefix)); prevLeaf = prevLeaf->nextSibling(); } } void printParents(KisProjectionLeafSP leaf, QList &refNodes, const QString &prefix = "") { qDebug() << prefix << leaf->node()->name(); QCOMPARE(leaf->node()->name(), refNodes.takeFirst()); leaf = leaf->parent(); if (leaf) { printParents(leaf, refNodes, QString("\"\"%1").arg(prefix)); } QVERIFY(refNodes.isEmpty()); } void KisProjectionLeafTest::test() { TestImage t; checkNode(t.image->root(), ""); } void KisProjectionLeafTest::testPassThrough() { TestImage t; KisGroupLayerSP group1 = new KisGroupLayer(t.image, "group1", OPACITY_OPAQUE_U8); KisPaintLayerSP paint2 = new KisPaintLayer(t.image, "paint2", OPACITY_OPAQUE_U8); KisPaintLayerSP paint3 = new KisPaintLayer(t.image, "paint3", OPACITY_OPAQUE_U8); KisPaintLayerSP paint4 = new KisPaintLayer(t.image, "paint4", OPACITY_OPAQUE_U8); group1->setPassThroughMode(true); t.image->addNode(group1, t.image->root(), t.findBlur1()); t.image->addNode(paint2, group1); t.image->addNode(paint3, group1); t.image->addNode(paint4, group1); //checkNode(t.image->root(), ""); qDebug() << "== Nodes"; printNodes(t.image->root()); { qDebug() << "== Leafs backward"; QList refNodes; refNodes << "root" << "paint1" << "tmask1" << "group1" << "paint4" << "paint3" << "paint2" << "blur1" << "clone1"; printLeafsBackward(t.image->root()->projectionLeaf(), refNodes); } { qDebug() << "== Leafs forward"; QList refNodes; refNodes << "root" << "clone1" << "blur1" << "paint2" << "paint3" << "paint4" << "group1" << "paint1" << "tmask1"; printLeafsForward(t.image->root()->projectionLeaf(), refNodes); } { qDebug() << "== Parents for paint4"; QList refNodes; refNodes << "paint4" << "root"; printParents(paint4->projectionLeaf(), refNodes); } { qDebug() << "== Parents for paint3"; QList refNodes; refNodes << "paint3" << "root"; printParents(paint3->projectionLeaf(), refNodes); } { qDebug() << "== Parents for group1"; QList refNodes; refNodes << "group1" << "root"; printParents(group1->projectionLeaf(), refNodes); } } void KisProjectionLeafTest::testNestedPassThrough() { TestImage t; KisGroupLayerSP group1 = new KisGroupLayer(t.image, "group1", OPACITY_OPAQUE_U8); KisGroupLayerSP group2 = new KisGroupLayer(t.image, "group2", OPACITY_OPAQUE_U8); KisGroupLayerSP group3 = new KisGroupLayer(t.image, "group3", OPACITY_OPAQUE_U8); KisPaintLayerSP paint4 = new KisPaintLayer(t.image, "paint4", OPACITY_OPAQUE_U8); KisPaintLayerSP paint5 = new KisPaintLayer(t.image, "paint5", OPACITY_OPAQUE_U8); group1->setPassThroughMode(true); group2->setPassThroughMode(true); group3->setPassThroughMode(true); t.image->addNode(group1, t.image->root(), t.findBlur1()); t.image->addNode(group2, group1); t.image->addNode(paint4, group2); t.image->addNode(group3, t.image->root(), t.findBlur1()); t.image->addNode(paint5, group3); //checkNode(t.image->root(), ""); qDebug() << "== Nodes"; printNodes(t.image->root()); { qDebug() << "== Leafs backward"; QList refNodes; refNodes << "root" << "paint1" << "tmask1" << "group1" << "group2" <<"paint4" << "group3" << "paint5" << "blur1" << "clone1"; printLeafsBackward(t.image->root()->projectionLeaf(), refNodes); } { qDebug() << "== Leafs forward"; QList refNodes; refNodes << "root" << "clone1" << "blur1" << "paint5" << "group3" << "paint4" << "group2" << "group1" << "paint1" << "tmask1"; printLeafsForward(t.image->root()->projectionLeaf(), refNodes); } { qDebug() << "== Parents for paint4"; QList refNodes; refNodes << "paint4" << "root"; printParents(paint4->projectionLeaf(), refNodes); } { qDebug() << "== Parents for paint5"; QList refNodes; refNodes << "paint5" << "root"; printParents(paint5->projectionLeaf(), refNodes); } { qDebug() << "== Parents for group1"; QList refNodes; refNodes << "group1" << "root"; printParents(group1->projectionLeaf(), refNodes); } } #include "kis_selection_mask.h" #include "kis_transparency_mask.h" void KisProjectionLeafTest::testSkippedSelectionMasks() { TestImage t; KisGroupLayerSP group1 = new KisGroupLayer(t.image, "group1", OPACITY_OPAQUE_U8); KisPaintLayerSP paint2 = new KisPaintLayer(t.image, "paint2", OPACITY_OPAQUE_U8); KisSelectionMaskSP selection3 = new KisSelectionMask(t.image); selection3->setName("selection3"); KisPaintLayerSP paint4 = new KisPaintLayer(t.image, "paint4", OPACITY_OPAQUE_U8); KisTransparencyMaskSP tmask5 = new KisTransparencyMask(); tmask5->setName("tmask5"); KisSelectionMaskSP selection6 = new KisSelectionMask(t.image); selection6->setName("selection6"); KisTransparencyMaskSP tmask7 = new KisTransparencyMask(); tmask7->setName("tmask7"); KisPaintLayerSP paint8 = new KisPaintLayer(t.image, "paint8", OPACITY_OPAQUE_U8); KisSelectionMaskSP selection9 = new KisSelectionMask(t.image); selection9->setName("selection9"); KisTransparencyMaskSP tmask10 = new KisTransparencyMask(); tmask10->setName("tmask10"); t.image->addNode(group1, t.image->root(), t.findBlur1()); t.image->addNode(paint2, group1); t.image->addNode(selection3, paint2); t.image->addNode(paint4, group1); t.image->addNode(tmask5, paint4); t.image->addNode(selection6, paint4); t.image->addNode(tmask7, paint4); t.image->addNode(paint8, group1); t.image->addNode(selection9, paint8); t.image->addNode(tmask10, paint8); //checkNode(t.image->root(), ""); qDebug() << "== Nodes"; printNodes(t.image->root()); { qDebug() << "== Leafs backward"; QList refNodes; refNodes << "root" << "paint1" << "tmask1" << "group1" << "paint8" << "tmask10" << "paint4" << "tmask7" << "tmask5" << "paint2" << "blur1" << "clone1"; printLeafsBackward(t.image->root()->projectionLeaf(), refNodes); } { qDebug() << "== Leafs forward"; QList refNodes; refNodes << "root" << "clone1" << "blur1" << "group1" << "paint2" << "paint4" << "tmask5" << "tmask7" << "paint8" << "tmask10" << "paint1" << "tmask1"; printLeafsForward(t.image->root()->projectionLeaf(), refNodes); } { qDebug() << "== Parents for tmask5"; QList refNodes; refNodes << "tmask5" << "paint4" << "group1" << "root"; printParents(tmask5->projectionLeaf(), refNodes); } { qDebug() << "== Parents for selection6"; QList refNodes; refNodes << "selection6"; printParents(selection6->projectionLeaf(), refNodes); } /** * Selection masks are just excluded from the entire rendering hierarchy */ QVERIFY(!selection6->projectionLeaf()->nextSibling()); QVERIFY(!selection6->projectionLeaf()->prevSibling()); } void KisProjectionLeafTest::testSelectionMaskOverlay() { TestImage t; KisGroupLayerSP group1 = new KisGroupLayer(t.image, "group1", OPACITY_OPAQUE_U8); KisPaintLayerSP paint2 = new KisPaintLayer(t.image, "paint2", OPACITY_OPAQUE_U8); KisSelectionMaskSP selection3 = new KisSelectionMask(t.image); selection3->setName("selection3"); - selection3->setSelection(new KisSelection(new KisSelectionDefaultBounds(paint2->paintDevice(), t.image))); + selection3->setSelection(new KisSelection(new KisSelectionDefaultBounds(paint2->paintDevice()))); t.image->addNode(group1, t.image->root(), t.findBlur1()); t.image->addNode(paint2, group1); t.image->addNode(selection3, paint2); t.image->setOverlaySelectionMask(selection3); t.image->waitForDone(); qDebug() << "== Nodes"; printNodes(t.image->root()); { qDebug() << "== Leafs backward"; QList refNodes; refNodes << "root" << "selection3" << "paint1" << "tmask1" << "group1" << "paint2" << "blur1" << "clone1"; printLeafsBackward(t.image->root()->projectionLeaf(), refNodes); } { qDebug() << "== Leafs forward"; QList refNodes; refNodes << "root" << "clone1" << "blur1" << "group1" << "paint2" << "paint1" << "tmask1" << "selection3"; printLeafsForward(t.image->root()->projectionLeaf(), refNodes); } { qDebug() << "== Parents for selection3"; QList refNodes; refNodes << "selection3" << "root"; printParents(selection3->projectionLeaf(), refNodes); } } QTEST_MAIN(KisProjectionLeafTest) diff --git a/libs/image/tests/kis_selection_test.cpp b/libs/image/tests/kis_selection_test.cpp index e4f98411b0..1fc12397a7 100644 --- a/libs/image/tests/kis_selection_test.cpp +++ b/libs/image/tests/kis_selection_test.cpp @@ -1,340 +1,340 @@ /* * Copyright (c) 2007 Sven Langkamp * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_selection_test.h" #include #include #include #include #include #include #include "kis_datamanager.h" #include "kis_pixel_selection.h" #include "kis_selection.h" #include "kis_fill_painter.h" #include "kis_mask.h" #include "kis_image.h" #include "kis_transparency_mask.h" #include "testutil.h" #include void KisSelectionTest::testGrayColorspaceConversion() { const KoColorSpace *csA = KoColorSpaceRegistry::instance()-> colorSpace(GrayAColorModelID.id(), Integer8BitsColorDepthID.id(), QString()); const KoColorSpace *csNoA = KoColorSpaceRegistry::instance()->alpha8(); QVERIFY(csA); QVERIFY(csNoA); QCOMPARE(csA->pixelSize(), 2U); QCOMPARE(csNoA->pixelSize(), 1U); quint8 color1[1] = {128}; quint8 color2[2] = {64,32}; csA->convertPixelsTo(color2, color1, csNoA, 1, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); QCOMPARE((int)color1[0], 8); csNoA->convertPixelsTo(color1, color2, csA, 1, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); QCOMPARE((int)color2[0], 8); QCOMPARE((int)color2[1], 255); } void KisSelectionTest::testGrayColorspaceOverComposition() { const KoColorSpace *csA = KoColorSpaceRegistry::instance()-> colorSpace(GrayAColorModelID.id(), Integer8BitsColorDepthID.id(), QString()); const KoColorSpace *csNoA = KoColorSpaceRegistry::instance()->alpha8(); QVERIFY(csA); QVERIFY(csNoA); QCOMPARE(csA->pixelSize(), 2U); QCOMPARE(csNoA->pixelSize(), 1U); quint8 color0[2] = {32,255}; quint8 color1[2] = {128,64}; quint8 color3[1] = {32}; KoCompositeOp::ParameterInfo params; params.dstRowStart = color0; params.dstRowStride = 0; params.srcRowStart = color1; params.srcRowStride = 0; params.maskRowStart = 0; params.maskRowStride = 0; params.rows = 1; params.cols = 1; params.opacity = 1.0; params.flow = 1.0; csA->bitBlt(csA, params, csA->compositeOp(COMPOSITE_OVER), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); QCOMPARE((int)color0[0], 56); QCOMPARE((int)color0[1], 255); params.dstRowStart = color3; csNoA->bitBlt(csA, params, csNoA->compositeOp(COMPOSITE_OVER), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); QCOMPARE((int)color3[0], 56); } void KisSelectionTest::testSelectionComponents() { KisSelectionSP selection = new KisSelection(); QCOMPARE(selection->hasPixelSelection(), false); QCOMPARE(selection->hasShapeSelection(), false); QCOMPARE(selection->shapeSelection(), (void*)0); selection->pixelSelection()->select(QRect(10,10,10,10)); QCOMPARE(selection->hasPixelSelection(), true); QCOMPARE(selection->selectedExactRect(), QRect(10,10,10,10)); } void KisSelectionTest::testSelectionActions() { KisSelectionSP selection = new KisSelection(); QVERIFY(selection->hasPixelSelection() == false); QVERIFY(selection->hasShapeSelection() == false); KisPixelSelectionSP pixelSelection = selection->pixelSelection(); pixelSelection->select(QRect(0, 0, 20, 20)); KisPixelSelectionSP tmpSel = new KisPixelSelection(); tmpSel->select(QRect(10, 0, 20, 20)); pixelSelection->applySelection(tmpSel, SELECTION_ADD); QCOMPARE(pixelSelection->selectedExactRect(), QRect(0, 0, 30, 20)); QCOMPARE(selection->selectedExactRect(), QRect(0, 0, 30, 20)); pixelSelection->clear(); pixelSelection->select(QRect(0, 0, 20, 20)); pixelSelection->applySelection(tmpSel, SELECTION_SUBTRACT); QCOMPARE(pixelSelection->selectedExactRect(), QRect(0, 0, 10, 20)); QCOMPARE(selection->selectedExactRect(), QRect(0, 0, 10, 20)); pixelSelection->clear(); pixelSelection->select(QRect(0, 0, 20, 20)); pixelSelection->applySelection(tmpSel, SELECTION_INTERSECT); QCOMPARE(pixelSelection->selectedExactRect(), QRect(10, 0, 10, 20)); QCOMPARE(selection->selectedExactRect(), QRect(10, 0, 10, 20)); } void KisSelectionTest::testInvertSelection() { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, 1024, 1024, cs, "stest"); KisSelectionSP selection = new KisSelection(new KisDefaultBounds(image)); KisPixelSelectionSP pixelSelection = selection->pixelSelection(); pixelSelection->select(QRect(20, 20, 20, 20)); QCOMPARE(TestUtil::alphaDevicePixel(pixelSelection, 30, 30), MAX_SELECTED); QCOMPARE(TestUtil::alphaDevicePixel(pixelSelection, 0, 0), MIN_SELECTED); QCOMPARE(TestUtil::alphaDevicePixel(pixelSelection, 512, 512), MIN_SELECTED); QCOMPARE(pixelSelection->selectedExactRect(), QRect(20, 20, 20, 20)); pixelSelection->invert(); QCOMPARE(TestUtil::alphaDevicePixel(pixelSelection, 100, 100), MAX_SELECTED); QCOMPARE(TestUtil::alphaDevicePixel(pixelSelection, 22, 22), MIN_SELECTED); QCOMPARE(TestUtil::alphaDevicePixel(pixelSelection, 0, 0), MAX_SELECTED); QCOMPARE(TestUtil::alphaDevicePixel(pixelSelection, 512, 512), MAX_SELECTED); QCOMPARE(pixelSelection->selectedExactRect(), QRect(0,0,1024,1024)); QCOMPARE(pixelSelection->selectedRect(), QRect(0,0,1024,1024)); selection->updateProjection(); QCOMPARE(selection->selectedExactRect(), QRect(0,0,1024,1024)); QCOMPARE(selection->selectedRect(), QRect(0,0,1024,1024)); QCOMPARE(TestUtil::alphaDevicePixel(selection->projection(), 100, 100), MAX_SELECTED); QCOMPARE(TestUtil::alphaDevicePixel(selection->projection(), 22, 22), MIN_SELECTED); QCOMPARE(TestUtil::alphaDevicePixel(selection->projection(), 10, 10), MAX_SELECTED); QCOMPARE(TestUtil::alphaDevicePixel(selection->projection(), 0, 0), MAX_SELECTED); QCOMPARE(TestUtil::alphaDevicePixel(selection->projection(), 512, 512), MAX_SELECTED); } void KisSelectionTest::testInvertSelectionSemi() { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, 1024, 1024, cs, "stest"); KisSelectionSP selection = new KisSelection(new KisDefaultBounds(image)); KisPixelSelectionSP pixelSelection = selection->pixelSelection(); quint8 selectedness = 42; pixelSelection->select(QRect(20, 20, 20, 20), selectedness); QCOMPARE(TestUtil::alphaDevicePixel(pixelSelection, 30, 30), selectedness); QCOMPARE(TestUtil::alphaDevicePixel(pixelSelection, 0, 0), MIN_SELECTED); QCOMPARE(pixelSelection->selectedExactRect(), QRect(20, 20, 20, 20)); pixelSelection->invert(); quint8 invertedSelectedness = MAX_SELECTED - selectedness; QCOMPARE(TestUtil::alphaDevicePixel(pixelSelection, 30, 30), invertedSelectedness); QCOMPARE(TestUtil::alphaDevicePixel(pixelSelection, 0, 0), MAX_SELECTED); QCOMPARE(pixelSelection->selectedExactRect(), QRect(0,0,1024,1024)); QCOMPARE(pixelSelection->selectedRect(), QRect(0,0,1024,1024)); selection->updateProjection(); QCOMPARE(selection->selectedExactRect(), QRect(0,0,1024,1024)); QCOMPARE(selection->selectedRect(), QRect(0,0,1024,1024)); QCOMPARE(TestUtil::alphaDevicePixel(selection->projection(), 30, 30), invertedSelectedness); QCOMPARE(TestUtil::alphaDevicePixel(selection->projection(), 0, 0), MAX_SELECTED); } void KisSelectionTest::testCopy() { KisSelectionSP sel = new KisSelection(); sel->pixelSelection()->select(QRect(10, 10, 200, 200), 128); sel->updateProjection(); KisSelectionSP sel2 = new KisSelection(*sel.data()); QCOMPARE(sel2->selectedExactRect(), sel->selectedExactRect()); QPoint errpoint; if (!TestUtil::comparePaintDevices(errpoint, sel->projection(), sel2->projection())) { sel2->projection()->convertToQImage(0, 0, 0, 200, 200).save("merge_visitor6.png"); QFAIL(QString("Failed to copy selection, first different pixel: %1,%2 ") .arg(errpoint.x()) .arg(errpoint.y()) .toLatin1()); } } void KisSelectionTest::testSelectionExactBounds() { QRect referenceImageRect(0,0,1000,1000); QRect referenceDeviceRect(100,100,1040,1040); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, referenceImageRect.width(), referenceImageRect.height(), cs, "stest"); KisPaintDeviceSP device = new KisPaintDevice(cs); device->fill(referenceDeviceRect, KoColor(Qt::white, cs)); QCOMPARE(device->exactBounds(), referenceDeviceRect); - KisSelectionSP selection = new KisSelection(new KisSelectionDefaultBounds(device, image)); + KisSelectionSP selection = new KisSelection(new KisSelectionDefaultBounds(device)); quint8 defaultPixel = MAX_SELECTED; selection->pixelSelection()->setDefaultPixel(KoColor(&defaultPixel, selection->pixelSelection()->colorSpace())); // the selection uses device's extent only for performance reasons // \see bug 320213 QCOMPARE(selection->selectedExactRect(), device->extent() | referenceImageRect); } void KisSelectionTest::testSetParentNodeAfterCreation() { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, 100, 100, cs, "stest"); KisSelectionSP selection = new KisSelection(); KisPixelSelectionSP pixelSelection = selection->pixelSelection(); QCOMPARE(selection->parentNode(), KisNodeWSP(0)); QCOMPARE(selection->pixelSelection()->parentNode(), KisNodeWSP(0)); selection->setParentNode(image->root()); QCOMPARE(selection->parentNode(), KisNodeWSP(image->root())); QCOMPARE(selection->pixelSelection()->parentNode(), KisNodeWSP(image->root())); } void KisSelectionTest::testSetParentNodeBeforeCreation() { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, 100, 100, cs, "stest"); KisSelectionSP selection = new KisSelection(); selection->setParentNode(image->root()); KisPixelSelectionSP pixelSelection = selection->pixelSelection(); QCOMPARE(selection->parentNode(), KisNodeWSP(image->root())); QCOMPARE(selection->pixelSelection()->parentNode(), KisNodeWSP(image->root())); } void KisSelectionTest::testOutlineGeneration() { KisSelectionSP sel = new KisSelection(); sel->pixelSelection()->select(QRect(428,436, 430,211), 128); QVERIFY(sel->outlineCacheValid()); QPainterPath originalOutline = sel->outlineCache(); sel->pixelSelection()->invalidateOutlineCache(); sel->recalculateOutlineCache(); QPainterPath calculatedOutline = sel->outlineCache(); QPainterPath closedSubPath = calculatedOutline; closedSubPath.closeSubpath(); /** * Our outline generation code has a small problem: it can * generate a polygon, which isn't closed (it'll repeat the first * point instead). There is a special workaround for it in * KisPixelSelection::recalculateOutlineCache(), which explicitly * closes the path, so here we just check it. */ bool isClosed = closedSubPath == calculatedOutline; QVERIFY(isClosed); } KISTEST_MAIN(KisSelectionTest) diff --git a/libs/image/tiles3/kis_hline_iterator.cpp b/libs/image/tiles3/kis_hline_iterator.cpp index fe5558845e..66b8487884 100644 --- a/libs/image/tiles3/kis_hline_iterator.cpp +++ b/libs/image/tiles3/kis_hline_iterator.cpp @@ -1,231 +1,231 @@ /* * Copyright (c) 2010 Lukáš Tvrdý * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_hline_iterator.h" KisHLineIterator2::KisHLineIterator2(KisDataManager *dataManager, qint32 x, qint32 y, qint32 w, qint32 offsetX, qint32 offsetY, bool writable, KisIteratorCompleteListener *competionListener) : KisBaseIterator(dataManager, writable, competionListener), m_offsetX(offsetX), m_offsetY(offsetY) { x -= m_offsetX; y -= m_offsetY; Q_ASSERT(dataManager); if (w < 1) w = 1; // To make sure there's always at least one pixel read. m_x = x; m_y = y; m_left = x; m_right = x + w - 1; m_top = y; m_havePixels = (w == 0) ? false : true; if (m_left > m_right) { m_havePixels = false; return; } m_leftCol = xToCol(m_left); m_rightCol = xToCol(m_right); m_row = yToRow(m_y); m_yInTile = calcYInTile(m_y, m_row); m_leftInLeftmostTile = m_left - m_leftCol * KisTileData::WIDTH; m_tilesCacheSize = m_rightCol - m_leftCol + 1; m_tilesCache.resize(m_tilesCacheSize); m_tileWidth = m_pixelSize * KisTileData::HEIGHT; - // let's prealocate first row + // let's preallocate first row for (quint32 i = 0; i < m_tilesCacheSize; i++){ fetchTileDataForCache(m_tilesCache[i], m_leftCol + i, m_row); } m_index = 0; switchToTile(m_leftInLeftmostTile); } void KisHLineIterator2::resetPixelPos() { m_x = m_left; m_index = 0; switchToTile(m_leftInLeftmostTile); m_havePixels = true; } void KisHLineIterator2::resetRowPos() { m_y = m_top; m_row = yToRow(m_y); m_yInTile = calcYInTile(m_y, m_row); preallocateTiles(); resetPixelPos(); } bool KisHLineIterator2::nextPixel() { // We won't increment m_x here as integer can overflow here if (m_x >= m_right) { //return !m_isDoneFlag; return m_havePixels = false; } else { ++m_x; m_data += m_pixelSize; if (m_x <= m_rightmostInTile) m_oldData += m_pixelSize; else { // Switching to the beginning of the next tile ++m_index; switchToTile(0); } } return m_havePixels; } void KisHLineIterator2::nextRow() { m_x = m_left; ++m_y; if (++m_yInTile < KisTileData::HEIGHT) { /* do nothing, usual case */ } else { ++m_row; m_yInTile = 0; preallocateTiles(); } m_index = 0; switchToTile(m_leftInLeftmostTile); m_havePixels = true; } qint32 KisHLineIterator2::nConseqPixels() const { return qMin(m_rightmostInTile, m_right) - m_x + 1; } bool KisHLineIterator2::nextPixels(qint32 n) { Q_ASSERT_X(!(m_x > 0 && (m_x + n) < 0), "hlineIt+=", "Integer overflow"); qint32 previousCol = xToCol(m_x); // We won't increment m_x here first as integer can overflow if (m_x >= m_right || (m_x += n) > m_right) { m_havePixels = false; } else { qint32 col = xToCol(m_x); // if we are in the same column in tiles if (col == previousCol) { m_data += n * m_pixelSize; } else { qint32 xInTile = calcXInTile(m_x, col); m_index += col - previousCol; switchToTile(xInTile); } } return m_havePixels; } KisHLineIterator2::~KisHLineIterator2() { for (uint i = 0; i < m_tilesCacheSize; i++) { unlockTile(m_tilesCache[i].tile); unlockOldTile(m_tilesCache[i].oldtile); } } quint8* KisHLineIterator2::rawData() { return m_data; } const quint8* KisHLineIterator2::oldRawData() const { return m_oldData; } const quint8* KisHLineIterator2::rawDataConst() const { return m_data; } void KisHLineIterator2::switchToTile(qint32 xInTile) { // The caller must ensure that we are not out of bounds Q_ASSERT(m_index < m_tilesCacheSize); m_data = m_tilesCache[m_index].data; m_oldData = m_tilesCache[m_index].oldData; int offset_row = m_pixelSize * (m_yInTile * KisTileData::WIDTH); m_data += offset_row; m_rightmostInTile = (m_leftCol + m_index + 1) * KisTileData::WIDTH - 1; int offset_col = m_pixelSize * xInTile; m_data += offset_col; m_oldData += offset_row + offset_col; } void KisHLineIterator2::fetchTileDataForCache(KisTileInfo& kti, qint32 col, qint32 row) { m_dataManager->getTilesPair(col, row, m_writable, &kti.tile, &kti.oldtile); lockTile(kti.tile); kti.data = kti.tile->data(); lockOldTile(kti.oldtile); kti.oldData = kti.oldtile->data(); } void KisHLineIterator2::preallocateTiles() { for (quint32 i = 0; i < m_tilesCacheSize; ++i){ unlockTile(m_tilesCache[i].tile); unlockOldTile(m_tilesCache[i].oldtile); fetchTileDataForCache(m_tilesCache[i], m_leftCol + i, m_row); } } qint32 KisHLineIterator2::x() const { return m_x + m_offsetX; } qint32 KisHLineIterator2::y() const { return m_y + m_offsetY; } diff --git a/libs/image/tiles3/kis_memento_manager.h b/libs/image/tiles3/kis_memento_manager.h index 0b20aaa852..cb9761acf1 100644 --- a/libs/image/tiles3/kis_memento_manager.h +++ b/libs/image/tiles3/kis_memento_manager.h @@ -1,174 +1,174 @@ /* * Copyright (c) 2009 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_MEMENTO_MANAGER_ #define KIS_MEMENTO_MANAGER_ #include #include "kis_memento_item.h" #include "config-hash-table-implementaion.h" typedef QList KisMementoItemList; typedef QListIterator KisMementoItemListIterator; class KisMemento; struct KisHistoryItem { KisMemento* memento; KisMementoItemList itemList; }; typedef QList KisHistoryList; class KisMemento; typedef KisSharedPtr KisMementoSP; #ifdef USE_LOCK_FREE_HASH_TABLE #include "kis_tile_hash_table2.h" typedef KisTileHashTableTraits2 KisMementoItemHashTable; typedef KisTileHashTableIteratorTraits2 KisMementoItemHashTableIterator; typedef KisTileHashTableIteratorTraits2 KisMementoItemHashTableIteratorConst; #else #include "kis_tile_hash_table.h" typedef KisTileHashTableTraits KisMementoItemHashTable; typedef KisTileHashTableIteratorTraits KisMementoItemHashTableIterator; typedef KisTileHashTableIteratorTraits KisMementoItemHashTableIteratorConst; #endif // USE_LOCK_FREE_HASH_TABLE class KRITAIMAGE_EXPORT KisMementoManager { public: KisMementoManager(); KisMementoManager(const KisMementoManager& rhs); ~KisMementoManager(); /** * Most tricky part. This function is called by a tile, when it gets new * tile-data through COW. The Memento Manager wraps this tile-data into * KisMementoItem class and waits until commit() order given. By this * time KisMementoItem doesn't take part in COW mechanism. It only holds * tileData->m_refCount counter to ensure tile isn't deleted from memory. * When commit() comes, KisMementoItem grabs tileData->m_usersCount and * since that moment it is a rightful co-owner of the tileData and COW * participant. It means that tileData won't be ever changed since then. * Every write request to the original tile will lead to duplicating * tileData and registering it here again... */ void registerTileChange(KisTile *tile); /** * Called when a tile deleted. Creates empty KisMementoItem showing that * there was a tile one day */ void registerTileDeleted(KisTile *tile); /** * Commits changes, made in INDEX: appends m_index into m_revisions list * and owes all modified tileDatas. */ void commit(); /** * Undo and Redo stuff respectively. * * When calling them, INDEX list should be empty, so to say, "working * copy should be clean". */ void rollback(KisTileHashTable *ht); void rollforward(KisTileHashTable *ht); /** * Get old tile, whose memento is in the HEAD revision. * \p existingTile returns if the tile is actually an existing * non-default tile or it was created on the fly * from the default tile data */ KisTileSP getCommitedTile(qint32 col, qint32 row, bool &existingTile); KisMementoSP getMemento(); bool hasCurrentMemento() { return m_currentMemento; } KisMementoSP currentMemento(); void setDefaultTileData(KisTileData *defaultTileData); void debugPrintInfo(); /** - * Removes all the history that preceds the revision + * Removes all the history that precedes the revision * pointed by oldestMemento. That is after calling to * purgeHistory(someMemento) you won't be able to do * rollback(someMemento) anymore. */ void purgeHistory(KisMementoSP oldestMemento); protected: qint32 findRevisionByMemento(KisMementoSP memento) const; void resetRevisionHistory(KisMementoItemList list); protected: /** * INDEX of tiles to be committed with next commit() * We use a hash table to be able to check that * we have the only memento item for a tile * per commit efficiently */ KisMementoItemHashTable m_index; /** * Main list that stores every commit ever done */ KisHistoryList m_revisions; /** * List of revisions temporarily undone while rollback() */ KisHistoryList m_cancelledRevisions; /** * A hash table, that stores the most recently updated * versions of tiles. Say, HEAD revision :) */ KisMementoItemHashTable m_headsHashTable; /** * Stores extent of current INDEX. * It is the "name" of current named transaction */ KisMementoSP m_currentMemento; /** * The flag that blocks registration of changes on tiles. * This is a temporary state of the memento manager, that * is used for traveling in history * * \see rollback() * \see rollforward() */ bool m_registrationBlocked; }; #endif /* KIS_MEMENTO_MANAGER_ */ diff --git a/libs/image/tiles3/kis_tile.h b/libs/image/tiles3/kis_tile.h index 7b40b4e6d5..73d197bdc0 100644 --- a/libs/image/tiles3/kis_tile.h +++ b/libs/image/tiles3/kis_tile.h @@ -1,193 +1,193 @@ /* * Copyright (c) 2004 C. Boemann * (c) 2009 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_TILE_H_ #define KIS_TILE_H_ #include #include #include #include #include #include #include #include "kis_tile_data.h" #include "kis_tile_data_store.h" //#define DEAD_TILES_SANITY_CHECK class KisTile; typedef KisSharedPtr KisTileSP; class KisMementoManager; /** * Provides abstraction to a tile. * + A tile contains a part of a PaintDevice, - * but only the individual pixels are accesable + * but only the individual pixels are accessible * and that only via iterators. * + Actual tile data is stored in KisTileData that can be * shared between many tiles */ class KRITAIMAGE_EXPORT KisTile : public KisShared { public: KisTile(qint32 col, qint32 row, KisTileData *defaultTileData, KisMementoManager* mm); KisTile(const KisTile& rhs, qint32 col, qint32 row, KisMementoManager* mm); KisTile(const KisTile& rhs, KisMementoManager* mm); KisTile(const KisTile& rhs); ~KisTile(); /** * This method is called by the hash table when the tile is * disconnected from it. It means that from now on the tile is not * associated with any particular datamanager. All the users of * the tile (via shared pointers) may silently finish they work on * this tile and leave it. No result will be saved. Used for * threading purposes */ void notifyDetachedFromDataManager(); /** * Sometimes the tile gets replaced with another tile. In this case * we shouldn't notify memento manager that the tile has died. Just * forget the link to the manager and bury it in peace. */ void notifyDeadWithoutDetaching(); /** * Called by the hash table to notify that the tile has been attached * to the data manager. */ void notifyAttachedToDataManager(KisMementoManager *mm); public: void debugPrintInfo(); void debugDumpTile(); void lockForRead() const; void lockForWrite(); void unlockForWrite(); void unlockForRead() const; /* this allows us work directly on tile's data */ inline quint8 *data() const { return m_tileData->data(); } inline void setData(const quint8 *data) { m_tileData->setData(data); } inline qint32 row() const { return m_row; } inline qint32 col() const { return m_col; } inline QRect extent() const { return m_extent; //QRect(m_col * KisTileData::WIDTH, m_row * KisTileData::HEIGHT, // KisTileData::WIDTH, KisTileData::HEIGHT); } inline KisTileSP next() const { return m_nextTile; } void setNext(KisTileSP next) { m_nextTile = next; } inline qint32 pixelSize() const { /* don't lock here as pixelSize is constant */ return m_tileData->pixelSize(); } inline KisTileData* tileData() const { return m_tileData; } private: void init(qint32 col, qint32 row, KisTileData *defaultTileData, KisMementoManager* mm); inline void blockSwapping() const; inline void unblockSwapping() const; inline void safeReleaseOldTileData(KisTileData *td); private: KisTileData *m_tileData; mutable QStack m_oldTileData; mutable volatile int m_lockCounter; qint32 m_col; qint32 m_row; /** * Added for faster retrieving by processors */ QRect m_extent; /** * For KisTiledDataManager's hash table */ KisTileSP m_nextTile; QAtomicPointer m_mementoManager; /** * This is a special mutex for guarding copy-on-write * operations. We do not use lockless way here as it'll * create too much overhead for the most common operations * like "read the pointer of m_tileData". */ QMutex m_COWMutex; /** * This lock is used to ensure no one will read the tile data * before it has been loaded from to the memory. */ mutable QMutex m_swapBarrierLock; #ifdef DEAD_TILES_SANITY_CHECK QAtomicInt m_sanityHasBeenDetached; QAtomicInt m_sanityIsDead; QAtomicInt m_sanityMMHasBeenInitializedManually; QAtomicInt m_sanityNumCOWHappened; QAtomicInt m_sanityLockedForWrite; mutable QAtomicInt m_sanityLockedForRead; void sanityCheckIsNotDestroyedYet(); void sanityCheckIsNotLockedForWrite(); #endif }; #endif // KIS_TILE_H_ diff --git a/libs/image/tiles3/kis_tile_data_interface.h b/libs/image/tiles3/kis_tile_data_interface.h index 2874a59b56..eb9cf13719 100644 --- a/libs/image/tiles3/kis_tile_data_interface.h +++ b/libs/image/tiles3/kis_tile_data_interface.h @@ -1,327 +1,327 @@ /* * Copyright (c) 2009 Dmitry Kazakov * Copyright (c) 2018 Andrey Kamakin * * 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_TILE_DATA_INTERFACE_H_ #define KIS_TILE_DATA_INTERFACE_H_ #include #include #include "kis_lockless_stack.h" #include "swap/kis_chunk_allocator.h" class KisTileData; class KisTileDataStore; /** * WARNING: Those definitions for internal use only! * Please use KisTileData::WIDTH/HEIGHT instead */ #define __TILE_DATA_WIDTH 64 #define __TILE_DATA_HEIGHT 64 typedef KisLocklessStack KisTileDataCache; typedef QLinkedList KisTileDataList; typedef KisTileDataList::iterator KisTileDataListIterator; typedef KisTileDataList::const_iterator KisTileDataListConstIterator; class SimpleCache { public: SimpleCache() = default; ~SimpleCache(); bool push(int pixelSize, quint8 *&ptr) { QReadLocker l(&m_cacheLock); switch (pixelSize) { case 4: m_4Pool.push(ptr); break; case 8: m_8Pool.push(ptr); break; case 16: m_16Pool.push(ptr); break; default: return false; } return true; } bool pop(int pixelSize, quint8 *&ptr) { QReadLocker l(&m_cacheLock); switch (pixelSize) { case 4: return m_4Pool.pop(ptr); case 8: return m_8Pool.pop(ptr); case 16: return m_16Pool.pop(ptr); default: return false; } } void clear(); private: QReadWriteLock m_cacheLock; KisLocklessStack m_4Pool; KisLocklessStack m_8Pool; KisLocklessStack m_16Pool; }; /** * Stores actual tile's data */ class KRITAIMAGE_EXPORT KisTileData { public: KisTileData(qint32 pixelSize, const quint8 *defPixel, KisTileDataStore *store, bool checkFreeMemory = true); private: KisTileData(const KisTileData& rhs, bool checkFreeMemory = true); public: ~KisTileData(); enum EnumTileDataState { NORMAL = 0, COMPRESSED, SWAPPED }; /** * Information about data stored */ inline quint8* data() const; inline void setData(const quint8 *data); inline quint32 pixelSize() const; /** * Increments usersCount of a TD and refs shared pointer counter * Used by KisTile for COW */ inline bool acquire(); /** * Decrements usersCount of a TD and derefs shared pointer counter * Used by KisTile for COW */ inline bool release(); /** * Only refs shared pointer counter. * Used only by KisMementoManager without * consideration of COW. */ inline bool ref() const; /** * Only refs shared pointer counter. * Used only by KisMementoManager without * consideration of COW. */ inline bool deref(); /** * Creates a clone of the tile data safely. * It will try to use the cached clones. */ inline KisTileData* clone(); /** * Control the access of swapper to the tile data */ inline void blockSwapping(); inline void unblockSwapping(); /** * The position of the tile data in a swap file */ inline KisChunk swapChunk() const; inline void setSwapChunk(KisChunk chunk); /** * Show whether a tile data is a part of history */ inline bool mementoed() const; inline void setMementoed(bool value); /** * Controlling methods for setting 'age' marks */ inline int age() const; inline void resetAge(); inline void markOld(); /** * Returns number of tiles (or memento items), * referencing the tile data. */ inline qint32 numUsers() const; /** - * Conveniece method. Returns true iff the tile data is linked to + * Convenience method. Returns true iff the tile data is linked to * information only and therefore can be swapped out easily. * * Effectively equivalent to: (mementoed() && numUsers() <= 1) */ inline bool historical() const; /** * Used for swapping purposes only. * Frees the memory occupied by the tile data. * (the caller must save the data beforehand) */ void releaseMemory(); /** * Used for swapping purposes only. * Allocates memory for the tile data after * it has been freed in releaseMemory(). * NOTE: the new data can be not-initialized * and you must fill it yourself! * * \see releaseMemory() */ void allocateMemory(); /** * Releases internal pools, which keep blobs where the tiles are * stored. The point is that we don't allocate the tiles from * glibc directly, but use pools (implemented via boost) to * allocate bigger chunks. This method should be called when one * knows that we have just free'd quite a lot of memory and we * won't need it anymore. E.g. when a document has been closed. */ static void releaseInternalPools(); private: void fillWithPixel(const quint8 *defPixel); static quint8* allocateData(const qint32 pixelSize); static void freeData(quint8 *ptr, const qint32 pixelSize); private: friend class KisTileDataPooler; friend class KisTileDataPoolerTest; /** * A list of pre-duplicated tiledatas. * To make a COW faster, KisTileDataPooler thread duplicates * a tile beforehand and stores clones here, in this stack */ KisTileDataCache m_clonesStack; private: friend class KisTile; friend class KisTileDataStore; friend class KisTileDataStoreIterator; friend class KisTileDataStoreReverseIterator; friend class KisTileDataStoreClockIterator; /** * The state of the tile. * Filled in by tileDataStore and * checked in KisTile::acquireFor* * see also: comment for @m_data */ mutable EnumTileDataState m_state; /** * Iterator that points to a position in the list * where the tile data is stored */ int m_tileNumber = -1; private: /** * The chunk of the swap file, that corresponds * to this tile data. Used by KisSwappedDataStore. */ KisChunk m_swapChunk; /** * The flag is set by KisMementoItem to show this * tile data is going down in history. * * (m_mementoFlag && m_usersCount == 1) means that * the only user of tile data is a memento manager. */ qint32 m_mementoFlag; /** * Counts up time after last access to the tile data. * 0 - recently accessed * 1+ - not recently accessed */ //FIXME: make memory aligned int m_age; /** * The primitive for controlling swapping of the tile. * lockForRead() - used by regular threads to ensure swapper * won't touch this tile data. * tryLockForWrite() - used by swapper to check no-one reads * this tile data */ QReadWriteLock m_swapLock; private: friend class KisLowMemoryTests; /** * FIXME: We should be able to work in const environment * even when actual data is swapped out to disk */ mutable quint8* m_data; /** * How many tiles/mementoes use * this tiledata through COW? */ mutable QAtomicInt m_usersCount; /** * Shared pointer counter */ mutable QAtomicInt m_refCount; qint32 m_pixelSize; //qint32 m_timeStamp; KisTileDataStore *m_store; static SimpleCache m_cache; public: static const qint32 WIDTH; static const qint32 HEIGHT; }; #endif /* KIS_TILE_DATA_INTERFACE_H_ */ diff --git a/libs/image/tiles3/kis_vline_iterator.cpp b/libs/image/tiles3/kis_vline_iterator.cpp index 732d12f194..8a1c7086fd 100644 --- a/libs/image/tiles3/kis_vline_iterator.cpp +++ b/libs/image/tiles3/kis_vline_iterator.cpp @@ -1,234 +1,234 @@ /* * Copyright (c) 2010 Lukáš Tvrdý * 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_vline_iterator.h" #include KisVLineIterator2::KisVLineIterator2(KisDataManager *dataManager, qint32 x, qint32 y, qint32 h, qint32 offsetX, qint32 offsetY, bool writable, KisIteratorCompleteListener *completeListener) : KisBaseIterator(dataManager, writable, completeListener), m_offsetX(offsetX), m_offsetY(offsetY) { x -= m_offsetX; y -= m_offsetY; Q_ASSERT(dataManager != 0); Q_ASSERT(h > 0); // for us, to warn us when abusing the iterators if (h < 1) h = 1; // for release mode, to make sure there's always at least one pixel read. m_lineStride = m_pixelSize * KisTileData::WIDTH; m_x = x; m_y = y; m_top = y; m_bottom = y + h - 1; m_left = m_x; m_havePixels = (h == 0) ? false : true; if (m_top > m_bottom) { m_havePixels = false; return; } m_topRow = yToRow(m_top); m_bottomRow = yToRow(m_bottom); m_column = xToCol(m_x); m_xInTile = calcXInTile(m_x, m_column); m_topInTopmostTile = m_top - m_topRow * KisTileData::WIDTH; m_tilesCacheSize = m_bottomRow - m_topRow + 1; m_tilesCache.resize(m_tilesCacheSize); m_tileSize = m_lineStride * KisTileData::HEIGHT; - // let's prealocate first row + // let's preallocate first row for (int i = 0; i < m_tilesCacheSize; i++){ fetchTileDataForCache(m_tilesCache[i], m_column, m_topRow + i); } m_index = 0; switchToTile(m_topInTopmostTile); } void KisVLineIterator2::resetPixelPos() { m_y = m_top; m_index = 0; switchToTile(m_topInTopmostTile); m_havePixels = true; } void KisVLineIterator2::resetColumnPos() { m_x = m_left; m_column = xToCol(m_x); m_xInTile = calcXInTile(m_x, m_column); preallocateTiles(); resetPixelPos(); } bool KisVLineIterator2::nextPixel() { // We won't increment m_x here as integer can overflow here if (m_y >= m_bottom) { //return !m_isDoneFlag; return m_havePixels = false; } else { ++m_y; m_data += m_lineStride; if (m_data < m_dataBottom) m_oldData += m_lineStride; else { // Switching to the beginning of the next tile ++m_index; switchToTile(0); } } return m_havePixels; } void KisVLineIterator2::nextColumn() { m_y = m_top; ++m_x; if (++m_xInTile < KisTileData::HEIGHT) { /* do nothing, usual case */ } else { ++m_column; m_xInTile = 0; preallocateTiles(); } m_index = 0; switchToTile(m_topInTopmostTile); m_havePixels = true; } qint32 KisVLineIterator2::nConseqPixels() const { return 1; } bool KisVLineIterator2::nextPixels(qint32 n) { Q_ASSERT_X(!(m_y > 0 && (m_y + n) < 0), "vlineIt+=", "Integer overflow"); qint32 previousRow = yToRow(m_y); // We won't increment m_x here first as integer can overflow if (m_y >= m_bottom || (m_y += n) > m_bottom) { m_havePixels = false; } else { qint32 row = yToRow(m_y); // if we are in the same column in tiles if (row == previousRow) { m_data += n * m_pixelSize; } else { qint32 yInTile = calcYInTile(m_y, row); m_index += row - previousRow; switchToTile(yInTile); } } return m_havePixels; } KisVLineIterator2::~KisVLineIterator2() { for (int i = 0; i < m_tilesCacheSize; i++) { unlockTile(m_tilesCache[i].tile); unlockOldTile(m_tilesCache[i].oldtile); } } quint8* KisVLineIterator2::rawData() { return m_data; } const quint8* KisVLineIterator2::oldRawData() const { return m_oldData; } const quint8* KisVLineIterator2::rawDataConst() const { return m_data; } void KisVLineIterator2::switchToTile(qint32 yInTile) { // The caller must ensure that we are not out of bounds Q_ASSERT(m_index < m_tilesCacheSize); Q_ASSERT(m_index >= 0); int offset_row = m_pixelSize * m_xInTile; m_data = m_tilesCache[m_index].data; m_oldData = m_tilesCache[m_index].oldData; m_data += offset_row; m_dataBottom = m_data + m_tileSize; int offset_col = m_pixelSize * yInTile * KisTileData::WIDTH; m_data += offset_col; m_oldData += offset_row + offset_col; } void KisVLineIterator2::fetchTileDataForCache(KisTileInfo& kti, qint32 col, qint32 row) { m_dataManager->getTilesPair(col, row, m_writable, &kti.tile, &kti.oldtile); lockTile(kti.tile); kti.data = kti.tile->data(); lockOldTile(kti.oldtile); kti.oldData = kti.oldtile->data(); } void KisVLineIterator2::preallocateTiles() { for (int i = 0; i < m_tilesCacheSize; ++i){ unlockTile(m_tilesCache[i].tile); unlockOldTile(m_tilesCache[i].oldtile); fetchTileDataForCache(m_tilesCache[i], m_column, m_topRow + i ); } } qint32 KisVLineIterator2::x() const { return m_x + m_offsetX; } qint32 KisVLineIterator2::y() const { return m_y + m_offsetY; } diff --git a/libs/image/tiles3/tests/kis_lockless_stack_test.cpp b/libs/image/tiles3/tests/kis_lockless_stack_test.cpp index 5fe713854a..91f35828fc 100644 --- a/libs/image/tiles3/tests/kis_lockless_stack_test.cpp +++ b/libs/image/tiles3/tests/kis_lockless_stack_test.cpp @@ -1,352 +1,352 @@ /* * 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_lockless_stack_test.h" #include #include "kis_debug.h" #include "tiles3/kis_lockless_stack.h" #include "config-limit-long-tests.h" void KisLocklessStackTest::testOperations() { KisLocklessStack stack; for(qint32 i = 0; i < 1024; i++) { stack.push(i); } QCOMPARE(stack.size(), 1024); for(qint32 i = 1023; i >= 0; i--) { int value; bool result = stack.pop(value); QVERIFY(result); QCOMPARE(value, i); } QVERIFY(stack.isEmpty()); } -/************ BENCHMARKING INFRASTRACTURE ************************/ +/************ BENCHMARKING INFRASTRUCTURE ************************/ #define NUM_TYPES 2 // high-concurrency #define NUM_CYCLES 500000 #define NUM_THREADS 10 // relaxed //#define NUM_CYCLES 100 //#define NUM_THREADS 2 // single-threaded //#define NUM_CYCLES 10000000 //#define NUM_THREADS 1 class KisAbstractIntStack { public: virtual ~KisAbstractIntStack() {} virtual void push(int value) = 0; virtual int pop() = 0; virtual bool isEmpty() = 0; virtual void clear() = 0; }; class KisTestingLocklessStack : public KisAbstractIntStack { public: void push(int value) override { m_stack.push(value); } int pop() override { int value = 0; bool result = m_stack.pop(value); Q_ASSERT(result); Q_UNUSED(result); // for release build return value; } bool isEmpty() override { return m_stack.isEmpty(); } void clear() override { m_stack.clear(); } private: KisLocklessStack m_stack; }; class KisTestingLegacyStack : public KisAbstractIntStack { public: void push(int value) override { m_mutex.lock(); m_stack.push(value); m_mutex.unlock(); } int pop() override { m_mutex.lock(); int result = m_stack.pop(); m_mutex.unlock(); return result; } bool isEmpty() override { m_mutex.lock(); bool result = m_stack.isEmpty(); m_mutex.unlock(); return result; } void clear() override { m_mutex.lock(); m_stack.clear(); m_mutex.unlock(); } private: QStack m_stack; QMutex m_mutex; }; class KisStressJob : public QRunnable { public: KisStressJob(KisAbstractIntStack &stack, qint32 startValue) : m_stack(stack), m_startValue(startValue) { m_pushSum = 0; m_popSum = 0; } void run() override { for(qint32 i = 0; i < NUM_CYCLES; i++) { qint32 type = i % NUM_TYPES; int newValue; switch(type) { case 0: newValue = m_startValue + i; m_pushSum += newValue; m_stack.push(newValue); break; case 1: m_popSum += m_stack.pop(); break; } } } qint64 pushSum() { return m_pushSum; } qint64 popSum() { return m_popSum; } private: KisAbstractIntStack &m_stack; qint32 m_startValue; qint64 m_pushSum; qint64 m_popSum; }; void KisLocklessStackTest::runStressTest(KisAbstractIntStack &stack) { QList jobsList; KisStressJob *job; for(qint32 i = 0; i < NUM_THREADS; i++) { job = new KisStressJob(stack, 1); job->setAutoDelete(false); jobsList.append(job); } QThreadPool pool; pool.setMaxThreadCount(NUM_THREADS); QBENCHMARK { Q_FOREACH (job, jobsList) { pool.start(job); } pool.waitForDone(); } QVERIFY(stack.isEmpty()); qint64 totalSum = 0; for(qint32 i = 0; i < NUM_THREADS; i++) { KisStressJob *job = jobsList.takeLast(); totalSum += job->pushSum(); totalSum -= job->popSum(); dbgKrita << ppVar(totalSum); delete job; } QCOMPARE(totalSum, (long long) 0); } void KisLocklessStackTest::stressTestLockless() { KisTestingLocklessStack stack; runStressTest(stack); } void KisLocklessStackTest::stressTestQStack() { KisTestingLegacyStack stack; runStressTest(stack); } class KisStressClearJob : public QRunnable { public: KisStressClearJob(KisLocklessStack &stack, qint32 startValue) : m_stack(stack), m_startValue(startValue) { } void run() override { for(qint32 i = 0; i < NUM_CYCLES; i++) { qint32 type = i % 4; int newValue; switch(type) { case 0: case 1: newValue = m_startValue + i; m_stack.push(newValue); break; case 2: int tmp; m_stack.pop(tmp); break; case 3: m_stack.clear(); break; } } } private: KisLocklessStack &m_stack; qint32 m_startValue; }; void KisLocklessStackTest::stressTestClear() { KisLocklessStack stack; KisStressClearJob *job; QThreadPool pool; pool.setMaxThreadCount(NUM_THREADS); for(qint32 i = 0; i < NUM_THREADS; i++) { job = new KisStressClearJob(stack, 1); pool.start(job); } pool.waitForDone(); stack.clear(); QVERIFY(stack.isEmpty()); } class KisStressBulkPopJob : public QRunnable { public: KisStressBulkPopJob(KisLocklessStack &stack, qint64 &removedCheckSum) : m_stack(stack), m_removedCheckSum(removedCheckSum) { } void run() override { int value = 0; while (m_stack.pop(value)) { m_removedCheckSum += value; } } private: KisLocklessStack &m_stack; qint64 &m_removedCheckSum; }; void KisLocklessStackTest::stressTestBulkPop() { qsrand(10); KisLocklessStack stack; #ifdef LIMIT_LONG_TESTS const int numThreads = 3; const int numObjects = 10000000; #else const int numThreads = 3; const int numObjects = 10000000; #endif QThreadPool pool; pool.setMaxThreadCount(numThreads); qint64 expectedSum = 0; for (int i = 0; i < numObjects; i++) { const int value = i % 3 + 1; expectedSum += value; stack.push(value); } QVector partialSums(numThreads); for(qint32 i = 0; i < numThreads; i++) { KisStressBulkPopJob *job = new KisStressBulkPopJob(stack, partialSums[i]); pool.start(job); } pool.waitForDone(); QVERIFY(stack.isEmpty()); const qint64 realSum = std::accumulate(partialSums.begin(), partialSums.end(), 0); QCOMPARE(realSum, expectedSum); } QTEST_MAIN(KisLocklessStackTest) diff --git a/libs/koplugin/KisMimeDatabase.cpp b/libs/koplugin/KisMimeDatabase.cpp index dae40c3c67..9ca4ab82a8 100644 --- a/libs/koplugin/KisMimeDatabase.cpp +++ b/libs/koplugin/KisMimeDatabase.cpp @@ -1,292 +1,297 @@ /* * 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 = "krita/x-colorset"; mimeType.description = i18nc("description of a file type", "Krita Color Palette"); mimeType.suffixes = QStringList() << "kpl"; 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-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-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 = "application/x-krita-shortcuts"; mimeType.description = i18nc("description of a file type", "Krita Shortcut Scheme"); mimeType.suffixes = QStringList() << "shortcuts"; s_mimeDatabase << mimeType; mimeType.mimeType = "image/x-krita-raw"; mimeType.description = i18nc("description of a file type", "Camera Raw Files"); 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; + + mimeType.mimeType = "image/jp2"; + mimeType.description = i18nc("description of a file type", "JP2 Image"); + mimeType.suffixes = QStringList() << "jp2" << "j2k"; + s_mimeDatabase << mimeType; debugPlugin << "Filled mimedatabase with" << s_mimeDatabase.count() << "special mimetypes"; } } diff --git a/libs/libkis/Document.cpp b/libs/libkis/Document.cpp index d63b202036..9f731ebf4b 100644 --- a/libs/libkis/Document.cpp +++ b/libs/libkis/Document.cpp @@ -1,995 +1,995 @@ /* * Copyright (c) 2016 Boudewijn Rempt * * 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 "Document.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_animation_importer.h" #include #include #include #include struct Document::Private { Private() {} QPointer document; }; Document::Document(KisDocument *document, QObject *parent) : QObject(parent) , d(new Private) { d->document = document; } Document::~Document() { delete d; } bool Document::operator==(const Document &other) const { return (d->document == other.d->document); } bool Document::operator!=(const Document &other) const { return !(operator==(other)); } bool Document::batchmode() const { if (!d->document) return false; return d->document->fileBatchMode(); } void Document::setBatchmode(bool value) { if (!d->document) return; d->document->setFileBatchMode(value); } Node *Document::activeNode() const { QList activeNodes; Q_FOREACH(QPointer view, KisPart::instance()->views()) { if (view && view->document() == d->document) { activeNodes << view->currentNode(); } } if (activeNodes.size() > 0) { QList nodes = LibKisUtils::createNodeList(activeNodes, d->document->image()); return nodes.first(); } return 0; } void Document::setActiveNode(Node* value) { if (!value->node()) return; KisMainWindow *mainWin = KisPart::instance()->currentMainwindow(); if (!mainWin) return; KisViewManager *viewManager = mainWin->viewManager(); if (!viewManager) return; if (viewManager->document() != d->document) return; KisNodeManager *nodeManager = viewManager->nodeManager(); if (!nodeManager) return; KisNodeSelectionAdapter *selectionAdapter = nodeManager->nodeSelectionAdapter(); if (!selectionAdapter) return; selectionAdapter->setActiveNode(value->node()); } QList Document::topLevelNodes() const { if (!d->document) return QList(); Node n(d->document->image(), d->document->image()->rootLayer()); return n.childNodes(); } Node *Document::nodeByName(const QString &name) const { if (!d->document) return 0; KisNodeSP node = d->document->image()->rootLayer()->findChildByName(name); if (node.isNull()) return 0; return Node::createNode(d->document->image(), node); } QString Document::colorDepth() const { if (!d->document) return ""; return d->document->image()->colorSpace()->colorDepthId().id(); } QString Document::colorModel() const { if (!d->document) return ""; return d->document->image()->colorSpace()->colorModelId().id(); } QString Document::colorProfile() const { if (!d->document) return ""; return d->document->image()->colorSpace()->profile()->name(); } bool Document::setColorProfile(const QString &value) { if (!d->document) return false; if (!d->document->image()) return false; const KoColorProfile *profile = KoColorSpaceRegistry::instance()->profileByName(value); if (!profile) return false; bool retval = d->document->image()->assignImageProfile(profile); d->document->image()->setModified(); d->document->image()->initialRefreshGraph(); return retval; } bool Document::setColorSpace(const QString &colorModel, const QString &colorDepth, const QString &colorProfile) { if (!d->document) return false; if (!d->document->image()) return false; const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->colorSpace(colorModel, colorDepth, colorProfile); if (!colorSpace) return false; d->document->image()->convertImageColorSpace(colorSpace, KoColorConversionTransformation::IntentPerceptual, KoColorConversionTransformation::HighQuality | KoColorConversionTransformation::NoOptimization); d->document->image()->setModified(); d->document->image()->initialRefreshGraph(); return true; } QColor Document::backgroundColor() { if (!d->document) return QColor(); if (!d->document->image()) return QColor(); const KoColor color = d->document->image()->defaultProjectionColor(); return color.toQColor(); } bool Document::setBackgroundColor(const QColor &color) { if (!d->document) return false; if (!d->document->image()) return false; KoColor background = KoColor(color, d->document->image()->colorSpace()); d->document->image()->setDefaultProjectionColor(background); d->document->image()->setModified(); d->document->image()->initialRefreshGraph(); return true; } QString Document::documentInfo() const { QDomDocument doc = KisDocument::createDomDocument("document-info" /*DTD name*/, "document-info" /*tag name*/, "1.1"); doc = d->document->documentInfo()->save(doc); return doc.toString(); } void Document::setDocumentInfo(const QString &document) { KoXmlDocument doc; QString errorMsg; int errorLine, errorColumn; doc.setContent(document, &errorMsg, &errorLine, &errorColumn); d->document->documentInfo()->load(doc); } QString Document::fileName() const { if (!d->document) return QString(); return d->document->url().toLocalFile(); } void Document::setFileName(QString value) { if (!d->document) return; QString mimeType = KisMimeDatabase::mimeTypeForFile(value, false); d->document->setMimeType(mimeType.toLatin1()); d->document->setUrl(QUrl::fromLocalFile(value)); } int Document::height() const { if (!d->document) return 0; KisImageSP image = d->document->image(); if (!image) return 0; return image->height(); } void Document::setHeight(int value) { if (!d->document) return; if (!d->document->image()) return; resizeImage(d->document->image()->bounds().x(), d->document->image()->bounds().y(), d->document->image()->width(), value); } QString Document::name() const { if (!d->document) return ""; return d->document->documentInfo()->aboutInfo("title"); } void Document::setName(QString value) { if (!d->document) return; d->document->documentInfo()->setAboutInfo("title", value); } int Document::resolution() const { if (!d->document) return 0; KisImageSP image = d->document->image(); if (!image) return 0; return qRound(d->document->image()->xRes() * 72); } void Document::setResolution(int value) { if (!d->document) return; KisImageSP image = d->document->image(); if (!image) return; d->document->image()->setResolution(value / 72.0, value / 72.0); } Node *Document::rootNode() const { if (!d->document) return 0; KisImageSP image = d->document->image(); if (!image) return 0; return Node::createNode(image, image->root()); } Selection *Document::selection() const { if (!d->document) return 0; if (!d->document->image()) return 0; if (!d->document->image()->globalSelection()) return 0; return new Selection(d->document->image()->globalSelection()); } void Document::setSelection(Selection* value) { if (!d->document) return; if (!d->document->image()) return; if (value) { d->document->image()->setGlobalSelection(value->selection()); } else { d->document->image()->setGlobalSelection(0); } } int Document::width() const { if (!d->document) return 0; KisImageSP image = d->document->image(); if (!image) return 0; return image->width(); } void Document::setWidth(int value) { if (!d->document) return; if (!d->document->image()) return; resizeImage(d->document->image()->bounds().x(), d->document->image()->bounds().y(), value, d->document->image()->height()); } int Document::xOffset() const { if (!d->document) return 0; KisImageSP image = d->document->image(); if (!image) return 0; return image->bounds().x(); } void Document::setXOffset(int x) { if (!d->document) return; if (!d->document->image()) return; resizeImage(x, d->document->image()->bounds().y(), d->document->image()->width(), d->document->image()->height()); } int Document::yOffset() const { if (!d->document) return 0; KisImageSP image = d->document->image(); if (!image) return 0; return image->bounds().y(); } void Document::setYOffset(int y) { if (!d->document) return; if (!d->document->image()) return; resizeImage(d->document->image()->bounds().x(), y, d->document->image()->width(), d->document->image()->height()); } double Document::xRes() const { if (!d->document) return 0.0; if (!d->document->image()) return 0.0; return d->document->image()->xRes()*72.0; } void Document::setXRes(double xRes) const { if (!d->document) return; if (!d->document->image()) return; d->document->image()->setResolution(xRes/72.0, d->document->image()->yRes()); } double Document::yRes() const { if (!d->document) return 0.0; if (!d->document->image()) return 0.0; return d->document->image()->yRes()*72.0; } void Document::setYRes(double yRes) const { if (!d->document) return; if (!d->document->image()) return; d->document->image()->setResolution(d->document->image()->xRes(), yRes/72.0); } QByteArray Document::pixelData(int x, int y, int w, int h) const { QByteArray ba; if (!d->document) return ba; KisImageSP image = d->document->image(); if (!image) return ba; KisPaintDeviceSP dev = image->projection(); ba.resize(w * h * dev->pixelSize()); dev->readBytes(reinterpret_cast(ba.data()), x, y, w, h); return ba; } bool Document::close() { bool retval = d->document->closeUrl(false); Q_FOREACH(KisView *view, KisPart::instance()->views()) { if (view->document() == d->document) { view->close(); view->closeView(); view->deleteLater(); } } KisPart::instance()->removeDocument(d->document); d->document = 0; return retval; } void Document::crop(int x, int y, int w, int h) { if (!d->document) return; KisImageSP image = d->document->image(); if (!image) return; QRect rc(x, y, w, h); image->cropImage(rc); } bool Document::exportImage(const QString &filename, const InfoObject &exportConfiguration) { if (!d->document) return false; const QString outputFormatString = KisMimeDatabase::mimeTypeForFile(filename, false); const QByteArray outputFormat = outputFormatString.toLatin1(); return d->document->exportDocumentSync(QUrl::fromLocalFile(filename), outputFormat, exportConfiguration.configuration()); } void Document::flatten() { if (!d->document) return; if (!d->document->image()) return; d->document->image()->flatten(0); } void Document::resizeImage(int x, int y, int w, int h) { if (!d->document) return; KisImageSP image = d->document->image(); if (!image) return; QRect rc; rc.setX(x); rc.setY(y); rc.setWidth(w); rc.setHeight(h); image->resizeImage(rc); } void Document::scaleImage(int w, int h, int xres, int yres, QString strategy) { if (!d->document) return; KisImageSP image = d->document->image(); if (!image) return; QRect rc = image->bounds(); rc.setWidth(w); rc.setHeight(h); KisFilterStrategy *actualStrategy = KisFilterStrategyRegistry::instance()->get(strategy); if (!actualStrategy) actualStrategy = KisFilterStrategyRegistry::instance()->get("Bicubic"); image->scaleImage(rc.size(), xres/72, yres/72, actualStrategy); } void Document::rotateImage(double radians) { if (!d->document) return; KisImageSP image = d->document->image(); if (!image) return; image->rotateImage(radians); } void Document::shearImage(double angleX, double angleY) { if (!d->document) return; KisImageSP image = d->document->image(); if (!image) return; image->shear(angleX, angleY); } bool Document::save() { if (!d->document) return false; if (d->document->url().isEmpty()) return false; bool retval = d->document->save(true, 0); d->document->waitForSavingToComplete(); return retval; } bool Document::saveAs(const QString &filename) { if (!d->document) return false; const QString outputFormatString = KisMimeDatabase::mimeTypeForFile(filename, false); const QByteArray outputFormat = outputFormatString.toLatin1(); QUrl oldUrl = d->document->url(); d->document->setUrl(QUrl::fromLocalFile(filename)); bool retval = d->document->saveAs(QUrl::fromLocalFile(filename), outputFormat, true); d->document->waitForSavingToComplete(); d->document->setUrl(oldUrl); return retval; } Node* Document::createNode(const QString &name, const QString &nodeType) { if (!d->document) return 0; if (!d->document->image()) return 0; KisImageSP image = d->document->image(); Node *node = 0; if (nodeType.toLower()== "paintlayer") { node = new Node(image, new KisPaintLayer(image, name, OPACITY_OPAQUE_U8)); } else if (nodeType.toLower() == "grouplayer") { node = new Node(image, new KisGroupLayer(image, name, OPACITY_OPAQUE_U8)); } else if (nodeType.toLower() == "filelayer") { node = new Node(image, new KisFileLayer(image, name, OPACITY_OPAQUE_U8)); } else if (nodeType.toLower() == "filterlayer") { node = new Node(image, new KisAdjustmentLayer(image, name, 0, 0)); } else if (nodeType.toLower() == "filllayer") { node = new Node(image, new KisGeneratorLayer(image, name, 0, 0)); } else if (nodeType.toLower() == "clonelayer") { node = new Node(image, new KisCloneLayer(0, image, name, OPACITY_OPAQUE_U8)); } else if (nodeType.toLower() == "vectorlayer") { node = new Node(image, new KisShapeLayer(d->document->shapeController(), image, name, OPACITY_OPAQUE_U8)); } else if (nodeType.toLower() == "transparencymask") { node = new Node(image, new KisTransparencyMask()); } else if (nodeType.toLower() == "filtermask") { node = new Node(image, new KisFilterMask()); } else if (nodeType.toLower() == "transformmask") { node = new Node(image, new KisTransformMask()); } else if (nodeType.toLower() == "selectionmask") { node = new Node(image, new KisSelectionMask(image)); } return node; } GroupLayer *Document::createGroupLayer(const QString &name) { if (!d->document) return 0; if (!d->document->image()) return 0; KisImageSP image = d->document->image(); return new GroupLayer(image, name); } FileLayer *Document::createFileLayer(const QString &name, const QString fileName, const QString scalingMethod) { if (!d->document) return 0; if (!d->document->image()) return 0; KisImageSP image = d->document->image(); return new FileLayer(image, name, this->fileName(), fileName, scalingMethod); } FilterLayer *Document::createFilterLayer(const QString &name, Filter &filter, Selection &selection) { if (!d->document) return 0; if (!d->document->image()) return 0; KisImageSP image = d->document->image(); return new FilterLayer(image, name, filter, selection); } FillLayer *Document::createFillLayer(const QString &name, const QString generatorName, InfoObject &configuration, Selection &selection) { if (!d->document) return 0; if (!d->document->image()) return 0; KisImageSP image = d->document->image(); KisGeneratorSP generator = KisGeneratorRegistry::instance()->value(generatorName); if (generator) { - KisFilterConfigurationSP config = generator->defaultConfiguration(); + KisFilterConfigurationSP config = generator->factoryConfiguration(); Q_FOREACH(const QString property, configuration.properties().keys()) { config->setProperty(property, configuration.property(property)); } return new FillLayer(image, name, config, selection); } return 0; } CloneLayer *Document::createCloneLayer(const QString &name, const Node *source) { if (!d->document) return 0; if (!d->document->image()) return 0; KisImageSP image = d->document->image(); KisLayerSP layer = qobject_cast(source->node().data()); return new CloneLayer(image, name, layer); } VectorLayer *Document::createVectorLayer(const QString &name) { if (!d->document) return 0; if (!d->document->image()) return 0; KisImageSP image = d->document->image(); return new VectorLayer(d->document->shapeController(), image, name); } FilterMask *Document::createFilterMask(const QString &name, Filter &filter, const Node *selection_source) { if (!d->document) return 0; if (!d->document->image()) return 0; if(!selection_source) return 0; KisLayerSP layer = qobject_cast(selection_source->node().data()); if(layer.isNull()) return 0; KisImageSP image = d->document->image(); FilterMask* mask = new FilterMask(image, name, filter); qobject_cast(mask->node().data())->initSelection(layer); return mask; } FilterMask *Document::createFilterMask(const QString &name, Filter &filter, Selection &selection) { if (!d->document) return 0; if (!d->document->image()) return 0; KisImageSP image = d->document->image(); FilterMask* mask = new FilterMask(image, name, filter); qobject_cast(mask->node().data())->setSelection(selection.selection()); return mask; } SelectionMask *Document::createSelectionMask(const QString &name) { if (!d->document) return 0; if (!d->document->image()) return 0; KisImageSP image = d->document->image(); return new SelectionMask(image, name); } QImage Document::projection(int x, int y, int w, int h) const { if (!d->document || !d->document->image()) return QImage(); return d->document->image()->convertToQImage(x, y, w, h, 0); } QImage Document::thumbnail(int w, int h) const { if (!d->document || !d->document->image()) return QImage(); return d->document->generatePreview(QSize(w, h)).toImage(); } void Document::lock() { if (!d->document || !d->document->image()) return; d->document->image()->barrierLock(); } void Document::unlock() { if (!d->document || !d->document->image()) return; d->document->image()->unlock(); } void Document::waitForDone() { if (!d->document || !d->document->image()) return; d->document->image()->waitForDone(); } bool Document::tryBarrierLock() { if (!d->document || !d->document->image()) return false; return d->document->image()->tryBarrierLock(); } bool Document::isIdle() { if (!d->document || !d->document->image()) return false; return d->document->image()->isIdle(); } void Document::refreshProjection() { if (!d->document || !d->document->image()) return; d->document->image()->refreshGraph(); } QList Document::horizontalGuides() const { QList lines; if (!d->document || !d->document->image()) return lines; KisCoordinatesConverter converter; converter.setImage(d->document->image()); QTransform transform = converter.imageToDocumentTransform().inverted(); QList untransformedLines = d->document->guidesConfig().horizontalGuideLines(); for (int i = 0; i< untransformedLines.size(); i++) { qreal line = untransformedLines[i]; lines.append(transform.map(QPointF(line, line)).x()); } return lines; } QList Document::verticalGuides() const { QList lines; if (!d->document || !d->document->image()) return lines; KisCoordinatesConverter converter; converter.setImage(d->document->image()); QTransform transform = converter.imageToDocumentTransform().inverted(); QList untransformedLines = d->document->guidesConfig().verticalGuideLines(); for (int i = 0; i< untransformedLines.size(); i++) { qreal line = untransformedLines[i]; lines.append(transform.map(QPointF(line, line)).y()); } return lines; } bool Document::guidesVisible() const { return d->document->guidesConfig().showGuides(); } bool Document::guidesLocked() const { return d->document->guidesConfig().lockGuides(); } Document *Document::clone() const { if (!d->document) return 0; QPointer clone = d->document->clone(); Document * d = new Document(clone); clone->setParent(d); // It's owned by the document, not KisPart return d; } void Document::setHorizontalGuides(const QList &lines) { if (!d->document) return; KisGuidesConfig config = d->document->guidesConfig(); KisCoordinatesConverter converter; converter.setImage(d->document->image()); QTransform transform = converter.imageToDocumentTransform(); QList transformedLines; for (int i = 0; i< lines.size(); i++) { qreal line = lines[i]; transformedLines.append(transform.map(QPointF(line, line)).x()); } config.setHorizontalGuideLines(transformedLines); d->document->setGuidesConfig(config); } void Document::setVerticalGuides(const QList &lines) { if (!d->document) return; KisGuidesConfig config = d->document->guidesConfig(); KisCoordinatesConverter converter; converter.setImage(d->document->image()); QTransform transform = converter.imageToDocumentTransform(); QList transformedLines; for (int i = 0; i< lines.size(); i++) { qreal line = lines[i]; transformedLines.append(transform.map(QPointF(line, line)).y()); } config.setVerticalGuideLines(transformedLines); d->document->setGuidesConfig(config); } void Document::setGuidesVisible(bool visible) { if (!d->document) return; KisGuidesConfig config = d->document->guidesConfig(); config.setShowGuides(visible); d->document->setGuidesConfig(config); } void Document::setGuidesLocked(bool locked) { if (!d->document) return; KisGuidesConfig config = d->document->guidesConfig(); config.setLockGuides(locked); d->document->setGuidesConfig(config); } bool Document::modified() const { if (!d->document) return false; return d->document->isModified(); } QRect Document::bounds() const { if (!d->document) return QRect(); return d->document->image()->bounds(); } QPointer Document::document() const { return d->document; } /* Animation related function */ bool Document::importAnimation(const QList &files, int firstFrame, int step) { KisView *activeView = KisPart::instance()->currentMainwindow()->activeView(); KoUpdaterPtr updater = 0; if (activeView && d->document->fileBatchMode()) { updater = activeView->viewManager()->createUnthreadedUpdater(i18n("Import frames")); } KisAnimationImporter importer(d->document->image(), updater); KisImportExportErrorCode status = importer.import(files, firstFrame, step); return status.isOk(); } int Document::framesPerSecond() { if (!d->document) return false; if (!d->document->image()) return false; return d->document->image()->animationInterface()->framerate(); } void Document::setFramesPerSecond(int fps) { if (!d->document) return; if (!d->document->image()) return; d->document->image()->animationInterface()->setFramerate(fps); } void Document::setFullClipRangeStartTime(int startTime) { if (!d->document) return; if (!d->document->image()) return; d->document->image()->animationInterface()->setFullClipRangeStartTime(startTime); } int Document::fullClipRangeStartTime() { if (!d->document) return false; if (!d->document->image()) return false; return d->document->image()->animationInterface()->fullClipRange().start(); } void Document::setFullClipRangeEndTime(int endTime) { if (!d->document) return; if (!d->document->image()) return; d->document->image()->animationInterface()->setFullClipRangeEndTime(endTime); } int Document::fullClipRangeEndTime() { if (!d->document) return false; if (!d->document->image()) return false; return d->document->image()->animationInterface()->fullClipRange().end(); } int Document::animationLength() { if (!d->document) return false; if (!d->document->image()) return false; return d->document->image()->animationInterface()->totalLength(); } void Document::setPlayBackRange(int start, int stop) { if (!d->document) return; if (!d->document->image()) return; const KisTimeRange newTimeRange = KisTimeRange(start, (stop-start)); d->document->image()->animationInterface()->setPlaybackRange(newTimeRange); } int Document::playBackStartTime() { if (!d->document) return false; if (!d->document->image()) return false; return d->document->image()->animationInterface()->playbackRange().start(); } int Document::playBackEndTime() { if (!d->document) return false; if (!d->document->image()) return false; return d->document->image()->animationInterface()->playbackRange().end(); } int Document::currentTime() { if (!d->document) return false; if (!d->document->image()) return false; return d->document->image()->animationInterface()->currentTime(); } void Document::setCurrentTime(int time) { if (!d->document) return; if (!d->document->image()) return; return d->document->image()->animationInterface()->requestTimeSwitchWithUndo(time); } diff --git a/libs/libkis/FillLayer.cpp b/libs/libkis/FillLayer.cpp index caeb9fa6f7..a4efb92518 100644 --- a/libs/libkis/FillLayer.cpp +++ b/libs/libkis/FillLayer.cpp @@ -1,74 +1,74 @@ /* * 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 "FillLayer.h" #include #include #include #include #include #include FillLayer::FillLayer(KisImageSP image, QString name, KisFilterConfigurationSP filterConfig, Selection &selection, QObject *parent) : Node(image, new KisGeneratorLayer(image, name, filterConfig, selection.selection()), parent) { } FillLayer::FillLayer(KisGeneratorLayerSP layer, QObject *parent): Node(layer->image(), layer, parent) { } FillLayer::~FillLayer() { } QString FillLayer::generatorName() { const KisGeneratorLayer *layer = qobject_cast(this->node()); return layer->filter()->name(); } InfoObject * FillLayer::filterConfig() { const KisGeneratorLayer *layer = qobject_cast(this->node()); return new InfoObject(layer->filter()); } QString FillLayer::type() const { return "filllayer"; } bool FillLayer::setGenerator(const QString &generatorName, InfoObject *config) { KisGeneratorLayer *layer = dynamic_cast(this->node().data()); //getting the default configuration here avoids trouble with versioning. KisGeneratorSP generator = KisGeneratorRegistry::instance()->value(generatorName); if (generator) { - KisFilterConfigurationSP cfg = generator->defaultConfiguration(); + KisFilterConfigurationSP cfg = generator->factoryConfiguration(); Q_FOREACH(const QString property, config->properties().keys()) { cfg->setProperty(property, config->property(property)); } layer->setFilter(cfg); return true; } return false; } diff --git a/libs/libkis/Filter.cpp b/libs/libkis/Filter.cpp index a0f9a4c8a0..f20a5e613a 100644 --- a/libs/libkis/Filter.cpp +++ b/libs/libkis/Filter.cpp @@ -1,174 +1,174 @@ /* * Copyright (c) 2016 Boudewijn Rempt * * 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 "Filter.h" #include #include #include #include #include #include #include #include #include #include #include #include "Krita.h" #include "Document.h" #include "InfoObject.h" #include "Node.h" struct Filter::Private { Private() {} QString name; InfoObject *configuration {0}; }; Filter::Filter() : QObject(0) , d(new Private) { } Filter::~Filter() { delete d->configuration; delete d; } bool Filter::operator==(const Filter &other) const { return (d->name == other.d->name && d->configuration == other.d->configuration); } bool Filter::operator!=(const Filter &other) const { return !(operator==(other)); } QString Filter::name() const { return d->name; } void Filter::setName(const QString &name) { d->name = name; delete d->configuration; KisFilterSP filter = KisFilterRegistry::instance()->value(d->name); d->configuration = new InfoObject(filter->defaultConfiguration()); } InfoObject* Filter::configuration() const { return d->configuration; } void Filter::setConfiguration(InfoObject* value) { d->configuration = value; } bool Filter::apply(Node *node, int x, int y, int w, int h) { if (node->locked()) return false; KisFilterSP filter = KisFilterRegistry::instance()->value(d->name); if (!filter) return false; KisPaintDeviceSP dev = node->paintDevice(); if (!dev) return false; QRect applyRect = QRect(x, y, w, h); KisFilterConfigurationSP config = static_cast(d->configuration->configuration().data()); filter->process(dev, applyRect, config); return true; } bool Filter::startFilter(Node *node, int x, int y, int w, int h) { if (node->locked()) return false; KisFilterSP filter = KisFilterRegistry::instance()->value(d->name); if (!filter) return false; KisImageWSP image = node->image(); if (!image) return false; KisFilterConfigurationSP filterConfig = static_cast(d->configuration->configuration().data()); image->waitForDone(); QRect initialApplyRect = QRect(x, y, w, h); QRect applyRect = initialApplyRect; KisPaintDeviceSP paintDevice = node->paintDevice(); if (paintDevice && filter->needsTransparentPixels(filterConfig.data(), paintDevice->colorSpace())) { applyRect |= image->bounds(); } KisResourcesSnapshotSP resources = new KisResourcesSnapshot(image, node->node()); Document *document = Krita::instance()->activeDocument(); if (document && KisPart::instance()->viewCount(document->document()) > 0) { Q_FOREACH (QPointer view, KisPart::instance()->views()) { if (view && view->document() == document->document()) { resources = new KisResourcesSnapshot(image, node->node(), view->resourceProvider()->resourceManager()); break; } } } delete document; KisStrokeId currentStrokeId = image->startStroke(new KisFilterStrokeStrategy(filter, KisFilterConfigurationSP(filterConfig), resources)); QRect processRect = filter->changedRect(applyRect, filterConfig.data(), 0); processRect &= image->bounds(); if (filter->supportsThreading()) { QSize size = KritaUtils::optimalPatchSize(); QVector rects = KritaUtils::splitRectIntoPatches(processRect, size); Q_FOREACH (const QRect &rc, rects) { image->addJob(currentStrokeId, new KisFilterStrokeStrategy::Data(rc, true)); } } else { image->addJob(currentStrokeId, new KisFilterStrokeStrategy::Data(processRect, false)); } image->endStroke(currentStrokeId); image->waitForDone(); return true; } KisFilterConfigurationSP Filter::filterConfig() { - KisFilterConfigurationSP config = KisFilterRegistry::instance()->get(d->name)->defaultConfiguration(); + KisFilterConfigurationSP config = KisFilterRegistry::instance()->get(d->name)->factoryConfiguration(); Q_FOREACH(const QString property, d->configuration->properties().keys()) { config->setProperty(property, d->configuration->property(property)); } return config; } diff --git a/libs/libkis/Resource.h b/libs/libkis/Resource.h index 8ed2052c11..87a6c0f05b 100644 --- a/libs/libkis/Resource.h +++ b/libs/libkis/Resource.h @@ -1,121 +1,121 @@ /* * Copyright (c) 2016 Boudewijn Rempt * * 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_RESOURCE_H #define LIBKIS_RESOURCE_H #include #include #include "kritalibkis_export.h" #include "libkis.h" class KoResource; /** * A Resource represents a gradient, pattern, brush tip, brush preset, palette or * workspace definition. * * @code * allPresets = Application.resources("preset") * for preset in allPresets: * print(preset.name()) * @endcode * * Resources are identified by their type, name and filename. If you want to change * the contents of a resource, you should read its data using data(), parse it and * write the changed contents back. */ class KRITALIBKIS_EXPORT Resource : public QObject { Q_OBJECT public: explicit Resource(KoResource *resource, QObject *parent = 0); ~Resource() override; bool operator==(const Resource &other) const; bool operator!=(const Resource &other) const; public Q_SLOTS: /** * Return the type of this resource. Valid types are: *

    *
  • pattern: a raster image representing a pattern *
  • gradient: a gradient *
  • brush: a brush tip *
  • preset: a brush preset *
  • palette: a color set *
  • workspace: a workspace definition. *
*/ QString type() const; /** * The user-visible name of the resource. */ QString name() const; /** * setName changes the user-visible name of the current resource. */ void setName(QString value); /** * The filename of the resource, if present. Not all resources * are loaded from files. */ QString filename() const; /** * An image that can be used to represent the resource in the * user interface. For some resources, like patterns, the * image is identical to the resource, for others it's a mere * icon. */ QImage image() const; /** * Change the image for this resource. */ void setImage(QImage image); /** * Return the resource as a byte array. */ QByteArray data() const; /** * Change the internal data of the resource to the given byte * array. If the byte array is not valid, setData returns - * false, otherwwise true. + * false, otherwise true. */ bool setData(QByteArray data); private: friend class PresetChooser; friend class View; friend class Palette; KoResource *resource() const; struct Private; const Private *const d; }; #endif // LIBKIS_RESOURCE_H diff --git a/libs/libqml/plugins/kritasketchplugin/models/CompositeOpModel.cpp b/libs/libqml/plugins/kritasketchplugin/models/CompositeOpModel.cpp index 0e416a618c..91a85a991a 100644 --- a/libs/libqml/plugins/kritasketchplugin/models/CompositeOpModel.cpp +++ b/libs/libqml/plugins/kritasketchplugin/models/CompositeOpModel.cpp @@ -1,482 +1,482 @@ /* 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 "CompositeOpModel.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class CompositeOpModel::Private { public: Private(CompositeOpModel* qq) : q(qq) , model(new KisCompositeOpListModel()) , view(0) , eraserMode(0) , opacity(0) , opacityEnabled(false) , flow(0) , flowEnabled(false) , size(0) , sizeEnabled(false) , presetsEnabled(true) {}; CompositeOpModel* q; KisCompositeOpListModel* model; KisViewManager* view; QString currentCompositeOpID; QString prevCompositeOpID; bool eraserMode; QMap settingsWidgets; qreal opacity; bool opacityEnabled; qreal flow; bool flowEnabled; qreal size; bool sizeEnabled; bool presetsEnabled; KisPaintOpPresetSP currentPreset; void updateCompositeOp(QString compositeOpID) { if (!view) return; KisNodeSP node = view->canvasResourceProvider()->currentNode(); if (node && node->paintDevice()) { if (!node->paintDevice()->colorSpace()->hasCompositeOp(compositeOpID)) compositeOpID = KoCompositeOpRegistry::instance().getDefaultCompositeOp().id(); if (compositeOpID != currentCompositeOpID) { q->setEraserMode(compositeOpID == COMPOSITE_ERASE); currentPreset->settings()->setProperty("CompositeOp", compositeOpID); //m_optionWidget->setConfiguration(m_activePreset->settings().data()); view->canvasResourceProvider()->setCurrentCompositeOp(compositeOpID); prevCompositeOpID = currentCompositeOpID; currentCompositeOpID = compositeOpID; } } emit q->currentCompositeOpIDChanged(); } void ofsChanged() { if (presetsEnabled && !currentPreset.isNull() && !currentPreset->settings().isNull()) { // IMPORTANT: set the PaintOp size before setting the other properties // it wont work the other way //qreal sizeDiff = size - currentPreset->settings()->paintOpSize(); //currentPreset->settings()->changePaintOpSize(sizeDiff, 0); if (currentPreset->settings()->hasProperty("OpacityValue")) currentPreset->settings()->setProperty("OpacityValue", opacity); if (currentPreset->settings()->hasProperty("FlowValue")) currentPreset->settings()->setProperty("FlowValue", flow); //m_optionWidget->setConfiguration(d->currentPreset->settings().data()); } if (view) { view->canvasResourceProvider()->setOpacity(opacity); } } }; CompositeOpModel::CompositeOpModel(QObject* parent) : QAbstractListModel(parent) , d(new Private(this)) { connect(KoToolManager::instance(), SIGNAL(changedTool(KoCanvasController*,int)), this, SLOT(slotToolChanged(KoCanvasController*,int))); } CompositeOpModel::~CompositeOpModel() { delete d; } QHash CompositeOpModel::roleNames() const { QHash roles; roles[TextRole] = "text"; roles[IsCategoryRole] = "isCategory"; return roles; } QVariant CompositeOpModel::data(const QModelIndex& index, int role) const { QVariant data; if (index.isValid()) { QModelIndex otherIndex = d->model->index(index.row(), index.column(), QModelIndex()); switch(role) { case TextRole: data = d->model->data(otherIndex, Qt::DisplayRole); break; case IsCategoryRole: data = d->model->data(otherIndex, __CategorizedListModelBase::IsHeaderRole); break; default: break; } } return data; } int CompositeOpModel::rowCount(const QModelIndex& parent) const { if (parent.isValid()) return 0; return d->model->rowCount(QModelIndex()); } void CompositeOpModel::activateItem(int index) { if (index > -1 && index < d->model->rowCount(QModelIndex())) { KoID compositeOp; if (d->model->entryAt(compositeOp, d->model->index(index))) d->updateCompositeOp(compositeOp.id()); } } QObject* CompositeOpModel::view() const { return d->view; } void CompositeOpModel::setView(QObject* newView) { if (d->view) { d->view->canvasBase()->disconnect(this); d->view->canvasBase()->globalInputManager()->disconnect(this); d->view->nodeManager()->disconnect(this); } d->view = qobject_cast( newView ); if (d->view) { if (d->view->canvasBase() && d->view->canvasBase()->resourceManager()) { connect(d->view->canvasBase()->resourceManager(), SIGNAL(canvasResourceChanged(int,QVariant)), this, SLOT(resourceChanged(int,QVariant))); } - if (d->view->nodeManager()) { - connect(d->view->nodeManager(), SIGNAL(sigLayerActivated(KisLayerSP)), - this, SLOT(currentNodeChanged(KisLayerSP))); - } +// if (d->view->nodeManager()) { +// connect(d->view->nodeManager(), SIGNAL(sigLayerActivated(KisLayerSP)), +// this, SLOT(currentNodeChanged(KisLayerSP))); +// } slotToolChanged(0, 0); } emit viewChanged(); } bool CompositeOpModel::eraserMode() const { return d->eraserMode; } void CompositeOpModel::setEraserMode(bool newEraserMode) { if (d->eraserMode != newEraserMode) { d->eraserMode = newEraserMode; if (d->eraserMode) d->updateCompositeOp(COMPOSITE_ERASE); else d->updateCompositeOp(d->prevCompositeOpID); emit eraserModeChanged(); } } qreal CompositeOpModel::flow() const { return d->flow; } void CompositeOpModel::setFlow(qreal newFlow) { if (d->flow != newFlow) { d->flow = newFlow; d->ofsChanged(); emit flowChanged(); } } bool CompositeOpModel::flowEnabled() const { return d->flowEnabled; } void CompositeOpModel::setFlowEnabled(bool newFlowEnabled) { d->flowEnabled = newFlowEnabled; emit flowEnabledChanged(); } qreal CompositeOpModel::opacity() const { return d->opacity; } void CompositeOpModel::setOpacity(qreal newOpacity) { if (d->opacity != newOpacity) { d->opacity = newOpacity; d->ofsChanged(); emit opacityChanged(); } } bool CompositeOpModel::opacityEnabled() const { return d->opacityEnabled; } void CompositeOpModel::setOpacityEnabled(bool newOpacityEnabled) { d->opacityEnabled = newOpacityEnabled; emit opacityEnabledChanged(); } qreal CompositeOpModel::size() const { return d->size; } void CompositeOpModel::setSize(qreal newSize) { if (d->size != newSize) { d->size = newSize; d->ofsChanged(); emit sizeChanged(); } } bool CompositeOpModel::sizeEnabled() const { return d->sizeEnabled; } void CompositeOpModel::setSizeEnabled(bool newSizeEnabled) { d->sizeEnabled = newSizeEnabled; emit sizeEnabledChanged(); } void CompositeOpModel::changePaintopValue(QString propertyName, QVariant value) { if (propertyName == "size" && value.toReal() != d->size) setSize(value.toReal()); else if (propertyName == "opacity" && value.toReal() != d->opacity) setOpacity(value.toReal()); else if (propertyName == "flow" && value.toReal() != d->flow) setFlow(value.toReal()); } bool CompositeOpModel::mirrorHorizontally() const { if (d->view) return d->view->canvasResourceProvider()->mirrorHorizontal(); return false; } void CompositeOpModel::setMirrorHorizontally(bool newMirrorHorizontally) { if (d->view && d->view->canvasResourceProvider()->mirrorHorizontal() != newMirrorHorizontally) { d->view->canvasResourceProvider()->setMirrorHorizontal(newMirrorHorizontally); emit mirrorHorizontallyChanged(); } } bool CompositeOpModel::mirrorVertically() const { if (d->view) return d->view->canvasResourceProvider()->mirrorVertical(); return false; } void CompositeOpModel::setMirrorVertically(bool newMirrorVertically) { if (d->view && d->view->canvasResourceProvider()->mirrorVertical() != newMirrorVertically) { d->view->canvasResourceProvider()->setMirrorVertical(newMirrorVertically); emit mirrorVerticallyChanged(); } } void CompositeOpModel::slotToolChanged(KoCanvasController* canvas, int toolId) { Q_UNUSED(canvas); Q_UNUSED(toolId); if (!d->view) return; if (!d->view->canvasBase()) return; QString id = KoToolManager::instance()->activeToolId(); KisTool* tool = dynamic_cast(KoToolManager::instance()->toolById(d->view->canvasBase(), id)); if (tool) { int flags = tool->flags(); if (flags & KisTool::FLAG_USES_CUSTOM_COMPOSITEOP) { //setWidgetState(ENABLE_COMPOSITEOP|ENABLE_OPACITY); d->opacityEnabled = true; } else { //setWidgetState(DISABLE_COMPOSITEOP|DISABLE_OPACITY); d->opacityEnabled = false; } if (flags & KisTool::FLAG_USES_CUSTOM_PRESET) { d->flowEnabled = true; d->sizeEnabled = true; d->presetsEnabled = true; } else { d->flowEnabled = false; d->sizeEnabled = false; d->presetsEnabled = false; } } else { d->opacityEnabled = false; d->flowEnabled = false; d->sizeEnabled = false; } emit opacityEnabledChanged(); emit flowEnabledChanged(); emit sizeEnabledChanged(); } void CompositeOpModel::resourceChanged(int key, const QVariant& /*v*/) { if (d->view && d->view->canvasBase() && d->view->canvasBase()->resourceManager() && d->view->canvasResourceProvider()) { if (key == KisCanvasResourceProvider::MirrorHorizontal) { emit mirrorHorizontallyChanged(); return; } else if(key == KisCanvasResourceProvider::MirrorVertical) { emit mirrorVerticallyChanged(); return; } KisPaintOpPresetSP preset = d->view->canvasBase()->resourceManager()->resource(KisCanvasResourceProvider::CurrentPaintOpPreset).value(); if (preset && d->currentPreset.data() != preset.data()) { d->currentPreset = preset; if (!d->settingsWidgets.contains(preset.data())) { d->settingsWidgets[preset.data()] = KisPaintOpRegistry::instance()->get(preset->paintOp().id())->createConfigWidget(0); d->settingsWidgets[preset.data()]->setImage(d->view->image()); d->settingsWidgets[preset.data()]->setConfiguration(preset->settings()); } if (d->settingsWidgets[preset.data()]) { preset->settings()->setOptionsWidget(d->settingsWidgets[preset.data()]); } d->size = preset->settings()->paintOpSize(); emit sizeChanged(); if (preset->settings()->hasProperty("OpacityValue")) { d->opacityEnabled = true; d->opacity = preset->settings()->getProperty("OpacityValue").toReal(); } else { d->opacityEnabled = false; d->opacity = 1; } d->view->canvasResourceProvider()->setOpacity(d->opacity); emit opacityChanged(); emit opacityEnabledChanged(); if (preset->settings()->hasProperty("FlowValue")) { d->flowEnabled = true; d->flow = preset->settings()->getProperty("FlowValue").toReal(); } else { d->flowEnabled = false; d->flow = 1; } emit flowChanged(); emit flowEnabledChanged(); QString compositeOp = preset->settings()->getString("CompositeOp"); // This is a little odd, but the logic here is that the opposite of an eraser is a normal composite op (so we just select over, aka normal) // This means that you can switch your eraser over to being a painting tool by turning off the eraser again. if (compositeOp == COMPOSITE_ERASE) { d->currentCompositeOpID = COMPOSITE_OVER; d->eraserMode = true; } else { d->eraserMode = false; } emit eraserModeChanged(); d->updateCompositeOp(compositeOp); } } } void CompositeOpModel::currentNodeChanged(KisLayerSP newNode) { Q_UNUSED(newNode); if (d->eraserMode) { d->eraserMode = false; d->updateCompositeOp(d->prevCompositeOpID); emit eraserModeChanged(); } } int CompositeOpModel::indexOf(QString compositeOpId) { return d->model->indexOf(KoID(compositeOpId)).row(); } QString CompositeOpModel::currentCompositeOpID() const { return d->currentCompositeOpID; } diff --git a/libs/libqml/plugins/kritasketchplugin/models/FiltersCategoryModel.cpp b/libs/libqml/plugins/kritasketchplugin/models/FiltersCategoryModel.cpp index 6b156a4249..99d7f57333 100644 --- a/libs/libqml/plugins/kritasketchplugin/models/FiltersCategoryModel.cpp +++ b/libs/libqml/plugins/kritasketchplugin/models/FiltersCategoryModel.cpp @@ -1,316 +1,316 @@ /* 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 "FiltersCategoryModel.h" #include "FiltersModel.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include bool categoryLessThan(const FiltersModel* s1, const FiltersModel* s2) { return s1->categoryName.toLower() < s2->categoryName.toLower(); } class FiltersCategoryModel::Private { public: Private(FiltersCategoryModel* qq) : q(qq) , currentCategory(-1) , view(0) , previewEnabled(false) , previewFilterID(-1) , previewTimer(new QTimer()) { previewTimer->setInterval(150); previewTimer->setSingleShot(true); connect(previewTimer, SIGNAL(timeout()), q, SLOT(updatePreview())); } FiltersCategoryModel* q; int currentCategory; KisViewManager* view; QList categories; FiltersModel* categoryByName(const QString& name) { FiltersModel* category = 0; for(int i = 0; i < categories.count(); ++i) { if (categories.at(i)->categoryId == name) { category = categories[i]; break; } } return category; } void refreshContents() { q->beginResetModel(); qDeleteAll(categories); categories.clear(); QList filters = KisFilterRegistry::instance()->values(); QList tmpCategoryIDs; Q_FOREACH (const KisFilterSP filter, filters) { Q_ASSERT(filter); FiltersModel* cat = 0; if (!tmpCategoryIDs.contains(filter->menuCategory().id())) { cat = new FiltersModel(q); cat->categoryId = filter->menuCategory().id(); cat->categoryName = filter->menuCategory().name(); cat->setView(view); categories << cat; tmpCategoryIDs << filter->menuCategory().id(); connect(cat, SIGNAL(configurationChanged(int)), q, SLOT(filterConfigurationChanged(int))); connect(cat, SIGNAL(filterActivated(int)), q, SLOT(filterActivated(int))); } else cat = categoryByName(filter->menuCategory().id()); cat->addFilter(filter); qApp->processEvents(); } std::sort(categories.begin(), categories.end(), categoryLessThan); q->endResetModel(); } bool previewEnabled; KisFilterMaskSP mask; KisNodeSP node; int previewFilterID; KisFilterConfigurationSP newConfig; QTimer* previewTimer; }; FiltersCategoryModel::FiltersCategoryModel(QObject* parent) : QAbstractListModel(parent) , d(new Private(this)) { } FiltersCategoryModel::~FiltersCategoryModel() { delete d; } QHash FiltersCategoryModel::roleNames() const { QHash roles; roles[TextRole] = "text"; return roles; } QVariant FiltersCategoryModel::data(const QModelIndex& index, int role) const { QVariant data; if (index.isValid()) { switch(role) { case TextRole: data = d->categories[index.row()]->categoryName; break; default: break; } } return data; } int FiltersCategoryModel::rowCount(const QModelIndex& parent) const { if (parent.isValid()) return 0; return d->categories.count(); } QObject* FiltersCategoryModel::filterModel() const { if (d->currentCategory == -1) return 0; return d->categories[d->currentCategory]; } void FiltersCategoryModel::activateItem(int index) { if (index > -1 && index < d->categories.count()) { d->currentCategory = index; emit filterModelChanged(); } } QObject* FiltersCategoryModel::view() const { return d->view; } void FiltersCategoryModel::setView(QObject* newView) { if (d->view) { setPreviewEnabled(false); d->view->nodeManager()->disconnect(this); d->view->selectionManager()->disconnect(this); } d->view = qobject_cast( newView ); if (d->view) { d->refreshContents(); - connect(d->view->nodeManager(), SIGNAL(sigLayerActivated(KisLayerSP)), this, SLOT(activeLayerChanged(KisLayerSP))); +// connect(d->view->nodeManager(), SIGNAL(sigLayerActivated(KisLayerSP)), this, SLOT(activeLayerChanged(KisLayerSP))); connect(d->view->selectionManager(), SIGNAL(currentSelectionChanged()), this, SLOT(activeSelectionChanged())); } emit viewChanged(); } void FiltersCategoryModel::activeLayerChanged(KisLayerSP layer) { Q_UNUSED(layer); setPreviewEnabled(false); } void FiltersCategoryModel::activeSelectionChanged() { setPreviewEnabled(false); } void FiltersCategoryModel::filterActivated(int index) { Q_UNUSED(index); setPreviewEnabled(false); } void FiltersCategoryModel::filterConfigurationChanged(int index, FiltersModel* model) { d->previewFilterID = index; if (d->previewEnabled && index > -1) { if (!model) { model = qobject_cast(sender()); } if (!model) { return; } KisFilterConfigurationSP config; KisFilter* filter = model->filter(index); if (filter->showConfigurationWidget() && filter->id() != QLatin1String("colortransfer")) { KisConfigWidget* wdg = filter->createConfigurationWidget(0, d->view->activeNode()->original(), false); wdg->deleteLater(); config = KisFilterConfigurationSP(KisFilterRegistry::instance()->cloneConfiguration(dynamic_cast(wdg->configuration().data()))); } else { config = KisFilterConfigurationSP(KisFilterRegistry::instance()->cloneConfiguration(filter->defaultConfiguration())); } QObject* configuration = d->categories[d->currentCategory]->configuration(index); Q_FOREACH (const QByteArray& propName, configuration->dynamicPropertyNames()) { config->setProperty(QString(propName), configuration->property(propName)); } config->setCurve(qobject_cast(configuration)->curve()); config->setCurves(qobject_cast(configuration)->curves()); configuration->deleteLater(); d->newConfig = config; d->previewTimer->start(); } } void FiltersCategoryModel::updatePreview() { d->view->filterManager()->apply(d->newConfig); } bool FiltersCategoryModel::previewEnabled() const { return d->previewEnabled; } void FiltersCategoryModel::filterSelected(int index) { if (d->previewEnabled) filterConfigurationChanged(index, d->categories[d->currentCategory]); } void FiltersCategoryModel::setPreviewEnabled(bool enabled) { if (d->previewEnabled != enabled) { d->previewEnabled = enabled; emit previewEnabledChanged(); if (enabled) filterConfigurationChanged(d->previewFilterID, d->categories[d->currentCategory]); else d->view->filterManager()->cancel(); } } int FiltersCategoryModel::categoryIndexForConfig(QObject* config) { PropertyContainer* configuration = qobject_cast(config); if (!configuration) return -1; FiltersModel* model = 0; int i = 0; while(model == 0 && i < d->categories.count()) { FiltersModel* cat = d->categories.at(i); // i know there's no check here - but a category is not created unless there // is something to put in it for(int j = 0; j < cat->rowCount(); ++j) { if (cat->filter(j)->id() == configuration->name()) return i; } ++i; } return -1; } int FiltersCategoryModel::filterIndexForConfig(int categoryIndex, QObject* filterConfig) { PropertyContainer* configuration = qobject_cast(filterConfig); if (!configuration) return -1; if (categoryIndex < 0 || categoryIndex > d->categories.count() - 1) return -1; FiltersModel* cat = d->categories.at(categoryIndex); // i know there's no check here - but a category is not created unless there // is something to put in it for(int j = 0; j < cat->rowCount(); ++j) { if (cat->filter(j)->id() == configuration->name()) return j; } return -1; } diff --git a/libs/odf/KoBorder.cpp b/libs/odf/KoBorder.cpp index c357ccee44..c546abe8ff 100644 --- a/libs/odf/KoBorder.cpp +++ b/libs/odf/KoBorder.cpp @@ -1,1164 +1,1164 @@ /* This file is part of the KDE project * * Copyright (C) 2009 Inge Wallin * Copyright (C) 2009 Thomas Zander * Copyright (C) 2011 Pierre Ducroquet * * 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 "KoBorder.h" #include #include #include #include #include #include class KoBorderPrivate : public QSharedData { public: KoBorderPrivate(); ~KoBorderPrivate(); QMap data; }; KoBorderPrivate::KoBorderPrivate() { } KoBorderPrivate::~KoBorderPrivate() { } KoBorder::BorderData::BorderData() : style(KoBorder::BorderNone) , outerPen(QPen()) , innerPen(QPen()) , spacing(0) { outerPen.setWidthF(0.0f); innerPen.setWidthF(0.0f); } bool KoBorder::BorderData::operator==(const KoBorder::BorderData& other) const { // Left Borders if (style == BorderNone && other.style == BorderNone) { // If both styles are None, then the rest of the values don't // need to be compared. ; } else if (style != other.style) { // If any of them are non-None, and they are different, the // borders are also different. return false; } else { // Here we know that the border styles are the same, now // compare the rest of the values. if (outerPen != other.outerPen) return false; // If the border style == BorderDouble, then compare a couple // of other values too. if (style == BorderDouble) { if (innerPen != other.innerPen) return false; if (spacing != other.spacing) return false; } } return true; } // ---------------------------------------------------------------- KoBorder::KoBorder() : d(new KoBorderPrivate) { } KoBorder::KoBorder(const KoBorder &kb) : d(kb.d) { } KoBorder::~KoBorder() { // No delete because d is a QSharedDataPointer. } // ---------------------------------------------------------------- // operators KoBorder &KoBorder::operator=(const KoBorder &other) { d = other.d; return *this; } bool KoBorder::operator==(const KoBorder &other) const { if (d.data() == other.d.data()) return true; if (d->data.size() != other.d->data.size()) return false; KoBorder::BorderSide key; foreach (key, d->data.keys()) { if (!other.d->data.contains(key)) return false; if (!(other.d->data[key] == d->data[key])) return false; } return true; } // ---------------------------------------------------------------- // public, non-class functions KoBorder::BorderStyle KoBorder::odfBorderStyle(const QString &borderstyle, bool *converted) { // Note: the styles marked "Not odf compatible" below are legacies // from the old words format. There are also lots of border // styles in the MS DOC that we may have to handle at some point. if (converted) *converted = true; if (borderstyle == "none") return BorderNone; if (borderstyle == "solid") return BorderSolid; if (borderstyle == "dashed") return BorderDashed; if (borderstyle == "dotted") return BorderDotted; if (borderstyle == "dot-dash") return BorderDashDot; if (borderstyle == "dot-dot-dash") return BorderDashDotDot; if (borderstyle == "double") return BorderDouble; if (borderstyle == "groove") // Not odf compatible -- see above return BorderGroove; if (borderstyle == "ridge") // Not odf compatible -- see above return BorderRidge; if (borderstyle == "inset") // Not odf compatible -- see above return BorderInset; if (borderstyle == "outset") // Not odf compatible -- see above return BorderOutset; if (borderstyle == "dash-largegap") return KoBorder::BorderDashedLong; if (borderstyle == "slash") // not officially odf, but we support it anyway return KoBorder::BorderSlash; if (borderstyle == "wave") // not officially odf, but we support it anyway return KoBorder::BorderWave; if (borderstyle == "double-wave") // not officially odf, but we support it anyway return KoBorder::BorderDoubleWave; if (converted) *converted = false; return BorderSolid; } QString KoBorder::odfBorderStyleString(BorderStyle borderstyle) { switch (borderstyle) { case BorderDashed: return QString("dashed"); case BorderDotted: return QString("dotted"); case BorderDashDot: return QString("dot-dash"); case BorderDashDotDot: return QString("dot-dot-dash"); case BorderDouble: return QString("double"); case BorderGroove: return QString("groove"); // not odf -- see above case BorderRidge: return QString("ridge"); // not odf -- see above case BorderInset: return QString("inset"); // not odf -- see above case BorderOutset: return QString("outset"); // not odf -- see above case BorderSolid: return QString("solid"); case BorderNone: return QString("none"); default: // Handle unknown types as solid. return QString("solid"); } } QString KoBorder::msoBorderStyleString(BorderStyle borderstyle) { switch (borderstyle) { case KoBorder::BorderDashedLong: return QString("dash-largegap"); case KoBorder::BorderSlash: return QString("slash"); // not officially odf, but we support it anyway case KoBorder::BorderWave: return QString("wave"); // not officially odf, but we support it anyway case KoBorder::BorderDoubleWave: return QString("double-wave"); // not officially odf, but we support it anyway default: // Handle remaining styles as odf type style. return odfBorderStyleString(borderstyle); } } // ---------------------------------------------------------------- // Getters and Setters void KoBorder::setBorderStyle(BorderSide side, BorderStyle style) { if (d->data[side].style == style) { return; } if (!d->data.contains(side)) { BorderData data; data.style = style; d->data[side] = data; } else { d->data[side].style = style; } // Make a best effort to create the best possible dash pattern for the chosen style. // FIXME: KoTableCellStyle::setEdge() should call this function. BorderData &edge = d->data[side]; qreal width = edge.outerPen.widthF(); qreal innerWidth = 0; qreal middleWidth = 0; qreal space = 0; QVector dashes; switch (style) { case KoBorder::BorderNone: width = 0.0; break; case KoBorder::BorderDouble: innerWidth = space = edge.outerPen.width() / 3; //some nice default look width -= (space + innerWidth); edge.outerPen.setStyle(Qt::SolidLine); break; case KoBorder::BorderDotted: dashes << 1 << 1; edge.outerPen.setDashPattern(dashes); break; case KoBorder::BorderDashed: dashes << 4 << 1; edge.outerPen.setDashPattern(dashes); break; case KoBorder::BorderDashedLong: { dashes << 4 << 4; edge.outerPen.setDashPattern(dashes); break; } case KoBorder::BorderTriple: innerWidth = middleWidth = space = width/6; width -= (space + innerWidth); edge.outerPen.setStyle(Qt::SolidLine); break; case KoBorder::BorderDashDot: dashes << 3 << 3<< 7 << 3; edge.outerPen.setDashPattern(dashes); break; case KoBorder::BorderDashDotDot: dashes << 2 << 2<< 6 << 2 << 2 << 2; edge.outerPen.setDashPattern(dashes); break; case KoBorder::BorderWave: edge.outerPen.setStyle(Qt::SolidLine); break; case KoBorder::BorderSlash: edge.outerPen.setStyle(Qt::SolidLine); break; case KoBorder::BorderDoubleWave: innerWidth = space = width/3; //some nice default look width -= (space + innerWidth); edge.outerPen.setStyle(Qt::SolidLine); break; default: edge.outerPen.setStyle(Qt::SolidLine); break; } edge.outerPen.setJoinStyle(Qt::MiterJoin); edge.outerPen.setCapStyle(Qt::FlatCap); edge.outerPen.setWidthF(width); edge.spacing = space; edge.innerPen = edge.outerPen; edge.innerPen.setWidthF(innerWidth); } KoBorder::BorderStyle KoBorder::borderStyle(BorderSide side) const { if (!d->data.contains(side)) { return BorderNone; } else { return d->data[side].style; } } void KoBorder::setBorderColor(BorderSide side, const QColor &color) { if (!d->data.contains(side)) { BorderData data; data.outerPen.setColor(color); d->data[side] = data; } else { d->data[side].outerPen.setColor(color); } } QColor KoBorder::borderColor(BorderSide side) const { if (!d->data.contains(side)) { return QColor(); } else { return d->data[side].outerPen.color(); } } void KoBorder::setBorderWidth(BorderSide side, qreal width) { if (!d->data.contains(side)) { BorderData data; data.outerPen.setWidthF(width); d->data[side] = data; } else { d->data[side].outerPen.setWidthF(width); } } qreal KoBorder::borderWidth(BorderSide side) const { if (!d->data.contains(side)) { return 0; } else { if (d->data[side].style == BorderDouble) return (d->data[side].outerPen.widthF() + d->data[side].innerPen.widthF() + d->data[side].spacing); else return d->data[side].outerPen.widthF(); } } void KoBorder::setOuterBorderWidth(BorderSide side, qreal width) { if (!d->data.contains(side)) { BorderData data; data.outerPen.setWidthF(width); d->data[side] = data; } else { d->data[side].outerPen.setWidthF(width); } } qreal KoBorder::outerBorderWidth(BorderSide side) const { if (!d->data.contains(side)) { return 0; } else { return d->data[side].outerPen.widthF(); } } void KoBorder::setInnerBorderWidth(BorderSide side, qreal width) { if (!d->data.contains(side)) { BorderData data; data.innerPen.setWidthF(width); d->data[side] = data; } else { d->data[side].innerPen.setWidthF(width); } } qreal KoBorder::innerBorderWidth(BorderSide side) const { if (!d->data.contains(side)) { return 0; } else { return d->data[side].innerPen.widthF(); } } void KoBorder::setBorderSpacing(BorderSide side, qreal width) { if (!d->data.contains(side)) { BorderData data; data.spacing = width; d->data[side] = data; } else { d->data[side].spacing = width; } } qreal KoBorder::borderSpacing(BorderSide side) const { if (!d->data.contains(side)) { return 0; } else { return d->data[side].spacing; } } KoBorder::BorderData KoBorder::borderData(BorderSide side) const { return d->data.value(side, BorderData()); } void KoBorder::setBorderData(BorderSide side, const BorderData &data) { d->data[side] = data; } // ------------------------------- bool KoBorder::hasBorder() const { if (borderStyle(LeftBorder) != BorderNone && borderWidth(LeftBorder) > 0.0) return true; if (borderStyle(RightBorder) != BorderNone && borderWidth(RightBorder) > 0.0) return true; if (borderStyle(TopBorder) != BorderNone && borderWidth(TopBorder) > 0.0) return true; if (borderStyle(BottomBorder) != BorderNone && borderWidth(BottomBorder) > 0.0) return true; if (borderStyle(TlbrBorder) != BorderNone && borderWidth(TlbrBorder) > 0.0) return true; if (borderStyle(BltrBorder) != BorderNone && borderWidth(BltrBorder) > 0.0) return true; return false; } bool KoBorder::hasBorder(KoBorder::BorderSide side) const { return borderStyle(side) != BorderNone && borderWidth(side) > 0.0; } // ---------------------------------------------------------------- // painting void KoBorder::paint(QPainter &painter, const QRectF &borderRect, BorderPaintArea whereToPaint) const { Q_UNUSED(whereToPaint); // In tables it is apparently best practice to paint the // horizontal lines over the vertical ones. So let's use the same // strategy here. QPointF start; QPointF end; // FIXME: Make KoBorder store pointers to BorderData instead. This is very inefficient. BorderData leftEdge = borderData(KoBorder::LeftBorder); BorderData rightEdge = borderData(KoBorder::RightBorder); BorderData topEdge = borderData(KoBorder::TopBorder); BorderData bottomEdge = borderData(KoBorder::BottomBorder); // Left border if (hasBorder(LeftBorder)) { start = borderRect.topLeft(); end = borderRect.bottomLeft(); paintBorderSide(painter, start, end, &leftEdge, true, hasBorder(TopBorder) ? &topEdge : 0, hasBorder(BottomBorder) ? &bottomEdge : 0, 1); } // Right border if (hasBorder(RightBorder)) { start = borderRect.topRight(); end = borderRect.bottomRight(); paintBorderSide(painter, start, end, &rightEdge, true, hasBorder(TopBorder) ? &topEdge : 0, hasBorder(BottomBorder) ? &bottomEdge : 0, -1); } // Top border if (hasBorder(TopBorder)) { start = borderRect.topLeft(); end = borderRect.topRight(); paintBorderSide(painter, start, end, &topEdge, false, hasBorder(LeftBorder) ? &leftEdge : 0, hasBorder(RightBorder) ? &rightEdge : 0, 1); } // Bottom border if (hasBorder(BottomBorder)) { start = borderRect.bottomLeft(); end = borderRect.bottomRight(); paintBorderSide(painter, start, end, &bottomEdge, false, hasBorder(LeftBorder) ? &leftEdge : 0, hasBorder(RightBorder) ? &rightEdge : 0, -1); } // FIXME: Diagonal borders } void KoBorder::paintBorderSide(QPainter &painter, QPointF lineStart, QPointF lineEnd, BorderData *borderData, bool isVertical, BorderData *neighbour1, BorderData *neighbour2, int inwardsAcross) const { // Adjust the outer line so that it is inside the boundary. qreal displacement = borderData->outerPen.widthF() / qreal(2.0); if (isVertical) { lineStart.setX(lineStart.x() + inwardsAcross * displacement); lineEnd.setX(lineEnd.x() + inwardsAcross * displacement); } else { lineStart.setY(lineStart.y() + inwardsAcross * displacement); lineEnd.setY(lineEnd.y() + inwardsAcross * displacement); } painter.setPen(borderData->outerPen); painter.drawLine(lineStart, lineEnd); if (borderData->style == BorderDouble) { displacement = (borderData->outerPen.widthF() / qreal(2.0) + borderData->spacing + borderData->innerPen.widthF() / qreal(2.0)); if (isVertical) { lineStart.setX(lineStart.x() + inwardsAcross * displacement); lineEnd.setX(lineEnd.x() + inwardsAcross * displacement); } else { lineStart.setY(lineStart.y() + inwardsAcross * displacement); lineEnd.setY(lineEnd.y() + inwardsAcross * displacement); } - // Adjust for neigboring inner lines. + // Adjust for neighboring inner lines. if (neighbour1 && neighbour1->style == BorderDouble) { displacement = neighbour1->outerPen.widthF() + neighbour1->spacing; if (isVertical) { lineStart.setY(lineStart.y() + displacement); } else { lineStart.setX(lineStart.x() + displacement); } } if (neighbour2 && neighbour2->style == BorderDouble) { displacement = neighbour2->outerPen.widthF() + neighbour2->spacing; if (isVertical) { lineEnd.setY(lineEnd.y() - displacement); } else { lineEnd.setX(lineEnd.x() - displacement); } } // Draw the inner line. painter.setPen(borderData->innerPen); painter.drawLine(lineStart, lineEnd); } } // ---------------------------------------------------------------- // static functions void parseOdfBorder(const QString &border, QColor *color, KoBorder::BorderStyle *borderStyle, bool *hasBorderStyle, qreal *borderWidth, bool *hasBorderWidth) { *hasBorderStyle = false; *hasBorderWidth = false; if (!border.isEmpty() && border != "none" && border != "hidden") { QStringList borderData = border.split(' ', QString::SkipEmptyParts); if (borderData.length() > 0) { const QColor borderColor = QColor(borderData.last()); if (borderColor.isValid()) { *color = borderColor; borderData.removeLast(); } bool converted = false; const KoBorder::BorderStyle parsedBorderStyle = KoBorder::odfBorderStyle(borderData.last(), &converted); if (converted) { *hasBorderStyle = true; borderData.removeLast(); *borderStyle = parsedBorderStyle; } if (!borderData.isEmpty()) { const qreal parsedBorderWidth = KoUnit::parseValue(borderData[0], 1.0); *borderWidth = parsedBorderWidth; *hasBorderWidth = true; } } } } // ---------------------------------------------------------------- // load and save bool KoBorder::loadOdf(const KoXmlElement &style) { bool result = false; QString borderString; bool hasSpecialBorder; QString specialBorderString; if (style.hasAttributeNS(KoXmlNS::fo, "border")) { borderString = style.attributeNS(KoXmlNS::fo, "border"); if (borderString == "none") { // We use the "false" to indicate that there is no border // rather than that the parsing has failed. return false; } result = true; if ((hasSpecialBorder = style.hasAttributeNS(KoXmlNS::calligra, "specialborder"))) { specialBorderString = style.attributeNS(KoXmlNS::calligra, "specialborder"); } parseAndSetBorder(borderString, hasSpecialBorder, specialBorderString); } else { // No common border attributes, check for the individual ones. if (style.hasAttributeNS(KoXmlNS::fo, "border-left")) { result = true; borderString = style.attributeNS(KoXmlNS::fo, "border-left"); if ((hasSpecialBorder = style.hasAttributeNS(KoXmlNS::calligra, "specialborder-left"))) { specialBorderString = style.attributeNS(KoXmlNS::calligra, "specialborder-left"); } parseAndSetBorder(LeftBorder, borderString, hasSpecialBorder, specialBorderString); } if (style.hasAttributeNS(KoXmlNS::fo, "border-top")) { result = true; borderString = style.attributeNS(KoXmlNS::fo, "border-top"); if ((hasSpecialBorder = style.hasAttributeNS(KoXmlNS::calligra, "specialborder-top"))) { specialBorderString = style.attributeNS(KoXmlNS::calligra, "specialborder-top"); } parseAndSetBorder(TopBorder, borderString, hasSpecialBorder, specialBorderString); } if (style.hasAttributeNS(KoXmlNS::fo, "border-right")) { result = true; borderString = style.attributeNS(KoXmlNS::fo, "border-right"); if ((hasSpecialBorder = style.hasAttributeNS(KoXmlNS::calligra, "specialborder-right"))) { specialBorderString = style.attributeNS(KoXmlNS::calligra, "specialborder-right"); } parseAndSetBorder(RightBorder, borderString, hasSpecialBorder, specialBorderString); } if (style.hasAttributeNS(KoXmlNS::fo, "border-bottom")) { result = true; borderString = style.attributeNS(KoXmlNS::fo, "border-bottom"); if ((hasSpecialBorder = style.hasAttributeNS(KoXmlNS::calligra, "specialborder-bottom"))) { specialBorderString = style.attributeNS(KoXmlNS::calligra, "specialborder-bottom"); } parseAndSetBorder(BottomBorder, borderString, hasSpecialBorder, specialBorderString); } } // Diagonals are treated individually and are NOT part of . if (style.hasAttributeNS(KoXmlNS::style, "diagonal-tl-br")) { result = true; borderString = style.attributeNS(KoXmlNS::fo, "border-tl-br"); if ((hasSpecialBorder = style.hasAttributeNS(KoXmlNS::calligra, "specialborder-tl-br"))) { specialBorderString = style.attributeNS(KoXmlNS::calligra, "specialborder-tl-br"); } parseAndSetBorder(TlbrBorder, borderString, hasSpecialBorder, specialBorderString); } if (style.hasAttributeNS(KoXmlNS::style, "diagonal-bl-tr")) { result = true; borderString = style.attributeNS(KoXmlNS::fo, "border-bl-tr"); if ((hasSpecialBorder = style.hasAttributeNS(KoXmlNS::calligra, "specialborder-bl-tr"))) { specialBorderString = style.attributeNS(KoXmlNS::calligra, "specialborder-bl-tr"); } parseAndSetBorder(BltrBorder, borderString, hasSpecialBorder, specialBorderString); } // Handle double borders. if (style.hasAttributeNS(KoXmlNS::style, "border-line-width")) { result = true; QString borderLineWidth = style.attributeNS(KoXmlNS::style, "border-line-width"); if (!borderLineWidth.isEmpty() && borderLineWidth != "none" && borderLineWidth != "hidden") { QStringList blw = borderLineWidth.split(' ', QString::SkipEmptyParts); setInnerBorderWidth(LeftBorder, KoUnit::parseValue(blw[0], 0.1)); setBorderSpacing(LeftBorder, KoUnit::parseValue(blw[1], 1.0)); setBorderWidth(LeftBorder, KoUnit::parseValue(blw[2], 0.1)); setInnerBorderWidth(TopBorder, KoUnit::parseValue(blw[0], 0.1)); setBorderSpacing(TopBorder, KoUnit::parseValue(blw[1], 1.0)); setBorderWidth(TopBorder, KoUnit::parseValue(blw[2], 0.1)); setInnerBorderWidth(RightBorder, KoUnit::parseValue(blw[0], 0.1)); setBorderSpacing(RightBorder, KoUnit::parseValue(blw[1], 1.0)); setBorderWidth(RightBorder, KoUnit::parseValue(blw[2], 0.1)); setInnerBorderWidth(BottomBorder, KoUnit::parseValue(blw[0], 0.1)); setBorderSpacing(BottomBorder, KoUnit::parseValue(blw[1], 1.0)); setBorderWidth(BottomBorder, KoUnit::parseValue(blw[2], 0.1)); } } else { if (style.hasAttributeNS(KoXmlNS::style, "border-line-width-left")) { result = true; QString borderLineWidth = style.attributeNS(KoXmlNS::style, "border-line-width-left"); if (!borderLineWidth.isEmpty() && borderLineWidth != "none" && borderLineWidth != "hidden") { QStringList blw = borderLineWidth.split(' ', QString::SkipEmptyParts); setInnerBorderWidth(LeftBorder, KoUnit::parseValue(blw[0], 0.1)); setBorderSpacing(LeftBorder, KoUnit::parseValue(blw[1], 1.0)); setBorderWidth(LeftBorder, KoUnit::parseValue(blw[2], 0.1)); } } if (style.hasAttributeNS(KoXmlNS::style, "border-line-width-top")) { result = true; QString borderLineWidth = style.attributeNS(KoXmlNS::style, "border-line-width-top"); if (!borderLineWidth.isEmpty() && borderLineWidth != "none" && borderLineWidth != "hidden") { QStringList blw = borderLineWidth.split(' ', QString::SkipEmptyParts); setInnerBorderWidth(TopBorder, KoUnit::parseValue(blw[0], 0.1)); setBorderSpacing(TopBorder, KoUnit::parseValue(blw[1], 1.0)); setBorderWidth(TopBorder, KoUnit::parseValue(blw[2], 0.1)); } } if (style.hasAttributeNS(KoXmlNS::style, "border-line-width-right")) { result = true; QString borderLineWidth = style.attributeNS(KoXmlNS::style, "border-line-width-right"); if (!borderLineWidth.isEmpty() && borderLineWidth != "none" && borderLineWidth != "hidden") { QStringList blw = borderLineWidth.split(' ', QString::SkipEmptyParts); setInnerBorderWidth(RightBorder, KoUnit::parseValue(blw[0], 0.1)); setBorderSpacing(RightBorder, KoUnit::parseValue(blw[1], 1.0)); setBorderWidth(RightBorder, KoUnit::parseValue(blw[2], 0.1)); } } if (style.hasAttributeNS(KoXmlNS::style, "border-line-width-bottom")) { result = true; QString borderLineWidth = style.attributeNS(KoXmlNS::style, "border-line-width-bottom"); if (!borderLineWidth.isEmpty() && borderLineWidth != "none" && borderLineWidth != "hidden") { QStringList blw = borderLineWidth.split(' ', QString::SkipEmptyParts); setInnerBorderWidth(BottomBorder, KoUnit::parseValue(blw[0], 0.1)); setBorderSpacing(BottomBorder, KoUnit::parseValue(blw[1], 1.0)); setBorderWidth(BottomBorder, KoUnit::parseValue(blw[2], 0.1)); } } } if (style.hasAttributeNS(KoXmlNS::style, "diagonal-tl-br-widths")) { result = true; QString borderLineWidth = style.attributeNS(KoXmlNS::style, "diagonal-tl-br-widths"); if (!borderLineWidth.isEmpty() && borderLineWidth != "none" && borderLineWidth != "hidden") { QStringList blw = borderLineWidth.split(' ', QString::SkipEmptyParts); setInnerBorderWidth(TlbrBorder, KoUnit::parseValue(blw[0], 0.1)); setBorderSpacing(TlbrBorder, KoUnit::parseValue(blw[1], 1.0)); setBorderWidth(TlbrBorder, KoUnit::parseValue(blw[2], 0.1)); } } if (style.hasAttributeNS(KoXmlNS::style, "diagonal-bl-tr-widths")) { result = true; QString borderLineWidth = style.attributeNS(KoXmlNS::style, "diagonal-bl-tr-widths"); if (!borderLineWidth.isEmpty() && borderLineWidth != "none" && borderLineWidth != "hidden") { QStringList blw = borderLineWidth.split(' ', QString::SkipEmptyParts); setInnerBorderWidth(BltrBorder, KoUnit::parseValue(blw[0], 0.1)); setBorderSpacing(BltrBorder, KoUnit::parseValue(blw[1], 1.0)); setBorderWidth(BltrBorder, KoUnit::parseValue(blw[2], 0.1)); } } return result; } bool KoBorder::loadOdf(const KoStyleStack &styleStack) { bool result = false; QString borderString; bool hasSpecialBorder; QString specialBorderString; if (styleStack.hasProperty(KoXmlNS::fo, "border")) { result = true; borderString = styleStack.property(KoXmlNS::fo, "border"); if ((hasSpecialBorder = styleStack.hasProperty(KoXmlNS::calligra, "specialborder"))) { specialBorderString = styleStack.property(KoXmlNS::calligra, "specialborder"); } parseAndSetBorder(borderString, hasSpecialBorder, specialBorderString); } // Even if there are common border attributes, check for the // individual ones since they have precedence. if (styleStack.hasProperty(KoXmlNS::fo, "border-left")) { result = true; borderString = styleStack.property(KoXmlNS::fo, "border-left"); if ((hasSpecialBorder = styleStack.hasProperty(KoXmlNS::calligra, "specialborder-left"))) { specialBorderString = styleStack.property(KoXmlNS::calligra, "specialborder-left"); } parseAndSetBorder(LeftBorder, borderString, hasSpecialBorder, specialBorderString); } if (styleStack.hasProperty(KoXmlNS::fo, "border-top")) { result = true; borderString = styleStack.property(KoXmlNS::fo, "border-top"); if ((hasSpecialBorder = styleStack.hasProperty(KoXmlNS::calligra, "specialborder-top"))) { specialBorderString = styleStack.property(KoXmlNS::calligra, "specialborder-top"); } parseAndSetBorder(TopBorder, borderString, hasSpecialBorder, specialBorderString); } if (styleStack.hasProperty(KoXmlNS::fo, "border-right")) { result = true; borderString = styleStack.property(KoXmlNS::fo, "border-right"); if ((hasSpecialBorder = styleStack.hasProperty(KoXmlNS::calligra, "specialborder-right"))) { specialBorderString = styleStack.property(KoXmlNS::calligra, "specialborder-right"); } parseAndSetBorder(RightBorder, borderString, hasSpecialBorder, specialBorderString); } if (styleStack.hasProperty(KoXmlNS::fo, "border-bottom")) { result = true; borderString = styleStack.property(KoXmlNS::fo, "border-bottom"); if ((hasSpecialBorder = styleStack.hasProperty(KoXmlNS::calligra, "specialborder-bottom"))) { specialBorderString = styleStack.property(KoXmlNS::calligra, "specialborder-bottom"); } parseAndSetBorder(BottomBorder, borderString, hasSpecialBorder, specialBorderString); } // Diagonals are treated individually and are NOT part of . if (styleStack.hasProperty(KoXmlNS::style, "diagonal-tl-br")) { result = true; borderString = styleStack.property(KoXmlNS::fo, "border-tl-br"); if ((hasSpecialBorder = styleStack.hasProperty(KoXmlNS::calligra, "specialborder-tl-br"))) { specialBorderString = styleStack.property(KoXmlNS::calligra, "specialborder-tl-br"); } parseAndSetBorder(TlbrBorder, borderString, hasSpecialBorder, specialBorderString); } if (styleStack.hasProperty(KoXmlNS::style, "diagonal-bl-tr")) { result = true; borderString = styleStack.property(KoXmlNS::fo, "border-bl-tr"); if ((hasSpecialBorder = styleStack.hasProperty(KoXmlNS::calligra, "specialborder-bl-tr"))) { specialBorderString = styleStack.property(KoXmlNS::calligra, "specialborder-bl-tr"); } parseAndSetBorder(BltrBorder, borderString, hasSpecialBorder, specialBorderString); } // Handle double borders. if (styleStack.hasProperty(KoXmlNS::style, "border-line-width")) { result = true; QString borderLineWidth = styleStack.property(KoXmlNS::style, "border-line-width"); if (!borderLineWidth.isEmpty() && borderLineWidth != "none" && borderLineWidth != "hidden") { QStringList blw = borderLineWidth.split(' ', QString::SkipEmptyParts); setInnerBorderWidth(LeftBorder, KoUnit::parseValue(blw[0], 0.1)); setBorderSpacing(LeftBorder, KoUnit::parseValue(blw[1], 1.0)); setBorderWidth(LeftBorder, KoUnit::parseValue(blw[2], 0.1)); setInnerBorderWidth(TopBorder, KoUnit::parseValue(blw[0], 0.1)); setBorderSpacing(TopBorder, KoUnit::parseValue(blw[1], 1.0)); setBorderWidth(TopBorder, KoUnit::parseValue(blw[2], 0.1)); setInnerBorderWidth(RightBorder, KoUnit::parseValue(blw[0], 0.1)); setBorderSpacing(RightBorder, KoUnit::parseValue(blw[1], 1.0)); setBorderWidth(RightBorder, KoUnit::parseValue(blw[2], 0.1)); setInnerBorderWidth(BottomBorder, KoUnit::parseValue(blw[0], 0.1)); setBorderSpacing(BottomBorder, KoUnit::parseValue(blw[1], 1.0)); setBorderWidth(BottomBorder, KoUnit::parseValue(blw[2], 0.1)); } } // Even if there are common border attributes, check for the // individual ones since they have precedence. if (styleStack.hasProperty(KoXmlNS::style, "border-line-width-left")) { result = true; QString borderLineWidth = styleStack.property(KoXmlNS::style, "border-line-width-left"); if (!borderLineWidth.isEmpty() && borderLineWidth != "none" && borderLineWidth != "hidden") { QStringList blw = borderLineWidth.split(' ', QString::SkipEmptyParts); setInnerBorderWidth(LeftBorder, KoUnit::parseValue(blw[0], 0.1)); setBorderSpacing(LeftBorder, KoUnit::parseValue(blw[1], 1.0)); setBorderWidth(LeftBorder, KoUnit::parseValue(blw[2], 0.1)); } } if (styleStack.hasProperty(KoXmlNS::style, "border-line-width-top")) { result = true; QString borderLineWidth = styleStack.property(KoXmlNS::style, "border-line-width-top"); if (!borderLineWidth.isEmpty() && borderLineWidth != "none" && borderLineWidth != "hidden") { QStringList blw = borderLineWidth.split(' ', QString::SkipEmptyParts); setInnerBorderWidth(TopBorder, KoUnit::parseValue(blw[0], 0.1)); setBorderSpacing(TopBorder, KoUnit::parseValue(blw[1], 1.0)); setBorderWidth(TopBorder, KoUnit::parseValue(blw[2], 0.1)); } } if (styleStack.hasProperty(KoXmlNS::style, "border-line-width-right")) { result = true; QString borderLineWidth = styleStack.property(KoXmlNS::style, "border-line-width-right"); if (!borderLineWidth.isEmpty() && borderLineWidth != "none" && borderLineWidth != "hidden") { QStringList blw = borderLineWidth.split(' ', QString::SkipEmptyParts); setInnerBorderWidth(RightBorder, KoUnit::parseValue(blw[0], 0.1)); setBorderSpacing(RightBorder, KoUnit::parseValue(blw[1], 1.0)); setBorderWidth(RightBorder, KoUnit::parseValue(blw[2], 0.1)); } } if (styleStack.hasProperty(KoXmlNS::style, "border-line-width-bottom")) { result = true; QString borderLineWidth = styleStack.property(KoXmlNS::style, "border-line-width-bottom"); if (!borderLineWidth.isEmpty() && borderLineWidth != "none" && borderLineWidth != "hidden") { QStringList blw = borderLineWidth.split(' ', QString::SkipEmptyParts); setInnerBorderWidth(BottomBorder, KoUnit::parseValue(blw[0], 0.1)); setBorderSpacing(BottomBorder, KoUnit::parseValue(blw[1], 1.0)); setBorderWidth(BottomBorder, KoUnit::parseValue(blw[2], 0.1)); } } // Diagonals are treated individually and are NOT part of . if (styleStack.hasProperty(KoXmlNS::style, "diagonal-tl-br-widths")) { result = true; QString borderLineWidth = styleStack.property(KoXmlNS::style, "diagonal-tl-br-widths"); if (!borderLineWidth.isEmpty() && borderLineWidth != "none" && borderLineWidth != "hidden") { QStringList blw = borderLineWidth.split(' ', QString::SkipEmptyParts); setInnerBorderWidth(TlbrBorder, KoUnit::parseValue(blw[0], 0.1)); setBorderSpacing(TlbrBorder, KoUnit::parseValue(blw[1], 1.0)); setBorderWidth(TlbrBorder, KoUnit::parseValue(blw[2], 0.1)); } } if (styleStack.hasProperty(KoXmlNS::style, "diagonal-bl-tr-widths")) { result = true; QString borderLineWidth = styleStack.property(KoXmlNS::style, "diagonal-bl-tr-widths"); if (!borderLineWidth.isEmpty() && borderLineWidth != "none" && borderLineWidth != "hidden") { QStringList blw = borderLineWidth.split(' ', QString::SkipEmptyParts); setInnerBorderWidth(BltrBorder, KoUnit::parseValue(blw[0], 0.1)); setBorderSpacing(BltrBorder, KoUnit::parseValue(blw[1], 1.0)); setBorderWidth(BltrBorder, KoUnit::parseValue(blw[2], 0.1)); } } return result; } // Private void KoBorder::parseAndSetBorder(const QString &borderString, bool hasSpecialBorder, const QString &specialBorderString) { if (borderString == "none") { return; } //debugOdf << "*** *** Found border: " << border; QColor bordersColor; BorderStyle bordersStyle; qreal bordersWidth; bool foundStyle; bool foundWidth; parseOdfBorder(borderString, &bordersColor, &bordersStyle, &foundStyle, &bordersWidth, &foundWidth); if (bordersColor.isValid()) { setBorderColor(LeftBorder, bordersColor); setBorderColor(TopBorder, bordersColor); setBorderColor(RightBorder, bordersColor); setBorderColor(BottomBorder, bordersColor); } if (hasSpecialBorder) { bordersStyle = KoBorder::odfBorderStyle(specialBorderString, &foundStyle); } if (foundStyle) { setBorderStyle(LeftBorder, bordersStyle); setBorderStyle(TopBorder, bordersStyle); setBorderStyle(RightBorder, bordersStyle); setBorderStyle(BottomBorder, bordersStyle); } if (foundWidth) { setBorderWidth(LeftBorder, bordersWidth); setBorderWidth(TopBorder, bordersWidth); setBorderWidth(RightBorder, bordersWidth); setBorderWidth(BottomBorder, bordersWidth); } } // Private void KoBorder::parseAndSetBorder(const BorderSide borderSide, const QString &borderString, bool hasSpecialBorder, const QString &specialBorderString) { QColor borderColor; BorderStyle borderStyle; qreal borderWidth; bool foundStyle; bool foundWidth; parseOdfBorder(borderString, &borderColor, &borderStyle, &foundStyle, &borderWidth, &foundWidth); if (borderColor.isValid()) { setBorderColor(borderSide, borderColor); } if (hasSpecialBorder) { borderStyle = KoBorder::odfBorderStyle(specialBorderString, &foundStyle); } if (foundStyle) { setBorderStyle( borderSide, borderStyle); } if (foundWidth) { setBorderWidth( borderSide, borderWidth); } } void KoBorder::saveOdf(KoGenStyle &style, KoGenStyle::PropertyType type) const { // Get the strings that describe respective borders. QString leftBorderString = QString("%1pt %2 %3") .arg(QString::number(borderWidth(LeftBorder)), odfBorderStyleString(borderStyle(LeftBorder)), borderColor(LeftBorder).name()); QString rightBorderString = QString("%1pt %2 %3") .arg(QString::number(borderWidth(RightBorder)), odfBorderStyleString(borderStyle(RightBorder)), borderColor(RightBorder).name()); QString topBorderString = QString("%1pt %2 %3") .arg(QString::number(borderWidth(TopBorder)), odfBorderStyleString(borderStyle(TopBorder)), borderColor(TopBorder).name()); QString bottomBorderString = QString("%1pt %2 %3") .arg(QString::number(borderWidth(BottomBorder)), odfBorderStyleString(borderStyle(BottomBorder)), borderColor(BottomBorder).name()); QString tlbrBorderString = QString("%1pt %2 %3") .arg(QString::number(borderWidth(TlbrBorder)), odfBorderStyleString(borderStyle(TlbrBorder)), borderColor(TlbrBorder).name()); QString trblBorderString = QString("%1pt %2 %3") .arg(QString::number(borderWidth(BltrBorder)), odfBorderStyleString(borderStyle(BltrBorder)), borderColor(BltrBorder).name()); // Get the strings that describe respective special borders (for special mso support). QString leftBorderSpecialString = msoBorderStyleString(borderStyle(LeftBorder)); QString rightBorderSpecialString = msoBorderStyleString(borderStyle(RightBorder)); QString topBorderSpecialString = msoBorderStyleString(borderStyle(TopBorder)); QString bottomBorderSpecialString = msoBorderStyleString(borderStyle(BottomBorder)); QString tlbrBorderSpecialString = msoBorderStyleString(borderStyle(TlbrBorder)); QString trblBorderSpecialString = msoBorderStyleString(borderStyle(BltrBorder)); // Check if we can save all borders in one fo:border attribute, or // if we have to use several different ones like fo:border-left, etc. if (leftBorderString == rightBorderString && leftBorderString == topBorderString && leftBorderString == bottomBorderString) { // Yes, they were all the same, so use only fo:border style.addProperty("fo:border", leftBorderString, type); style.addProperty("calligra:specialborder-left", leftBorderSpecialString, type); style.addProperty("calligra:specialborder-right", rightBorderSpecialString, type); style.addProperty("calligra:specialborder-top", topBorderSpecialString, type); style.addProperty("calligra:specialborder-bottom", bottomBorderSpecialString, type); } else { // No, they were different, so use the individual borders. //if (leftBorderStyle() != BorderNone) style.addProperty("fo:border-left", leftBorderString, type); style.addProperty("calligra:specialborder-left", leftBorderSpecialString, type); //if (rightBorderStyle() != BorderNone) style.addProperty("fo:border-right", rightBorderString, type); style.addProperty("calligra:specialborder-right", rightBorderSpecialString, type); //if (topBorderStyle() != BorderNone) style.addProperty("fo:border-top", topBorderString, type); style.addProperty("calligra:specialborder-top", topBorderSpecialString, type); //if (bottomBorderStyle() != BorderNone) style.addProperty("fo:border-bottom", bottomBorderString, type); style.addProperty("calligra:specialborder-bottom", bottomBorderSpecialString, type); } if (style.type() != KoGenStyle::PageLayoutStyle) { //if (tlbrBorderStyle() != BorderNone) { style.addProperty("style:diagonal-tl-br", tlbrBorderString, type); //} //if (trblBorderStyle() != BorderNone) { style.addProperty("style:diagonal-bl-tr", trblBorderString, type); //} } // Handle double borders QString leftBorderLineWidth = QString("%1pt %2pt %3pt") .arg(QString::number(innerBorderWidth(LeftBorder)), QString::number(borderSpacing(LeftBorder)), QString::number(borderWidth(LeftBorder))); QString rightBorderLineWidth = QString("%1pt %2pt %3pt") .arg(QString::number(innerBorderWidth(RightBorder)), QString::number(borderSpacing(RightBorder)), QString::number(borderWidth(RightBorder))); QString topBorderLineWidth = QString("%1pt %2pt %3pt") .arg(QString::number(innerBorderWidth(TopBorder)), QString::number(borderSpacing(TopBorder)), QString::number(borderWidth(TopBorder))); QString bottomBorderLineWidth = QString("%1pt %2pt %3pt") .arg(QString::number(innerBorderWidth(BottomBorder)), QString::number(borderSpacing(BottomBorder)), QString::number(borderWidth(BottomBorder))); QString tlbrBorderLineWidth = QString("%1pt %2pt %3pt") .arg(QString::number(innerBorderWidth(TlbrBorder)), QString::number(borderSpacing(TlbrBorder)), QString::number(borderWidth(TlbrBorder))); QString trblBorderLineWidth = QString("%1pt %2pt %3pt") .arg(QString::number(innerBorderWidth(BltrBorder)), QString::number(borderSpacing(BltrBorder)), QString::number(borderWidth(BltrBorder))); if (leftBorderLineWidth == rightBorderLineWidth && leftBorderLineWidth == topBorderLineWidth && leftBorderLineWidth == bottomBorderLineWidth && borderStyle(LeftBorder) == borderStyle(RightBorder) && borderStyle(TopBorder) == borderStyle(BottomBorder) && borderStyle(TopBorder) == borderStyle(LeftBorder) && (borderStyle(LeftBorder) == BorderDouble || borderStyle(LeftBorder) == BorderDoubleWave)) { style.addProperty("style:border-line-width", leftBorderLineWidth, type); } else { if (borderStyle(LeftBorder) == BorderDouble || borderStyle(LeftBorder) == BorderDoubleWave) style.addProperty("style:border-line-width-left", leftBorderLineWidth, type); if (borderStyle(RightBorder) == BorderDouble || borderStyle(RightBorder) == BorderDoubleWave) style.addProperty("style:border-line-width-right", rightBorderLineWidth, type); if (borderStyle(TopBorder) == BorderDouble || borderStyle(TopBorder) == BorderDoubleWave) style.addProperty("style:border-line-width-top", topBorderLineWidth, type); if (borderStyle(BottomBorder) == BorderDouble || borderStyle(BottomBorder) == BorderDoubleWave) style.addProperty("style:border-line-width-bottom", bottomBorderLineWidth, type); } if (style.type() != KoGenStyle::PageLayoutStyle) { if (borderStyle(TlbrBorder) == BorderDouble || borderStyle(TlbrBorder) == BorderDoubleWave) { style.addProperty("style:diagonal-tl-br-widths", tlbrBorderLineWidth, type); } if (borderStyle(BltrBorder) == BorderDouble || borderStyle(BltrBorder) == BorderDoubleWave) { style.addProperty("style:diagonal-bl-tr-widths", trblBorderLineWidth, type); } } } diff --git a/libs/odf/KoOdfBibliographyConfiguration.h b/libs/odf/KoOdfBibliographyConfiguration.h index a5e92e9c18..d7dca92020 100644 --- a/libs/odf/KoOdfBibliographyConfiguration.h +++ b/libs/odf/KoOdfBibliographyConfiguration.h @@ -1,116 +1,116 @@ /* This file is part of the KDE project * Copyright (C) 2011 Smit Patel * * 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 KOODFBIBLIOGRAPHYCONFIGURATION_H #define KOODFBIBLIOGRAPHYCONFIGURATION_H #include #include #include #include "KoXmlReaderForward.h" #include "kritaodf_export.h" class KoXmlWriter; typedef QPair SortKeyPair; /** * load and save the bibliography-configuration element from the text: namespace. * • Prefix * • Suffix * • Numbered entries * • Sort by position * • Sort algorithm */ class KRITAODF_EXPORT KoOdfBibliographyConfiguration : public QObject { Q_OBJECT public: KoOdfBibliographyConfiguration(); ~KoOdfBibliographyConfiguration() override; KoOdfBibliographyConfiguration(const KoOdfBibliographyConfiguration &other); KoOdfBibliographyConfiguration &operator=(const KoOdfBibliographyConfiguration &other); static const QList bibTypes; static const QList bibDataFields; /** * load the bibliography-configuration element */ void loadOdf(const KoXmlElement &element); /** * save the bibliography-configuration element */ void saveOdf(KoXmlWriter * writer) const; /** * Sort by position * If text:sort-by-position attribute is true then bibliography entries or citations will be * sorted according to their position in document. */ bool sortByPosition() const; void setSortByPosition(bool enable); /** - * Numberred entries + * Numbered entries * If text:numbered-entries attribute is true then bibliography entries or citations will be numbered. */ bool numberedEntries() const; void setNumberedEntries(bool enable); /** * Prefix * The text:prefix attribute specifies prefix of bibliography entry or citation(text:bibliography-mark) */ QString prefix() const; void setPrefix(const QString &prefixValue); /** * Suffix * The text:suffix attribute specifies suffix of bibliography entry or citation(text:bibliography-mark) */ QString suffix() const; void setSuffix(const QString &suffixValue); /** * Sort algorithm * The text:sort-algorithm attribute specifies sorting algorithm for bibliography entry */ QString sortAlgorithm() const; void setSortAlgorithm(const QString &algorithm); /** * Sort Keys * The text:sort-key attribute specifies sort key for bibliography entry */ QList sortKeys() const; void setSortKeys(const QList &sortKeys); private: class Private; Private * const d; }; Q_DECLARE_METATYPE(KoOdfBibliographyConfiguration*) #endif // KOODFBIBLIOGRAPHYCONFIGURATION_H diff --git a/libs/pigment/KoBasicHistogramProducers.h b/libs/pigment/KoBasicHistogramProducers.h index 3f695ccdb7..5509176705 100644 --- a/libs/pigment/KoBasicHistogramProducers.h +++ b/libs/pigment/KoBasicHistogramProducers.h @@ -1,267 +1,267 @@ /* * Copyright (c) 2005 Bart Coppens * * 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 program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _Ko_BASIC_HISTOGRAM_PRODUCERS_ #define _Ko_BASIC_HISTOGRAM_PRODUCERS_ #include "KoHistogramProducer.h" #include #include #include "KoColorSpace.h" #include "KoID.h" #include "kritapigment_export.h" #include "KoColorSpaceRegistry.h" class KRITAPIGMENT_EXPORT KoBasicHistogramProducer : public KoHistogramProducer { public: explicit KoBasicHistogramProducer(const KoID& id, int channelCount, int nrOfBins); explicit KoBasicHistogramProducer(const KoID& id, int nrOfBins, const KoColorSpace *colorSpace); ~KoBasicHistogramProducer() override {} void clear() override; void setView(qreal from, qreal size) override { m_from = from; m_width = size; } const KoID& id() const override { return m_id; } QList channels() override { return m_colorSpace->channels(); } qint32 numberOfBins() override { return m_nrOfBins; } qreal viewFrom() const override { return m_from; } qreal viewWidth() const override { return m_width; } qint32 count() override { return m_count; } qint32 getBinAt(int channel, int position) override { return m_bins.at(externalToInternal(channel)).at(position); } qint32 outOfViewLeft(int channel) override { return m_outLeft.at(externalToInternal(channel)); } qint32 outOfViewRight(int channel) override { return m_outRight.at(externalToInternal(channel)); } protected: /** * The order in which channels() returns is not the same as the internal representation, * that of the pixel internally. This method converts external usage to internal usage. - * This method uses some basic assumtpions about the layout of the pixel, so _extremely_ + * This method uses some basic assumptions about the layout of the pixel, so _extremely_ * exotic spaces might want to override this (see makeExternalToInternal source for * those assumptions) **/ virtual int externalToInternal(int ext) { if (channels().count() > 0 && m_external.count() == 0) // Set up the translation table makeExternalToInternal(); return m_external.at(ext); } // not virtual since that is useless: we call it from constructor void makeExternalToInternal(); typedef QVector vBins; QVector m_bins; vBins m_outLeft, m_outRight; qreal m_from, m_width; qint32 m_count; int m_channels, m_nrOfBins; const KoColorSpace *m_colorSpace; KoID m_id; QVector m_external; }; class KRITAPIGMENT_EXPORT KoBasicU8HistogramProducer : public KoBasicHistogramProducer { public: KoBasicU8HistogramProducer(const KoID& id, const KoColorSpace *colorSpace); ~KoBasicU8HistogramProducer() override {} void addRegionToBin(const quint8 * pixels, const quint8 * selectionMask, quint32 nPixels, const KoColorSpace *colorSpace) override; QString positionToString(qreal pos) const override; qreal maximalZoom() const override { return 1.0; } }; class KRITAPIGMENT_EXPORT KoBasicU16HistogramProducer : public KoBasicHistogramProducer { public: KoBasicU16HistogramProducer(const KoID& id, const KoColorSpace *colorSpace); ~KoBasicU16HistogramProducer() override {} void addRegionToBin(const quint8 * pixels, const quint8 * selectionMask, quint32 nPixels, const KoColorSpace *colorSpace) override; QString positionToString(qreal pos) const override; qreal maximalZoom() const override; }; class KRITAPIGMENT_EXPORT KoBasicF32HistogramProducer : public KoBasicHistogramProducer { public: KoBasicF32HistogramProducer(const KoID& id, const KoColorSpace *colorSpace); ~KoBasicF32HistogramProducer() override {} void addRegionToBin(const quint8 * pixels, const quint8 * selectionMask, quint32 nPixels, const KoColorSpace *colorSpace) override; QString positionToString(qreal pos) const override; qreal maximalZoom() const override; }; #ifdef HAVE_OPENEXR class KRITAPIGMENT_EXPORT KoBasicF16HalfHistogramProducer : public KoBasicHistogramProducer { public: KoBasicF16HalfHistogramProducer(const KoID& id, const KoColorSpace *colorSpace); ~KoBasicF16HalfHistogramProducer() override {} void addRegionToBin(const quint8 * pixels, const quint8 * selectionMask, quint32 nPixels, const KoColorSpace *colorSpace) override; QString positionToString(qreal pos) const override; qreal maximalZoom() const override; }; #endif /** * Parametrized on a specific KoHistogramProducer. Its generated producers * will have the same KoID as the factory's. This is acceptable because we can't mix * Factories with Producers in the code because they are incompatible types, and * in the GUI we actually only need a producer's name, not a factory's. */ template class KoBasicHistogramProducerFactory : public KoHistogramProducerFactory { public: KoBasicHistogramProducerFactory(const KoID& id, const QString& modelId, const QString& depthId ) : KoHistogramProducerFactory(id), m_modelId(modelId), m_depthId(depthId) { } ~KoBasicHistogramProducerFactory() override {} KoHistogramProducer *generate() override { KoHistogramProducer *producer = 0; const KoColorSpace *cs = KoColorSpaceRegistry::instance()->colorSpace(m_modelId, m_depthId, 0); if (cs) { producer = new T(KoID(id(), name()), cs); } return producer; } bool isCompatibleWith(const KoColorSpace* colorSpace, bool strict = false) const override { if( strict ){ return colorSpace->colorDepthId().id() == m_depthId; } return colorSpace->colorModelId().id() == m_modelId || colorSpace->colorDepthId().id() == m_depthId; } float preferrednessLevelWith(const KoColorSpace* colorSpace) const override { return 0.5 * ( (colorSpace->colorModelId().id() == m_modelId) + (colorSpace->colorDepthId().id() == m_depthId) ); } protected: QString m_modelId, m_depthId; }; /** * This is a Producer (with associated factory) that converts the pixels of the colorspace * to RGB8 with toQColor, and then does its counting on RGB. This is NOT registered with the * Registry, because it isCompatibleWith all colorspaces, and should only be used in extreme * cases (like no other producer being available **/ class KRITAPIGMENT_EXPORT KoGenericRGBHistogramProducer : public KoBasicHistogramProducer { public: KoGenericRGBHistogramProducer(); ~KoGenericRGBHistogramProducer() override {} void addRegionToBin(const quint8 * pixels, const quint8 * selectionMask, quint32 nPixels, const KoColorSpace *colorSpace) override; QString positionToString(qreal pos) const override; qreal maximalZoom() const override; QList channels() override; protected: QList m_channelsList; }; /** KoGenericRGBHistogramProducer his special Factory that isCompatibleWith everything. */ class KRITAPIGMENT_EXPORT KoGenericRGBHistogramProducerFactory : public KoHistogramProducerFactory { public: KoGenericRGBHistogramProducerFactory(); ~KoGenericRGBHistogramProducerFactory() override {} KoHistogramProducer *generate() override { return new KoGenericRGBHistogramProducer(); } bool isCompatibleWith(const KoColorSpace*, bool strict = false) const override { Q_UNUSED(strict); return true; } float preferrednessLevelWith(const KoColorSpace*) const override { return 0.0; } }; /** * This is a Producer (with associated factory) that converts the pixels of the colorspace * to L*a*b*, and then does its counting. * It isCompatibleWith all colorspaces **/ class KRITAPIGMENT_EXPORT KoGenericLabHistogramProducer : public KoBasicHistogramProducer { public: KoGenericLabHistogramProducer(); ~KoGenericLabHistogramProducer() override; void addRegionToBin(const quint8 * pixels, const quint8 * selectionMask, quint32 nPixels, const KoColorSpace *colorSpace) override; QString positionToString(qreal pos) const override; qreal maximalZoom() const override; QList channels() override; protected: QList m_channelsList; }; /** KoGenericLabHistogramProducer his special Factory that isCompatibleWith everything. */ class /*KRITAPIGMENT_EXPORT*/ KoGenericLabHistogramProducerFactory : public KoHistogramProducerFactory { public: KoGenericLabHistogramProducerFactory(); ~KoGenericLabHistogramProducerFactory() override {} KoHistogramProducer *generate() override { return new KoGenericLabHistogramProducer(); } bool isCompatibleWith(const KoColorSpace*, bool strict = false) const override { Q_UNUSED(strict); return true; } float preferrednessLevelWith(const KoColorSpace*) const override { return 0.0; } }; #endif // _Ko_BASIC_HISTOGRAM_PRODUCERS_ diff --git a/libs/pigment/KoCompositeOpRegistry.cpp b/libs/pigment/KoCompositeOpRegistry.cpp index 6589bc6aa5..fc0806f2c4 100644 --- a/libs/pigment/KoCompositeOpRegistry.cpp +++ b/libs/pigment/KoCompositeOpRegistry.cpp @@ -1,268 +1,316 @@ /* * 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" +#include "kis_assert.h" + Q_GLOBAL_STATIC(KoCompositeOpRegistry, registry) KoCompositeOpRegistry::KoCompositeOpRegistry() { m_categories << KoID("arithmetic", i18n("Arithmetic")) << KoID("binary" , i18n("Binary")) << KoID("dark" , i18n("Darken")) << KoID("light" , i18n("Lighten")) << KoID("modulo" , i18n("Modulo")) << 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")) << KoID("quadratic" , i18n("Quadratic")); 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_XOR , i18n("XOR"))); m_map.insert(m_categories[1], KoID(COMPOSITE_OR , i18n("OR"))); m_map.insert(m_categories[1], KoID(COMPOSITE_AND , i18n("AND"))); m_map.insert(m_categories[1], KoID(COMPOSITE_NAND , i18n("NAND"))); m_map.insert(m_categories[1], KoID(COMPOSITE_NOR , i18n("NOR"))); m_map.insert(m_categories[1], KoID(COMPOSITE_XNOR , i18n("XNOR"))); m_map.insert(m_categories[1], KoID(COMPOSITE_IMPLICATION , i18n("IMPLICATION"))); m_map.insert(m_categories[1], KoID(COMPOSITE_NOT_IMPLICATION , i18n("NOT IMPLICATION"))); m_map.insert(m_categories[1], KoID(COMPOSITE_CONVERSE , i18n("CONVERSE"))); m_map.insert(m_categories[1], KoID(COMPOSITE_NOT_CONVERSE , i18n("NOT CONVERSE"))); m_map.insert(m_categories[2], KoID(COMPOSITE_BURN , i18n("Burn"))); m_map.insert(m_categories[2], KoID(COMPOSITE_LINEAR_BURN, i18n("Linear Burn"))); m_map.insert(m_categories[2], KoID(COMPOSITE_DARKEN , i18n("Darken"))); m_map.insert(m_categories[2], KoID(COMPOSITE_GAMMA_DARK , i18n("Gamma Dark"))); m_map.insert(m_categories[2], KoID(COMPOSITE_DARKER_COLOR , i18n("Darker Color"))); m_map.insert(m_categories[2], KoID(COMPOSITE_SHADE_IFS_ILLUSIONS, i18n("Shade (IFS Illusions)"))); m_map.insert(m_categories[2], KoID(COMPOSITE_FOG_DARKEN_IFS_ILLUSIONS, i18n("Fog Darken (IFS Illusions)"))); m_map.insert(m_categories[2], KoID(COMPOSITE_EASY_BURN , i18n("Easy Burn"))); m_map.insert(m_categories[3], KoID(COMPOSITE_DODGE , i18n("Color Dodge"))); m_map.insert(m_categories[3], KoID(COMPOSITE_LINEAR_DODGE, i18n("Linear Dodge"))); m_map.insert(m_categories[3], KoID(COMPOSITE_LIGHTEN , i18n("Lighten"))); m_map.insert(m_categories[3], KoID(COMPOSITE_LINEAR_LIGHT, i18n("Linear Light"))); m_map.insert(m_categories[3], KoID(COMPOSITE_SCREEN , i18n("Screen"))); m_map.insert(m_categories[3], KoID(COMPOSITE_PIN_LIGHT , i18n("Pin Light"))); m_map.insert(m_categories[3], KoID(COMPOSITE_VIVID_LIGHT , i18n("Vivid Light"))); m_map.insert(m_categories[3], KoID(COMPOSITE_FLAT_LIGHT , i18n("Flat Light"))); m_map.insert(m_categories[3], KoID(COMPOSITE_HARD_LIGHT , i18n("Hard Light"))); m_map.insert(m_categories[3], KoID(COMPOSITE_SOFT_LIGHT_IFS_ILLUSIONS, i18n("Soft Light (IFS Illusions)"))); m_map.insert(m_categories[3], KoID(COMPOSITE_SOFT_LIGHT_PEGTOP_DELPHI, i18n("Soft Light (Pegtop-Delphi)"))); m_map.insert(m_categories[3], KoID(COMPOSITE_SOFT_LIGHT_PHOTOSHOP, i18n("Soft Light (Photoshop)"))); m_map.insert(m_categories[3], KoID(COMPOSITE_SOFT_LIGHT_SVG, i18n("Soft Light (SVG)"))); m_map.insert(m_categories[3], KoID(COMPOSITE_GAMMA_LIGHT , i18n("Gamma Light"))); m_map.insert(m_categories[3], KoID(COMPOSITE_GAMMA_ILLUMINATION , i18n("Gamma Illumination"))); m_map.insert(m_categories[3], KoID(COMPOSITE_LIGHTER_COLOR , i18n("Lighter Color"))); m_map.insert(m_categories[3], KoID(COMPOSITE_PNORM_A , i18n("P-Norm A"))); m_map.insert(m_categories[3], KoID(COMPOSITE_PNORM_B , i18n("P-Norm B"))); m_map.insert(m_categories[3], KoID(COMPOSITE_SUPER_LIGHT , i18n("Super Light"))); m_map.insert(m_categories[3], KoID(COMPOSITE_TINT_IFS_ILLUSIONS, i18n("Tint (IFS Illusions)"))); m_map.insert(m_categories[3], KoID(COMPOSITE_FOG_LIGHTEN_IFS_ILLUSIONS, i18n("Fog Lighten (IFS Illusions)"))); m_map.insert(m_categories[3], KoID(COMPOSITE_EASY_DODGE , i18n("Easy Dodge"))); m_map.insert(m_categories[3], KoID(COMPOSITE_LUMINOSITY_SAI , i18n("Luminosity/Shine (SAI)"))); m_map.insert(m_categories[4], KoID(COMPOSITE_MOD , i18n("Modulo"))); m_map.insert(m_categories[4], KoID(COMPOSITE_MOD_CON , i18n("Modulo - Continuous"))); m_map.insert(m_categories[4], KoID(COMPOSITE_DIVISIVE_MOD , i18n("Divisive Modulo"))); m_map.insert(m_categories[4], KoID(COMPOSITE_DIVISIVE_MOD_CON , i18n("Divisive Modulo - Continuous"))); m_map.insert(m_categories[4], KoID(COMPOSITE_MODULO_SHIFT , i18n("Modulo Shift"))); m_map.insert(m_categories[4], KoID(COMPOSITE_MODULO_SHIFT_CON , i18n("Modulo Shift - Continuous"))); m_map.insert(m_categories[5], KoID(COMPOSITE_DIFF , i18n("Difference"))); m_map.insert(m_categories[5], KoID(COMPOSITE_EQUIVALENCE , i18n("Equivalence"))); m_map.insert(m_categories[5], KoID(COMPOSITE_ADDITIVE_SUBTRACTIVE , i18n("Additive Subtractive"))); m_map.insert(m_categories[5], KoID(COMPOSITE_EXCLUSION , i18n("Exclusion"))); m_map.insert(m_categories[5], KoID(COMPOSITE_ARC_TANGENT , i18n("Arcus Tangent"))); m_map.insert(m_categories[5], KoID(COMPOSITE_NEGATION , i18n("Negation"))); m_map.insert(m_categories[6], KoID(COMPOSITE_OVER , i18n("Normal"))); m_map.insert(m_categories[6], KoID(COMPOSITE_BEHIND , i18n("Behind"))); m_map.insert(m_categories[6], KoID(COMPOSITE_GREATER , i18n("Greater"))); m_map.insert(m_categories[6], KoID(COMPOSITE_OVERLAY , i18n("Overlay"))); m_map.insert(m_categories[6], KoID(COMPOSITE_ERASE , i18n("Erase"))); m_map.insert(m_categories[6], KoID(COMPOSITE_ALPHA_DARKEN , i18n("Alpha Darken"))); m_map.insert(m_categories[6], KoID(COMPOSITE_HARD_MIX , i18n("Hard Mix"))); m_map.insert(m_categories[6], KoID(COMPOSITE_HARD_MIX_PHOTOSHOP, i18n("Hard Mix (Photoshop)"))); m_map.insert(m_categories[6], KoID(COMPOSITE_GRAIN_MERGE , i18n("Grain Merge"))); m_map.insert(m_categories[6], KoID(COMPOSITE_GRAIN_EXTRACT , i18n("Grain Extract"))); m_map.insert(m_categories[6], KoID(COMPOSITE_PARALLEL , i18n("Parallel"))); m_map.insert(m_categories[6], KoID(COMPOSITE_ALLANON , i18n("Allanon"))); m_map.insert(m_categories[6], KoID(COMPOSITE_GEOMETRIC_MEAN , i18n("Geometric Mean"))); m_map.insert(m_categories[6], KoID(COMPOSITE_DESTINATION_ATOP, i18n("Destination Atop"))); m_map.insert(m_categories[6], KoID(COMPOSITE_DESTINATION_IN , i18n("Destination In"))); m_map.insert(m_categories[6], KoID(COMPOSITE_HARD_OVERLAY , i18n("Hard Overlay"))); m_map.insert(m_categories[6], KoID(COMPOSITE_INTERPOLATION , i18n("Interpolation"))); m_map.insert(m_categories[6], KoID(COMPOSITE_INTERPOLATIONB , i18n("Interpolation - 2X"))); m_map.insert(m_categories[6], KoID(COMPOSITE_PENUMBRAA , i18n("Penumbra A"))); m_map.insert(m_categories[6], KoID(COMPOSITE_PENUMBRAB , i18n("Penumbra B"))); m_map.insert(m_categories[6], KoID(COMPOSITE_PENUMBRAC , i18n("Penumbra C"))); m_map.insert(m_categories[6], KoID(COMPOSITE_PENUMBRAD , i18n("Penumbra D"))); m_map.insert(m_categories[7], KoID(COMPOSITE_BUMPMAP , i18n("Bumpmap"))); m_map.insert(m_categories[7], KoID(COMPOSITE_COMBINE_NORMAL, i18n("Combine Normal Map"))); m_map.insert(m_categories[7], KoID(COMPOSITE_DISSOLVE , i18n("Dissolve"))); m_map.insert(m_categories[7], KoID(COMPOSITE_COPY_RED , i18n("Copy Red"))); m_map.insert(m_categories[7], KoID(COMPOSITE_COPY_GREEN, i18n("Copy Green"))); m_map.insert(m_categories[7], KoID(COMPOSITE_COPY_BLUE , i18n("Copy Blue"))); m_map.insert(m_categories[7], KoID(COMPOSITE_COPY , i18n("Copy"))); m_map.insert(m_categories[7], KoID(COMPOSITE_TANGENT_NORMALMAP, i18n("Tangent Normalmap"))); m_map.insert(m_categories[8], KoID(COMPOSITE_COLOR , i18n("Color"))); m_map.insert(m_categories[8], KoID(COMPOSITE_HUE , i18n("Hue"))); m_map.insert(m_categories[8], KoID(COMPOSITE_SATURATION , i18n("Saturation"))); m_map.insert(m_categories[8], KoID(COMPOSITE_LUMINIZE , i18n("Luminosity"))); m_map.insert(m_categories[8], KoID(COMPOSITE_DEC_SATURATION, i18n("Decrease Saturation"))); m_map.insert(m_categories[8], KoID(COMPOSITE_INC_SATURATION, i18n("Increase Saturation"))); m_map.insert(m_categories[8], KoID(COMPOSITE_DEC_LUMINOSITY, i18n("Decrease Luminosity"))); m_map.insert(m_categories[8], KoID(COMPOSITE_INC_LUMINOSITY, i18n("Increase Luminosity"))); m_map.insert(m_categories[9], KoID(COMPOSITE_COLOR_HSI , i18n("Color HSI"))); m_map.insert(m_categories[9], KoID(COMPOSITE_HUE_HSI , i18n("Hue HSI"))); m_map.insert(m_categories[9], KoID(COMPOSITE_SATURATION_HSI , i18n("Saturation HSI"))); m_map.insert(m_categories[9], KoID(COMPOSITE_INTENSITY , i18n("Intensity"))); m_map.insert(m_categories[9], KoID(COMPOSITE_DEC_SATURATION_HSI, i18n("Decrease Saturation HSI"))); m_map.insert(m_categories[9], KoID(COMPOSITE_INC_SATURATION_HSI, i18n("Increase Saturation HSI"))); m_map.insert(m_categories[9], KoID(COMPOSITE_DEC_INTENSITY , i18n("Decrease Intensity"))); m_map.insert(m_categories[9], KoID(COMPOSITE_INC_INTENSITY , i18n("Increase Intensity"))); m_map.insert(m_categories[10], KoID(COMPOSITE_COLOR_HSL , i18n("Color HSL"))); m_map.insert(m_categories[10], KoID(COMPOSITE_HUE_HSL , i18n("Hue HSL"))); m_map.insert(m_categories[10], KoID(COMPOSITE_SATURATION_HSL , i18n("Saturation HSL"))); m_map.insert(m_categories[10], KoID(COMPOSITE_LIGHTNESS , i18n("Lightness"))); m_map.insert(m_categories[10], KoID(COMPOSITE_DEC_SATURATION_HSL, i18n("Decrease Saturation HSL"))); m_map.insert(m_categories[10], KoID(COMPOSITE_INC_SATURATION_HSL, i18n("Increase Saturation HSL"))); m_map.insert(m_categories[10], KoID(COMPOSITE_DEC_LIGHTNESS , i18n("Decrease Lightness"))); m_map.insert(m_categories[10], KoID(COMPOSITE_INC_LIGHTNESS , i18n("Increase Lightness"))); m_map.insert(m_categories[11], KoID(COMPOSITE_COLOR_HSV , i18n("Color HSV"))); m_map.insert(m_categories[11], KoID(COMPOSITE_HUE_HSV , i18n("Hue HSV"))); m_map.insert(m_categories[11], KoID(COMPOSITE_SATURATION_HSV , i18n("Saturation HSV"))); m_map.insert(m_categories[11], KoID(COMPOSITE_VALUE , i18nc("HSV Value", "Value"))); m_map.insert(m_categories[11], KoID(COMPOSITE_DEC_SATURATION_HSV, i18n("Decrease Saturation HSV"))); m_map.insert(m_categories[11], KoID(COMPOSITE_INC_SATURATION_HSV, i18n("Increase Saturation HSV"))); m_map.insert(m_categories[11], KoID(COMPOSITE_DEC_VALUE , i18n("Decrease Value"))); m_map.insert(m_categories[11], KoID(COMPOSITE_INC_VALUE , i18n("Increase Value"))); m_map.insert(m_categories[12], KoID(COMPOSITE_REFLECT , i18n("Reflect"))); m_map.insert(m_categories[12], KoID(COMPOSITE_GLOW , i18n("Glow"))); m_map.insert(m_categories[12], KoID(COMPOSITE_FREEZE , i18n("Freeze"))); m_map.insert(m_categories[12], KoID(COMPOSITE_HEAT , i18n("Heat"))); m_map.insert(m_categories[12], KoID(COMPOSITE_GLEAT , i18n("Glow-Heat"))); m_map.insert(m_categories[12], KoID(COMPOSITE_HELOW , i18n("Heat-Glow"))); m_map.insert(m_categories[12], KoID(COMPOSITE_REEZE , i18n("Reflect-Freeze"))); m_map.insert(m_categories[12], KoID(COMPOSITE_FRECT , i18n("Freeze-Reflect"))); m_map.insert(m_categories[12], KoID(COMPOSITE_FHYRD , i18n("Heat-Glow & Freeze-Reflect Hybrid"))); } 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::KoIDMap KoCompositeOpRegistry::getLayerStylesCompositeOps() const +{ + QVector ids; + + // not available via the blending modes list in Krita + // ids << COMPOSITE_PASS_THROUGH; + + ids << COMPOSITE_OVER; + ids << COMPOSITE_DISSOLVE; + ids << COMPOSITE_DARKEN; + ids << COMPOSITE_MULT; + ids << COMPOSITE_BURN; + ids << COMPOSITE_LINEAR_BURN; + ids << COMPOSITE_DARKER_COLOR; + ids << COMPOSITE_LIGHTEN; + ids << COMPOSITE_SCREEN; + ids << COMPOSITE_DODGE; + ids << COMPOSITE_LINEAR_DODGE; + ids << COMPOSITE_LIGHTER_COLOR; + ids << COMPOSITE_OVERLAY; + ids << COMPOSITE_SOFT_LIGHT_PHOTOSHOP; + ids << COMPOSITE_HARD_LIGHT; + ids << COMPOSITE_VIVID_LIGHT; + ids << COMPOSITE_LINEAR_LIGHT; + ids << COMPOSITE_PIN_LIGHT; + ids << COMPOSITE_HARD_MIX_PHOTOSHOP; + ids << COMPOSITE_DIFF; + ids << COMPOSITE_EXCLUSION; + ids << COMPOSITE_SUBTRACT; + ids << COMPOSITE_DIVIDE; + ids << COMPOSITE_HUE; + ids << COMPOSITE_SATURATION; + ids << COMPOSITE_COLOR; + ids << COMPOSITE_LUMINIZE; + + KoIDMap result; + Q_FOREACH (const QString &id, ids) { + KoIDMap::const_iterator iter = std::find(m_map.begin(), m_map.end(), KoID(id)); + KIS_SAFE_ASSERT_RECOVER(iter != m_map.end()) { continue; } + + result.insert(iter.key(), iter.value()); + } + + return result; +} + 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/KoCompositeOpRegistry.h b/libs/pigment/KoCompositeOpRegistry.h index b71e61470f..6edae69baa 100644 --- a/libs/pigment/KoCompositeOpRegistry.h +++ b/libs/pigment/KoCompositeOpRegistry.h @@ -1,228 +1,229 @@ /* * Copyright (c) 2005 Adrian Page * 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 KOCOMPOSITEOPREGISTRY_H #define KOCOMPOSITEOPREGISTRY_H #include #include #include #include #include "kritapigment_export.h" class KoColorSpace; #include // TODO : convert this data blob into a modern design with an enum class. // This will reduce the need for runtime string comparisons. const QString COMPOSITE_OVER = "normal"; const QString COMPOSITE_ERASE = "erase"; const QString COMPOSITE_IN = "in"; const QString COMPOSITE_OUT = "out"; const QString COMPOSITE_ALPHA_DARKEN = "alphadarken"; const QString COMPOSITE_DESTINATION_IN = "destination-in"; const QString COMPOSITE_DESTINATION_ATOP = "destination-atop"; const QString COMPOSITE_XOR = "xor"; const QString COMPOSITE_OR = "or"; const QString COMPOSITE_AND = "and"; const QString COMPOSITE_NAND = "nand"; const QString COMPOSITE_NOR = "nor"; const QString COMPOSITE_XNOR = "xnor"; const QString COMPOSITE_IMPLICATION = "implication"; const QString COMPOSITE_NOT_IMPLICATION = "not_implication"; const QString COMPOSITE_CONVERSE = "converse"; const QString COMPOSITE_NOT_CONVERSE = "not_converse"; const QString COMPOSITE_PLUS = "plus"; const QString COMPOSITE_MINUS = "minus"; const QString COMPOSITE_ADD = "add"; const QString COMPOSITE_SUBTRACT = "subtract"; const QString COMPOSITE_INVERSE_SUBTRACT = "inverse_subtract"; const QString COMPOSITE_DIFF = "diff"; const QString COMPOSITE_MULT = "multiply"; const QString COMPOSITE_DIVIDE = "divide"; const QString COMPOSITE_ARC_TANGENT = "arc_tangent"; const QString COMPOSITE_GEOMETRIC_MEAN = "geometric_mean"; const QString COMPOSITE_ADDITIVE_SUBTRACTIVE = "additive_subtractive"; const QString COMPOSITE_NEGATION = "negation"; const QString COMPOSITE_MOD = "modulo"; const QString COMPOSITE_MOD_CON = "modulo_continuous"; const QString COMPOSITE_DIVISIVE_MOD = "divisive_modulo"; const QString COMPOSITE_DIVISIVE_MOD_CON = "divisive_modulo_continuous"; const QString COMPOSITE_MODULO_SHIFT = "modulo_shift"; const QString COMPOSITE_MODULO_SHIFT_CON = "modulo_shift_continuous"; const QString COMPOSITE_EQUIVALENCE = "equivalence"; const QString COMPOSITE_ALLANON = "allanon"; const QString COMPOSITE_PARALLEL = "parallel"; const QString COMPOSITE_GRAIN_MERGE = "grain_merge"; const QString COMPOSITE_GRAIN_EXTRACT = "grain_extract"; const QString COMPOSITE_EXCLUSION = "exclusion"; const QString COMPOSITE_HARD_MIX = "hard mix"; const QString COMPOSITE_HARD_MIX_PHOTOSHOP = "hard_mix_photoshop"; const QString COMPOSITE_OVERLAY = "overlay"; const QString COMPOSITE_BEHIND = "behind"; const QString COMPOSITE_GREATER = "greater"; const QString COMPOSITE_HARD_OVERLAY = "hard overlay"; const QString COMPOSITE_INTERPOLATION = "interpolation"; const QString COMPOSITE_INTERPOLATIONB = "interpolation 2x"; const QString COMPOSITE_PENUMBRAA = "penumbra a"; const QString COMPOSITE_PENUMBRAB = "penumbra b"; const QString COMPOSITE_PENUMBRAC = "penumbra c"; const QString COMPOSITE_PENUMBRAD = "penumbra d"; const QString COMPOSITE_DARKEN = "darken"; const QString COMPOSITE_BURN = "burn";//this is also known as 'color burn'. const QString COMPOSITE_LINEAR_BURN = "linear_burn"; const QString COMPOSITE_GAMMA_DARK = "gamma_dark"; const QString COMPOSITE_SHADE_IFS_ILLUSIONS = "shade_ifs_illusions"; const QString COMPOSITE_FOG_DARKEN_IFS_ILLUSIONS = "fog_darken_ifs_illusions"; const QString COMPOSITE_EASY_BURN = "easy burn"; const QString COMPOSITE_LIGHTEN = "lighten"; const QString COMPOSITE_DODGE = "dodge"; const QString COMPOSITE_LINEAR_DODGE = "linear_dodge"; const QString COMPOSITE_SCREEN = "screen"; const QString COMPOSITE_HARD_LIGHT = "hard_light"; const QString COMPOSITE_SOFT_LIGHT_IFS_ILLUSIONS = "soft_light_ifs_illusions"; const QString COMPOSITE_SOFT_LIGHT_PEGTOP_DELPHI = "soft_light_pegtop_delphi"; const QString COMPOSITE_SOFT_LIGHT_PHOTOSHOP = "soft_light"; const QString COMPOSITE_SOFT_LIGHT_SVG = "soft_light_svg"; const QString COMPOSITE_GAMMA_LIGHT = "gamma_light"; const QString COMPOSITE_GAMMA_ILLUMINATION = "gamma_illumination"; const QString COMPOSITE_VIVID_LIGHT = "vivid_light"; const QString COMPOSITE_FLAT_LIGHT = "flat_light"; const QString COMPOSITE_LINEAR_LIGHT = "linear light"; const QString COMPOSITE_PIN_LIGHT = "pin_light"; const QString COMPOSITE_PNORM_A = "pnorm_a"; const QString COMPOSITE_PNORM_B = "pnorm_b"; const QString COMPOSITE_SUPER_LIGHT = "super_light"; const QString COMPOSITE_TINT_IFS_ILLUSIONS = "tint_ifs_illusions"; const QString COMPOSITE_FOG_LIGHTEN_IFS_ILLUSIONS = "fog_lighten_ifs_illusions"; const QString COMPOSITE_EASY_DODGE = "easy dodge"; const QString COMPOSITE_LUMINOSITY_SAI = "luminosity_sai"; const QString COMPOSITE_HUE = "hue"; const QString COMPOSITE_COLOR = "color"; const QString COMPOSITE_SATURATION = "saturation"; const QString COMPOSITE_INC_SATURATION = "inc_saturation"; const QString COMPOSITE_DEC_SATURATION = "dec_saturation"; const QString COMPOSITE_LUMINIZE = "luminize"; const QString COMPOSITE_INC_LUMINOSITY = "inc_luminosity"; const QString COMPOSITE_DEC_LUMINOSITY = "dec_luminosity"; const QString COMPOSITE_HUE_HSV = "hue_hsv"; const QString COMPOSITE_COLOR_HSV = "color_hsv"; const QString COMPOSITE_SATURATION_HSV = "saturation_hsv"; const QString COMPOSITE_INC_SATURATION_HSV = "inc_saturation_hsv"; const QString COMPOSITE_DEC_SATURATION_HSV = "dec_saturation_hsv"; const QString COMPOSITE_VALUE = "value"; const QString COMPOSITE_INC_VALUE = "inc_value"; const QString COMPOSITE_DEC_VALUE = "dec_value"; const QString COMPOSITE_HUE_HSL = "hue_hsl"; const QString COMPOSITE_COLOR_HSL = "color_hsl"; const QString COMPOSITE_SATURATION_HSL = "saturation_hsl"; const QString COMPOSITE_INC_SATURATION_HSL = "inc_saturation_hsl"; const QString COMPOSITE_DEC_SATURATION_HSL = "dec_saturation_hsl"; const QString COMPOSITE_LIGHTNESS = "lightness"; const QString COMPOSITE_INC_LIGHTNESS = "inc_lightness"; const QString COMPOSITE_DEC_LIGHTNESS = "dec_lightness"; const QString COMPOSITE_HUE_HSI = "hue_hsi"; const QString COMPOSITE_COLOR_HSI = "color_hsi"; const QString COMPOSITE_SATURATION_HSI = "saturation_hsi"; const QString COMPOSITE_INC_SATURATION_HSI = "inc_saturation_hsi"; const QString COMPOSITE_DEC_SATURATION_HSI = "dec_saturation_hsi"; const QString COMPOSITE_INTENSITY = "intensity"; const QString COMPOSITE_INC_INTENSITY = "inc_intensity"; const QString COMPOSITE_DEC_INTENSITY = "dec_intensity"; const QString COMPOSITE_COPY = "copy"; const QString COMPOSITE_COPY_RED = "copy_red"; const QString COMPOSITE_COPY_GREEN = "copy_green"; const QString COMPOSITE_COPY_BLUE = "copy_blue"; const QString COMPOSITE_TANGENT_NORMALMAP = "tangent_normalmap"; const QString COMPOSITE_COLORIZE = "colorize"; const QString COMPOSITE_BUMPMAP = "bumpmap"; const QString COMPOSITE_COMBINE_NORMAL = "combine_normal"; const QString COMPOSITE_CLEAR = "clear"; const QString COMPOSITE_DISSOLVE = "dissolve"; const QString COMPOSITE_DISPLACE = "displace"; const QString COMPOSITE_NO = "nocomposition"; const QString COMPOSITE_PASS_THROUGH = "pass through"; // XXX: not implemented anywhere yet const QString COMPOSITE_DARKER_COLOR = "darker color"; const QString COMPOSITE_LIGHTER_COLOR = "lighter color"; const QString COMPOSITE_UNDEF = "undefined"; const QString COMPOSITE_REFLECT = "reflect"; const QString COMPOSITE_GLOW = "glow"; const QString COMPOSITE_FREEZE = "freeze"; const QString COMPOSITE_HEAT = "heat"; const QString COMPOSITE_GLEAT = "glow_heat"; const QString COMPOSITE_HELOW = "heat_glow"; const QString COMPOSITE_REEZE = "reflect_freeze"; const QString COMPOSITE_FRECT = "freeze_reflect"; const QString COMPOSITE_FHYRD = "heat_glow_freeze_reflect_hybrid"; class KRITAPIGMENT_EXPORT KoCompositeOpRegistry { typedef QMultiMap KoIDMap; typedef QList KoIDList; public: KoCompositeOpRegistry(); static const KoCompositeOpRegistry& instance(); KoID getDefaultCompositeOp() const; KoID getKoID(const QString& compositeOpID) const; KoIDMap getCompositeOps() const; + KoIDMap getLayerStylesCompositeOps() const; KoIDList getCategories() const; KoIDList getCompositeOps(const KoColorSpace* colorSpace) const; KoIDList getCompositeOps(const KoID& category, const KoColorSpace* colorSpace=0) const; bool colorSpaceHasCompositeOp(const KoColorSpace* colorSpace, const KoID& compositeOp) const; template KoIDList filterCompositeOps(TKoIdIterator begin, TKoIdIterator end, const KoColorSpace* colorSpace, bool removeInvaliOps=true) const { KoIDList list; for(; begin!=end; ++begin){ if (colorSpaceHasCompositeOp(colorSpace, *begin) == removeInvaliOps) { list.push_back(*begin); } } return list; } private: KoIDList m_categories; KoIDMap m_map; }; #endif // KOCOMPOSITEOPREGISTRY_H diff --git a/libs/pigment/Mainpage.dox b/libs/pigment/Mainpage.dox index 16e5783c25..79f7487c05 100644 --- a/libs/pigment/Mainpage.dox +++ b/libs/pigment/Mainpage.dox @@ -1,15 +1,15 @@ /** * \mainpage * Pigment is a Color Manipulation System with pluggable color spaces. Color spaces * can be based on the
LCMS library, or using - * the OpenCTL implmenetation of the Color + * the OpenCTL implmentation of the Color * Transformation Langboth are optional. * * Pigment color spaces offers support for many common manipulations and for * pixel composition. Pigment works on arrays of bytes: the color spaces interpret * the array of bytes as a pixel according to the pixel format defined by each * color space. */ // DOXYGEN_SET_PROJECT_NAME = KritaPigment // DOXYGEN_SET_IGNORE_PREFIX = Ko K diff --git a/libs/pigment/compositeops/KoCompositeOpFunctions.h b/libs/pigment/compositeops/KoCompositeOpFunctions.h index d51ad77299..7357238e0e 100644 --- a/libs/pigment/compositeops/KoCompositeOpFunctions.h +++ b/libs/pigment/compositeops/KoCompositeOpFunctions.h @@ -1,952 +1,952 @@ /* * 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 KOCOMPOSITEOP_FUNCTIONS_H_ #define KOCOMPOSITEOP_FUNCTIONS_H_ #include template inline void cfReorientedNormalMapCombine(TReal srcR, TReal srcG, TReal srcB, TReal& dstR, TReal& dstG, TReal& dstB) { // see http://blog.selfshadow.com/publications/blending-in-detail/ by Barre-Brisebois and Hill TReal tx = 2*srcR-1; TReal ty = 2*srcG-1; TReal tz = 2*srcB; TReal ux = -2*dstR+1; TReal uy = -2*dstG+1; TReal uz = 2*dstB-1; TReal k = (tx*ux+ty*uy+tz*uz)/tz; // dot(t,u)/t.z TReal rx = tx*k-ux; TReal ry = ty*k-uy; TReal rz = tz*k-uz; k = 1/sqrt(rx*rx+ry*ry+rz*rz); // normalize result rx *= k; ry *= k; rz *= k; dstR = rx*0.5+0.5; dstG = ry*0.5+0.5; dstB = rz*0.5+0.5; } template inline void cfColor(TReal sr, TReal sg, TReal sb, TReal& dr, TReal& dg, TReal& db) { TReal lum = getLightness(dr, dg, db); dr = sr; dg = sg; db = sb; setLightness(dr, dg, db, lum); } template inline void cfLightness(TReal sr, TReal sg, TReal sb, TReal& dr, TReal& dg, TReal& db) { setLightness(dr, dg, db, getLightness(sr, sg, sb)); } template inline void cfIncreaseLightness(TReal sr, TReal sg, TReal sb, TReal& dr, TReal& dg, TReal& db) { addLightness(dr, dg, db, getLightness(sr, sg, sb)); } template inline void cfDecreaseLightness(TReal sr, TReal sg, TReal sb, TReal& dr, TReal& dg, TReal& db) { addLightness(dr, dg, db, getLightness(sr, sg, sb) - TReal(1.0)); } template inline void cfSaturation(TReal sr, TReal sg, TReal sb, TReal& dr, TReal& dg, TReal& db) { TReal sat = getSaturation(sr, sg, sb); TReal light = getLightness(dr, dg, db); setSaturation(dr, dg, db, sat); setLightness(dr, dg, db, light); } template inline void cfIncreaseSaturation(TReal sr, TReal sg, TReal sb, TReal& dr, TReal& dg, TReal& db) { using namespace Arithmetic; TReal sat = lerp(getSaturation(dr,dg,db), unitValue(), getSaturation(sr,sg,sb)); TReal light = getLightness(dr, dg, db); setSaturation(dr, dg, db, sat); setLightness(dr, dg, db, light); } template inline void cfDecreaseSaturation(TReal sr, TReal sg, TReal sb, TReal& dr, TReal& dg, TReal& db) { using namespace Arithmetic; TReal sat = lerp(zeroValue(), getSaturation(dr,dg,db), getSaturation(sr,sg,sb)); TReal light = getLightness(dr, dg, db); setSaturation(dr, dg, db, sat); setLightness(dr, dg, db, light); } template inline void cfHue(TReal sr, TReal sg, TReal sb, TReal& dr, TReal& dg, TReal& db) { TReal sat = getSaturation(dr, dg, db); TReal lum = getLightness(dr, dg, db); dr = sr; dg = sg; db = sb; setSaturation(dr, dg, db, sat); setLightness(dr, dg, db, lum); } template inline void cfTangentNormalmap(TReal sr, TReal sg, TReal sb, TReal& dr, TReal& dg, TReal& db) { using namespace Arithmetic; TReal half=halfValue(); dr = sr+(dr-half); dg = sg+(dg-half); db = sb+(db-unitValue()); } template inline void cfDarkerColor(TReal sr, TReal sg, TReal sb, TReal& dr, TReal& dg, TReal& db) { TReal lum = getLightness(dr, dg, db); TReal lum2 = getLightness(sr, sg, sb); if (lum inline void cfLighterColor(TReal sr, TReal sg, TReal sb, TReal& dr, TReal& dg, TReal& db) { TReal lum = getLightness(dr, dg, db); TReal lum2 = getLightness(sr, sg, sb); if (lum>lum2) { sr = dr; sg = dg; sb = db; } else { dr = sr; dg = sg; db = sb; } } template inline T cfColorBurn(T src, T dst) { using namespace Arithmetic; if(dst == unitValue()) return unitValue(); T invDst = inv(dst); if(src < invDst) return zeroValue(); return inv(clamp(div(invDst, src))); } template inline T cfLinearBurn(T src, T dst) { using namespace Arithmetic; typedef typename KoColorSpaceMathsTraits::compositetype composite_type; return clamp(composite_type(src) + dst - unitValue()); } template inline T cfColorDodge(T src, T dst) { using namespace Arithmetic; //Fixing Color Dodge to avoid ZX Colors on bright area. if(src == unitValue()) return unitValue(); T invSrc = inv(src); if(invSrc == zeroValue()) return unitValue(); return Arithmetic::clamp(div(dst, invSrc)); } template inline T cfAddition(T src, T dst) { typedef typename KoColorSpaceMathsTraits::compositetype composite_type; return Arithmetic::clamp(composite_type(src) + dst); } template inline T cfSubtract(T src, T dst) { using namespace Arithmetic; typedef typename KoColorSpaceMathsTraits::compositetype composite_type; return clamp(composite_type(dst) - src); } template inline T cfInverseSubtract(T src, T dst) { using namespace Arithmetic; typedef typename KoColorSpaceMathsTraits::compositetype composite_type; return clamp(composite_type(dst) - inv(src)); } template inline T cfExclusion(T src, T dst) { using namespace Arithmetic; typedef typename KoColorSpaceMathsTraits::compositetype composite_type; composite_type x = mul(src, dst); return clamp(composite_type(dst) + src - (x + x)); } template inline T cfDivide(T src, T dst) { using namespace Arithmetic; //typedef typename KoColorSpaceMathsTraits::compositetype composite_type; if(src == zeroValue()) return (dst == zeroValue()) ? zeroValue() : unitValue(); return clamp(div(dst, src)); } template inline T cfHardLight(T src, T dst) { using namespace Arithmetic; typedef typename KoColorSpaceMathsTraits::compositetype composite_type; composite_type src2 = composite_type(src) + src; if(src > halfValue()) { // screen(src*2.0 - 1.0, dst) src2 -= unitValue(); // src2 is guaranteed to be smaller than unitValue() now return Arithmetic::unionShapeOpacity(T(src2), dst); } // src2 is guaranteed to be smaller than unitValue() due to 'if' return Arithmetic::mul(T(src2), dst); } template inline T cfSoftLightSvg(T src, T dst) { using namespace Arithmetic; qreal fsrc = scale(src); qreal fdst = scale(dst); if(fsrc > 0.5f) { qreal D = (fdst > 0.25f) ? sqrt(fdst) : ((16.0f*fdst - 12.0)*fdst + 4.0f)*fdst; return scale(fdst + (2.0f*fsrc - 1.0f) * (D - fdst)); } return scale(fdst - (1.0f - 2.0f * fsrc) * fdst * (1.0f - fdst)); } template inline T cfSoftLight(T src, T dst) { using namespace Arithmetic; qreal fsrc = scale(src); qreal fdst = scale(dst); if(fsrc > 0.5f) { return scale(fdst + (2.0f * fsrc - 1.0f) * (sqrt(fdst) - fdst)); } return scale(fdst - (1.0f - 2.0f*fsrc) * fdst * (1.0f - fdst)); } template inline T cfVividLight(T src, T dst) { using namespace Arithmetic; typedef typename KoColorSpaceMathsTraits::compositetype composite_type; if(src < halfValue()) { if(src == zeroValue()) return (dst == unitValue()) ? unitValue() : zeroValue(); // min(1,max(0,1-(1-dst) / (2*src))) composite_type src2 = composite_type(src) + src; composite_type dsti = inv(dst); return clamp(unitValue() - (dsti * unitValue() / src2)); } if(src == unitValue()) return (dst == zeroValue()) ? zeroValue() : unitValue(); // min(1,max(0, dst / (2*(1-src))) composite_type srci2 = inv(src); srci2 += srci2; return clamp(composite_type(dst) * unitValue() / srci2); } template inline T cfPinLight(T src, T dst) { typedef typename KoColorSpaceMathsTraits::compositetype composite_type; // TODO: verify that the formula is correct (the first max would be useless here) // max(0, max(2*src-1, min(dst, 2*src))) composite_type src2 = composite_type(src) + src; composite_type a = qMin(dst, src2); composite_type b = qMax(src2-Arithmetic::unitValue(), a); return T(b); } template inline T cfArcTangent(T src, T dst) { using namespace Arithmetic; if(dst == zeroValue()) return (src == zeroValue()) ? zeroValue() : unitValue(); return scale(2.0 * atan(scale(src) / scale(dst)) / Arithmetic::pi); } template inline T cfAllanon(T src, T dst) { using namespace Arithmetic; typedef typename KoColorSpaceMathsTraits::compositetype composite_type; // (dst + src) / 2 [or (dst + src) * 0.5] return T((composite_type(src) + dst) * halfValue() / unitValue()); } template inline T cfLinearLight(T src, T dst) { using namespace Arithmetic; typedef typename KoColorSpaceMathsTraits::compositetype composite_type; // min(1,max(0,(dst + 2*src)-1)) return clamp((composite_type(src) + src + dst) - unitValue()); } template inline T cfParallel(T src, T dst) { using namespace Arithmetic; typedef typename KoColorSpaceMathsTraits::compositetype composite_type; // min(max(2 / (1/dst + 1/src), 0), 1) composite_type unit = unitValue(); composite_type s = (src != zeroValue()) ? div(unit, src) : unit; composite_type d = (dst != zeroValue()) ? div(unit, dst) : unit; if (src == zeroValue()) { return zeroValue(); } if (dst == zeroValue()) { return zeroValue(); } return clamp((unit+unit) * unit / (d+s)); } template inline T cfEquivalence(T src, T dst) { typedef typename KoColorSpaceMathsTraits::compositetype composite_type; // 1 - abs(dst - src) composite_type x = composite_type(dst) - src; return (x < Arithmetic::zeroValue()) ? T(-x) : T(x); } template inline T cfGrainMerge(T src, T dst) { using namespace Arithmetic; typedef typename KoColorSpaceMathsTraits::compositetype composite_type; return clamp(composite_type(dst) + src - halfValue()); } template inline T cfGrainExtract(T src, T dst) { using namespace Arithmetic; typedef typename KoColorSpaceMathsTraits::compositetype composite_type; return clamp(composite_type(dst) - src + halfValue()); } template inline T cfHardMix(T src, T dst) { return (dst > Arithmetic::halfValue()) ? cfColorDodge(src,dst) : cfColorBurn(src,dst); } template inline T cfHardMixPhotoshop(T src, T dst) { using namespace Arithmetic; typedef typename KoColorSpaceMathsTraits::compositetype composite_type; const composite_type sum = composite_type(src) + dst; return sum > unitValue() ? unitValue() : zeroValue(); } template inline T cfAdditiveSubtractive(T src, T dst) { using namespace Arithmetic; // min(1,max(0,abs(sqr(CB)-sqr(CT)))) qreal x = sqrt(scale(dst)) - sqrt(scale(src)); return scale((x < 0.0) ? -x : x); } template inline T cfGammaDark(T src, T dst) { using namespace Arithmetic; if(src == zeroValue()) return zeroValue(); // power(dst, 1/src) return scale(pow(scale(dst), 1.0/scale(src))); } template inline T cfGammaLight(T src, T dst) { using namespace Arithmetic; return scale(pow(scale(dst), scale(src))); } template inline T cfGammaIllumination(T src, T dst) { using namespace Arithmetic; return inv(cfGammaDark(inv(src),inv(dst))); } template inline T cfGeometricMean(T src, T dst) { using namespace Arithmetic; return scale(sqrt(scale(dst) * scale(src))); } template inline T cfOver(T src, T dst) { Q_UNUSED(dst); return src; } template inline T cfOverlay(T src, T dst) { return cfHardLight(dst, src); } template inline T cfMultiply(T src, T dst) { return Arithmetic::mul(src, dst); } template inline T cfHardOverlay(T src, T dst) { using namespace Arithmetic; qreal fsrc = scale(src); qreal fdst = scale(dst); if (fsrc == 1.0) { return scale(1.0);} if(fsrc > 0.5f) { return scale(cfDivide(inv(2.0 * fsrc - 1.0f), fdst)); } return scale(mul(2.0 * fsrc, fdst)); } template inline T cfDifference(T src, T dst) { return qMax(src,dst) - qMin(src,dst); } template inline T cfScreen(T src, T dst) { return Arithmetic::unionShapeOpacity(src, dst); } template inline T cfDarkenOnly(T src, T dst) { return qMin(src, dst); } template inline T cfLightenOnly(T src, T dst) { return qMax(src, dst); } template inline T cfGlow(T src, T dst) { using namespace Arithmetic; // see http://www.pegtop.net/delphi/articles/blendmodes/quadratic.htm for formulas of Quadratic Blending Modes like Glow, Reflect, Freeze, and Heat if (dst == unitValue()) { return unitValue(); } return clamp(div(mul(src, src), inv(dst))); } template inline T cfReflect(T src, T dst) { using namespace Arithmetic; return clamp(cfGlow(dst,src)); } template inline T cfHeat(T src, T dst) { using namespace Arithmetic; if(src == unitValue()) { return unitValue(); } if(dst == zeroValue()) { return zeroValue(); } return inv(clamp(div(mul(inv(src), inv(src)),dst))); } template inline T cfFreeze(T src, T dst) { using namespace Arithmetic; return (cfHeat(dst,src)); } template inline T cfHelow(T src, T dst) { using namespace Arithmetic; // see http://www.pegtop.net/delphi/articles/blendmodes/quadratic.htm for formulas of Quadratic Blending Modes like Glow, Reflect, Freeze, and Heat if (cfHardMixPhotoshop(src,dst) == unitValue()) { return cfHeat(src,dst); } if (src == zeroValue()) { return zeroValue(); } return (cfGlow(src,dst)); } template inline T cfFrect(T src, T dst) { using namespace Arithmetic; if (cfHardMixPhotoshop(src,dst) == unitValue()) { return cfFreeze(src,dst); } if (dst == zeroValue()) { return zeroValue(); } return (cfReflect(src,dst)); } template inline T cfGleat(T src, T dst) { using namespace Arithmetic; // see http://www.pegtop.net/delphi/articles/blendmodes/quadratic.htm for formulas of Quadratic Blending Modes like Glow, Reflect, Freeze, and Heat if(dst == unitValue()) { return unitValue(); } if(cfHardMixPhotoshop(src,dst) == unitValue()) { return cfGlow(src,dst); } return (cfHeat(src,dst)); } template inline T cfReeze(T src, T dst) { using namespace Arithmetic; return (cfGleat(dst,src)); } template inline T cfFhyrd(T src, T dst) { using namespace Arithmetic; return (cfAllanon(cfFrect(src,dst),cfHelow(src,dst))); } template inline T cfInterpolation(T src, T dst) { using namespace Arithmetic; qreal fsrc = scale(src); qreal fdst = scale(dst); if(dst == zeroValue() && src == zeroValue()) { return zeroValue(); } return scale(.5f-.25f*cos(pi*(fsrc))-.25f*cos(pi*(fdst))); } template inline T cfInterpolationB(T src, T dst) { using namespace Arithmetic; return cfInterpolation(cfInterpolation(src,dst),cfInterpolation(src,dst)); } template inline T cfPenumbraB(T src, T dst) { using namespace Arithmetic; if (dst == unitValue()) { return unitValue(); } if (dst + src < unitValue()) { return (cfColorDodge(dst,src)/2); } if (src == zeroValue()) { return zeroValue(); } return inv(clamp(div(inv(dst),src)/2)); } template inline T cfPenumbraD(T src, T dst) { using namespace Arithmetic; if (dst == unitValue()) { return unitValue(); } return cfArcTangent(src,inv(dst)); } template inline T cfPenumbraC(T src, T dst) { using namespace Arithmetic; return cfPenumbraD(dst,src); } template inline T cfPenumbraA(T src, T dst) { using namespace Arithmetic; return (cfPenumbraB(dst,src)); } template inline T cfSoftLightIFSIllusions(T src, T dst) { using namespace Arithmetic; qreal fsrc = scale(src); qreal fdst = scale(dst); return scale(pow(fdst,pow(2.0,(mul(2.0,.5f-fsrc))))); } template inline T cfSoftLightPegtopDelphi(T src, T dst) { using namespace Arithmetic; return clamp(cfAddition(mul(dst,cfScreen(src,dst)),mul(mul(src,dst),inv(dst)))); } template inline T cfNegation(T src, T dst) { using namespace Arithmetic; typedef typename KoColorSpaceMathsTraits::compositetype composite_type; composite_type unit = unitValue(); composite_type a = unit - src - dst; composite_type s = abs(a); composite_type d = unit - s; return T(d); } template inline T cfNor(T src, T dst) { using namespace Arithmetic; return and(src,dst); } template inline T cfNand(T src, T dst) { using namespace Arithmetic; return or(src,dst); } template inline T cfXor(T src, T dst) { using namespace Arithmetic; return xor(src,dst); } template inline T cfXnor(T src, T dst) { using namespace Arithmetic; return cfXor(src,inv(dst)); } template inline T cfAnd(T src, T dst) { using namespace Arithmetic; return cfNor(inv(src),inv(dst)); } template inline T cfOr(T src, T dst) { using namespace Arithmetic; return cfNand(inv(src),inv(dst)); } template inline T cfConverse(T src, T dst) { using namespace Arithmetic; return cfOr(inv(src),dst); } template inline T cfNotConverse(T src, T dst) { using namespace Arithmetic; return cfAnd(src,inv(dst)); } template inline T cfImplies(T src, T dst) { using namespace Arithmetic; return cfOr(src,inv(dst)); } template inline T cfNotImplies(T src, T dst) { using namespace Arithmetic; return cfAnd(inv(src),dst); } template inline T cfPNormA(T src, T dst) { using namespace Arithmetic; //This is also known as P-Norm mode with factor of 2.3333 See IMBLEND image blending mode samples, and please see imblend.m file found on Additional Blending Mode thread at Phabricator. 1/2.3333 is .42875... return clamp(pow(pow((float)dst, 2.3333333333333333) + pow((float)src, 2.3333333333333333), 0.428571428571434)); } template inline T cfPNormB(T src, T dst) { using namespace Arithmetic; //This is also known as P-Norm mode with factor of 2.3333 See IMBLEND image blending mode samples, and please see imblend.m file found on Additional Blending Mode thread at Phabricator. 1/2.3333 is .42875... return clamp(pow(pow(dst,4)+pow(src,4),0.25)); } template inline T cfSuperLight(T src, T dst) { using namespace Arithmetic; //4.0 can be adjusted to taste. 4.0 is picked for being the best in terms of contrast and details. See imblend.m file. qreal fsrc = scale(src); qreal fdst = scale(dst); if (fsrc < .5) { return scale(inv(pow(pow(inv(fdst),2.875)+pow(inv(2.0*fsrc),2.875),1.0/2.875))); } return scale(pow(pow(fdst,2.875)+pow(2.0*fsrc-1.0,2.875),1.0/2.875)); } template inline T cfTintIFSIllusions(T src, T dst) { using namespace Arithmetic; //Known as Light Blending mode found in IFS Illusions. Picked this name because it results into a very strong tint, and has better naming convention. qreal fsrc = scale(src); qreal fdst = scale(dst); return scale(fsrc*inv(fdst)+sqrt(fdst)); } template inline T cfShadeIFSIllusions(T src, T dst) { using namespace Arithmetic; //Known as Shadow Blending mode found in IFS Illusions. Picked this name because it is the opposite of Tint (IFS Illusion Blending mode). qreal fsrc = scale(src); qreal fdst = scale(dst); return scale(inv((inv(fdst)*fsrc)+sqrt(inv(fsrc)))); } template inline T cfFogLightenIFSIllusions(T src, T dst) { using namespace Arithmetic; - //Known as Bright Blending mode found in IFS Illusions. Picked this name because the shading reminds me of fog when overlaying with a gradientt. + //Known as Bright Blending mode found in IFS Illusions. Picked this name because the shading reminds me of fog when overlaying with a gradient. qreal fsrc = scale(src); qreal fdst = scale(dst); if (fsrc < .5) { return scale(inv(inv(fsrc)*fsrc)-inv(fdst)*inv(fsrc)); } return scale(fsrc-inv(fdst)*inv(fsrc)+pow(inv(fsrc),2)); } template inline T cfFogDarkenIFSIllusions(T src, T dst) { using namespace Arithmetic; //Known as Dark Blending mode found in IFS Illusions. Picked this name because the shading reminds me of fog when overlaying with a gradient. qreal fsrc = scale(src); qreal fdst = scale(dst); if (fsrc < .5) { return scale(inv(fsrc)*fsrc+fsrc*fdst); } return scale(fsrc*fdst+fsrc-pow(fsrc,2)); } template inline T cfModulo(T src, T dst) { using namespace Arithmetic; return mod(dst,src); } template inline T cfModuloShift(T src, T dst) { using namespace Arithmetic; qreal fsrc = scale(src); qreal fdst = scale(dst); if (fsrc == 1.0 && fdst == 0.0) { return scale(0.0); } return scale(mod((fdst+fsrc),1.0000000000)); } template inline T cfModuloShiftContinuous(T src, T dst) { using namespace Arithmetic; - //This blending mode do not behave like difference/equilavent with destination layer inverted if you use group layer on addition while the content of group layer contains several addition-mode layers, it works as expected on float images. So, no need to change this. + //This blending mode do not behave like difference/equivalent with destination layer inverted if you use group layer on addition while the content of group layer contains several addition-mode layers, it works as expected on float images. So, no need to change this. qreal fsrc = scale(src); qreal fdst = scale(dst); if (fsrc == 1.0 && fdst == 0.0) { return scale(1.0); } return scale((int(ceil(fdst+fsrc)) % 2 != 0) || (fdst == zeroValue()) ? cfModuloShift(fsrc,fdst) : inv(cfModuloShift(fsrc,fdst))); } template inline T cfDivisiveModulo(T src, T dst) { using namespace Arithmetic; //I have to use 1.00000 as unitValue failed to work for those area. qreal fsrc = scale(src); qreal fdst = scale(dst); if (fsrc == zeroValue()) { return scale(mod(((1.0000000000/epsilon()) * fdst),1.0000000000)); } return scale(mod(((1.0000000000/fsrc) * fdst),1.0000000000)); } template inline T cfDivisiveModuloContinuous(T src, T dst) { using namespace Arithmetic; qreal fsrc = scale(src); qreal fdst = scale(dst); if (fdst == zeroValue()) { return zeroValue(); } if (fsrc == zeroValue()) { return cfDivisiveModulo(fsrc,fdst); } return scale( int(ceil(fdst/fsrc)) % 2 != 0 ? cfDivisiveModulo(fsrc,fdst) : inv(cfDivisiveModulo(fsrc,fdst))); } template inline T cfModuloContinuous(T src, T dst) { using namespace Arithmetic; return cfMultiply(cfDivisiveModuloContinuous(src,dst),src); } template inline T cfEasyDodge(T src, T dst) { using namespace Arithmetic; // The 13 divided by 15 can be adjusted to taste. See imgblend.m qreal fsrc = scale(src); qreal fdst = scale(dst); if (fsrc == 1.0) { return scale(1.0);} return scale(pow(fdst,mul(inv(fsrc != 1.0 ? fsrc : .999999999999),1.039999999))); } template inline T cfEasyBurn(T src, T dst) { using namespace Arithmetic; // The 13 divided by 15 can be adjusted to taste. See imgblend.m qreal fsrc = scale(src); qreal fdst = scale(dst); return scale(inv(pow(inv(fsrc != 1.0 ? fsrc : .999999999999),mul(fdst,1.039999999)))); } template inline T cfFlatLight(T src, T dst) { using namespace Arithmetic; if (src == zeroValue()) { return zeroValue(); } return clamp(cfHardMixPhotoshop(inv(src),dst)==unitValue() ? cfPenumbraB(src,dst) : cfPenumbraA(src,dst)); } template inline void cfAdditionSAI(TReal src, TReal sa, TReal& dst, TReal& da) { using namespace Arithmetic; typedef typename KoColorSpaceMathsTraits::compositetype composite_type; Q_UNUSED(da); composite_type newsrc; newsrc = mul(src, sa); dst = clamp(newsrc + dst); } #endif // KOCOMPOSITEOP_FUNCTIONS_H_ diff --git a/libs/pigment/compositeops/KoCompositeOps.h b/libs/pigment/compositeops/KoCompositeOps.h index b29c4d287e..b264284884 100644 --- a/libs/pigment/compositeops/KoCompositeOps.h +++ b/libs/pigment/compositeops/KoCompositeOps.h @@ -1,364 +1,364 @@ /* * 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 "compositeops/KoAlphaDarkenParamsWrapper.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) { if (useCreamyAlphaDarken()) { return new KoCompositeOpAlphaDarken(cs); } else { return new KoCompositeOpAlphaDarken(cs); } } static KoCompositeOp* createOverOp(const KoColorSpace *cs) { return new KoCompositeOpOver(cs); } }; template<> struct OptimizedOpsSelector { static KoCompositeOp* createAlphaDarkenOp(const KoColorSpace *cs) { return useCreamyAlphaDarken() ? KoOptimizedCompositeOpFactory::createAlphaDarkenOpCreamy32(cs) : KoOptimizedCompositeOpFactory::createAlphaDarkenOpHard32(cs); } static KoCompositeOp* createOverOp(const KoColorSpace *cs) { return KoOptimizedCompositeOpFactory::createOverOp32(cs); } }; template<> struct OptimizedOpsSelector { static KoCompositeOp* createAlphaDarkenOp(const KoColorSpace *cs) { return useCreamyAlphaDarken() ? KoOptimizedCompositeOpFactory::createAlphaDarkenOpCreamy32(cs) : KoOptimizedCompositeOpFactory::createAlphaDarkenOpHard32(cs); } static KoCompositeOp* createOverOp(const KoColorSpace *cs) { return KoOptimizedCompositeOpFactory::createOverOp32(cs); } }; template<> struct OptimizedOpsSelector { static KoCompositeOp* createAlphaDarkenOp(const KoColorSpace *cs) { // TODO: optimized code is disabled for 4.2 release, - // becasue it causes bug https://bugs.kde.org/show_bug.cgi?id=404133 + // because it causes bug https://bugs.kde.org/show_bug.cgi?id=404133 if (useCreamyAlphaDarken()) { return new KoCompositeOpAlphaDarken(cs); } else { return new KoCompositeOpAlphaDarken(cs); } // TODO: please restore this optimized version when the bug is fixed // return useCreamyAlphaDarken() ? // KoOptimizedCompositeOpFactory::createAlphaDarkenOpCreamy128(cs) : // KoOptimizedCompositeOpFactory::createAlphaDarkenOpHard128(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<&cfInterpolation >(cs, COMPOSITE_INTERPOLATION , i18n("Interpolation") , KoCompositeOp::categoryMix()); add<&cfInterpolationB >(cs, COMPOSITE_INTERPOLATIONB , i18n("Interpolation - 2X") , KoCompositeOp::categoryMix()); add<&cfPenumbraA >(cs, COMPOSITE_PENUMBRAA , i18n("Penumbra A") , KoCompositeOp::categoryMix()); add<&cfPenumbraB >(cs, COMPOSITE_PENUMBRAB , i18n("Penumbra B") , KoCompositeOp::categoryMix()); add<&cfPenumbraC >(cs, COMPOSITE_PENUMBRAC , i18n("Penumbra C") , KoCompositeOp::categoryMix()); add<&cfPenumbraD >(cs, COMPOSITE_PENUMBRAD , i18n("Penumbra D") , 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<&cfSoftLightIFSIllusions>(cs, COMPOSITE_SOFT_LIGHT_IFS_ILLUSIONS, i18n("Soft Light (IFS Illusions)") , KoCompositeOp::categoryLight()); add<&cfSoftLightPegtopDelphi>(cs, COMPOSITE_SOFT_LIGHT_PEGTOP_DELPHI, i18n("Soft Light (Pegtop-Delphi)") , 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<&cfGammaIllumination >(cs, COMPOSITE_GAMMA_ILLUMINATION , i18n("Gamma Illumination") , KoCompositeOp::categoryLight()); add<&cfVividLight >(cs, COMPOSITE_VIVID_LIGHT , i18n("Vivid Light") , KoCompositeOp::categoryLight()); add<&cfFlatLight >(cs, COMPOSITE_FLAT_LIGHT , i18n("Flat 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<&cfPNormA >(cs, COMPOSITE_PNORM_A , i18n("P-Norm A") , KoCompositeOp::categoryLight()); add<&cfPNormB >(cs, COMPOSITE_PNORM_B , i18n("P-Norm B") , KoCompositeOp::categoryLight()); add<&cfSuperLight >(cs, COMPOSITE_SUPER_LIGHT , i18n("Super Light") , KoCompositeOp::categoryLight()); add<&cfTintIFSIllusions >(cs, COMPOSITE_TINT_IFS_ILLUSIONS , i18n("Tint (IFS Illusions)") , KoCompositeOp::categoryLight()); add<&cfFogLightenIFSIllusions >(cs, COMPOSITE_FOG_LIGHTEN_IFS_ILLUSIONS , i18n("Fog Lighten (IFS Illusions)") , KoCompositeOp::categoryLight()); add<&cfEasyDodge >(cs, COMPOSITE_EASY_DODGE , i18n("Easy Dodge") , 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<&cfShadeIFSIllusions >(cs, COMPOSITE_SHADE_IFS_ILLUSIONS , i18n("Shade (IFS_Illusions)") , KoCompositeOp::categoryDark()); add<&cfFogDarkenIFSIllusions >(cs, COMPOSITE_FOG_DARKEN_IFS_ILLUSIONS , i18n("Fog Darken (IFS Illusions)") , KoCompositeOp::categoryDark()); add<&cfEasyBurn >(cs, COMPOSITE_EASY_BURN , i18n("Easy Burn") , 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<&cfModulo >(cs, COMPOSITE_MOD , i18n("Modulo") , KoCompositeOp::categoryModulo()); add<&cfModuloContinuous >(cs, COMPOSITE_MOD_CON , i18n("Modulo - Continuous") , KoCompositeOp::categoryModulo()); add<&cfDivisiveModulo >(cs, COMPOSITE_DIVISIVE_MOD , i18n("Divisive Modulo") , KoCompositeOp::categoryModulo()); add<&cfDivisiveModuloContinuous >(cs, COMPOSITE_DIVISIVE_MOD_CON , i18n("Divisive Modulo - Continuous") , KoCompositeOp::categoryModulo()); add<&cfModuloShift >(cs, COMPOSITE_MODULO_SHIFT , i18n("Modulo Shift") , KoCompositeOp::categoryModulo()); add<&cfModuloShiftContinuous >(cs, COMPOSITE_MODULO_SHIFT_CON , i18n("Modulo Shift - Continuous") , KoCompositeOp::categoryModulo()); 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()); add<&cfNegation >(cs, COMPOSITE_NEGATION , i18n("Negation") , KoCompositeOp::categoryNegative()); add<&cfXor >(cs, COMPOSITE_XOR , i18n("XOR") , KoCompositeOp::categoryBinary()); add<&cfOr >(cs, COMPOSITE_OR , i18n("OR") , KoCompositeOp::categoryBinary()); add<&cfAnd >(cs, COMPOSITE_AND , i18n("AND") , KoCompositeOp::categoryBinary()); add<&cfNand >(cs, COMPOSITE_NAND , i18n("NAND") , KoCompositeOp::categoryBinary()); add<&cfNor >(cs, COMPOSITE_NOR , i18n("NOR") , KoCompositeOp::categoryBinary()); add<&cfXnor >(cs, COMPOSITE_XNOR , i18n("XNOR") , KoCompositeOp::categoryBinary()); add<&cfImplies >(cs, COMPOSITE_IMPLICATION , i18n("IMPLICATION") , KoCompositeOp::categoryBinary()); add<&cfNotImplies >(cs, COMPOSITE_NOT_IMPLICATION , i18n("NOT IMPLICATION") , KoCompositeOp::categoryBinary()); add<&cfConverse >(cs, COMPOSITE_CONVERSE , i18n("CONVERSE") , KoCompositeOp::categoryBinary()); add<&cfNotConverse >(cs, COMPOSITE_NOT_CONVERSE , i18n("NOT CONVERSE") , KoCompositeOp::categoryBinary()); add<&cfReflect >(cs, COMPOSITE_REFLECT , i18n("Reflect") , KoCompositeOp::categoryQuadratic()); add<&cfGlow >(cs, COMPOSITE_GLOW , i18n("Glow") , KoCompositeOp::categoryQuadratic()); add<&cfFreeze >(cs, COMPOSITE_FREEZE , i18n("Freeze") , KoCompositeOp::categoryQuadratic()); add<&cfHeat >(cs, COMPOSITE_HEAT , i18n("Heat") , KoCompositeOp::categoryQuadratic()); add<&cfGleat >(cs, COMPOSITE_GLEAT , i18n("Glow-Heat") , KoCompositeOp::categoryQuadratic()); add<&cfHelow >(cs, COMPOSITE_HELOW , i18n("Heat-Glow") , KoCompositeOp::categoryQuadratic()); add<&cfReeze >(cs, COMPOSITE_REEZE , i18n("Reflect-Freeze") , KoCompositeOp::categoryQuadratic()); add<&cfFrect >(cs, COMPOSITE_FRECT , i18n("Freeze-Reflect") , KoCompositeOp::categoryQuadratic()); add<&cfFhyrd >(cs, COMPOSITE_FHYRD , i18n("Heat-Glow & Freeze-Reflect Hybrid") , KoCompositeOp::categoryQuadratic()); 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 , 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()); } }; template struct AddGeneralAlphaOps { static void add(KoColorSpace* cs) { Q_UNUSED(cs); } }; template struct AddGeneralAlphaOps { typedef float 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 KoCompositeOpGenericSCAlpha(cs, id, description, category)); } static void add(KoColorSpace* cs) { add<&cfAdditionSAI >(cs, COMPOSITE_LUMINOSITY_SAI , i18n("Luminosity/Shine (SAI)") , 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); _Private::AddGeneralAlphaOps <_Traits_, useGeneralOps>::add(cs); } template KoCompositeOp* createAlphaDarkenCompositeOp(const KoColorSpace *cs) { return _Private::OptimizedOpsSelector<_Traits_>::createAlphaDarkenOp(cs); } #endif diff --git a/libs/pigment/compositeops/KoStreamedMath.h b/libs/pigment/compositeops/KoStreamedMath.h index 21de9686f7..4cc3116b0a 100644 --- a/libs/pigment/compositeops/KoStreamedMath.h +++ b/libs/pigment/compositeops/KoStreamedMath.h @@ -1,427 +1,427 @@ /* * 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 __KOSTREAMED_MATH_H #define __KOSTREAMED_MATH_H #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 #include #include #include #define BLOCKDEBUG 0 #if !defined _MSC_VER #pragma GCC diagnostic ignored "-Wcast-align" #endif template struct KoStreamedMath { using int_v = Vc::SimdArray; using uint_v = Vc::SimdArray; /** * Composes src into dst without using vector instructions */ template static void genericComposite_novector(const KoCompositeOp::ParameterInfo& params) { using namespace Arithmetic; const qint32 linearInc = pixelSize; qint32 srcLinearInc = params.srcRowStride ? pixelSize : 0; quint8* dstRowStart = params.dstRowStart; const quint8* maskRowStart = params.maskRowStart; const quint8* srcRowStart = params.srcRowStart; typename Compositor::ParamsWrapper paramsWrapper(params); for(quint32 r=params.rows; r>0; --r) { const quint8 *mask = maskRowStart; const quint8 *src = srcRowStart; quint8 *dst = dstRowStart; int blockRest = params.cols; for(int i = 0; i < blockRest; i++) { Compositor::template compositeOnePixelScalar(src, dst, mask, params.opacity, paramsWrapper); src += srcLinearInc; dst += linearInc; if (useMask) { mask++; } } srcRowStart += params.srcRowStride; dstRowStart += params.dstRowStride; if (useMask) { maskRowStart += params.maskRowStride; } } } template static void genericComposite32_novector(const KoCompositeOp::ParameterInfo& params) { genericComposite_novector(params); } template static void genericComposite128_novector(const KoCompositeOp::ParameterInfo& params) { genericComposite_novector(params); } static inline quint8 round_float_to_uint(float value) { return quint8(value + float(0.5)); } static inline quint8 lerp_mixed_u8_float(quint8 a, quint8 b, float alpha) { return round_float_to_uint(qint16(b - a) * alpha + a); } /** * Get a vector containing first Vc::float_v::size() values of mask. * Each source mask element is considered to be a 8-bit integer */ static inline Vc::float_v fetch_mask_8(const quint8 *data) { uint_v data_i(data); return Vc::simd_cast(int_v(data_i)); } /** * Get an alpha values from Vc::float_v::size() pixels 32-bit each * (4 channels, 8 bit per channel). The alpha value is considered * to be stored in the most significant byte of the pixel * * \p aligned controls whether the \p data is fetched using aligned * instruction or not. * 1) Fetching aligned data with unaligned instruction * degrades performance. * 2) Fetching unaligned data with aligned instruction * causes \#GP (General Protection Exception) */ template static inline Vc::float_v fetch_alpha_32(const quint8 *data) { uint_v data_i; if (aligned) { data_i.load((const quint32*)data, Vc::Aligned); } else { data_i.load((const quint32*)data, Vc::Unaligned); } return Vc::simd_cast(int_v(data_i >> 24)); } /** * Get color values from Vc::float_v::size() pixels 32-bit each * (4 channels, 8 bit per channel). The color data is considered * to be stored in the 3 least significant bytes of the pixel. * * \p aligned controls whether the \p data is fetched using aligned * instruction or not. * 1) Fetching aligned data with unaligned instruction * degrades performance. * 2) Fetching unaligned data with aligned instruction * causes \#GP (General Protection Exception) */ template static inline void fetch_colors_32(const quint8 *data, Vc::float_v &c1, Vc::float_v &c2, Vc::float_v &c3) { int_v data_i; if (aligned) { data_i.load((const quint32*)data, Vc::Aligned); } else { data_i.load((const quint32*)data, Vc::Unaligned); } const quint32 lowByteMask = 0xFF; uint_v mask(lowByteMask); c1 = Vc::simd_cast(int_v((data_i >> 16) & mask)); c2 = Vc::simd_cast(int_v((data_i >> 8) & mask)); c3 = Vc::simd_cast(int_v( data_i & mask)); } /** * Pack color and alpha values to Vc::float_v::size() pixels 32-bit each * (4 channels, 8 bit per channel). The color data is considered * to be stored in the 3 least significant bytes of the pixel, alpha - * in the most significant byte * * NOTE: \p data must be aligned pointer! */ static inline void write_channels_32(quint8 *data, Vc::float_v::AsArg alpha, Vc::float_v::AsArg c1, Vc::float_v::AsArg c2, Vc::float_v::AsArg c3) { /** * FIXME: make conversion float->int - * use methematical rounding + * use mathematical rounding */ const quint32 lowByteMask = 0xFF; // FIXME: Use single-instruction rounding + conversion // The achieve that we need to implement Vc::iRound() uint_v mask(lowByteMask); uint_v v1 = uint_v(int_v(Vc::round(alpha))) << 24; uint_v v2 = (uint_v(int_v(Vc::round(c1))) & mask) << 16; uint_v v3 = (uint_v(int_v(Vc::round(c2))) & mask) << 8; uint_v v4 = uint_v(int_v(Vc::round(c3))) & mask; v1 = v1 | v2; v3 = v3 | v4; (v1 | v3).store((quint32*)data, Vc::Aligned); } /** * Composes src pixels into dst pixles. Is optimized for 32-bit-per-pixel * colorspaces. Uses \p Compositor strategy parameter for doing actual * math of the composition */ template static void genericComposite(const KoCompositeOp::ParameterInfo& params) { using namespace Arithmetic; const int vectorSize = Vc::float_v::size(); const qint32 vectorInc = pixelSize * vectorSize; const qint32 linearInc = pixelSize; qint32 srcVectorInc = vectorInc; qint32 srcLinearInc = pixelSize; quint8* dstRowStart = params.dstRowStart; const quint8* maskRowStart = params.maskRowStart; const quint8* srcRowStart = params.srcRowStart; typename Compositor::ParamsWrapper paramsWrapper(params); if (!params.srcRowStride) { if (pixelSize == 4) { quint32 *buf = Vc::malloc(vectorSize); *((uint_v*)buf) = uint_v(*((const quint32*)params.srcRowStart)); srcRowStart = reinterpret_cast(buf); srcLinearInc = 0; srcVectorInc = 0; } else { quint8 *buf = Vc::malloc(vectorInc); quint8 *ptr = buf; for (int i = 0; i < vectorSize; i++) { memcpy(ptr, params.srcRowStart, pixelSize); ptr += pixelSize; } srcRowStart = buf; srcLinearInc = 0; srcVectorInc = 0; } } #if BLOCKDEBUG int totalBlockAlign = 0; int totalBlockAlignedVector = 0; int totalBlockUnalignedVector = 0; int totalBlockRest = 0; #endif for(quint32 r=params.rows; r>0; --r) { // Hint: Mask is allowed to be unaligned const quint8 *mask = maskRowStart; const quint8 *src = srcRowStart; quint8 *dst = dstRowStart; const int pixelsAlignmentMask = vectorSize * sizeof(float) - 1; uintptr_t srcPtrValue = reinterpret_cast(src); uintptr_t dstPtrValue = reinterpret_cast(dst); uintptr_t srcAlignment = srcPtrValue & pixelsAlignmentMask; uintptr_t dstAlignment = dstPtrValue & pixelsAlignmentMask; // Uncomment if facing problems with alignment: // Q_ASSERT_X(!(dstAlignment & 3), "Compositioning", // "Pixel data must be aligned on pixels borders!"); int blockAlign = params.cols; int blockAlignedVector = 0; int blockUnalignedVector = 0; int blockRest = 0; int *vectorBlock = srcAlignment == dstAlignment || !srcVectorInc ? &blockAlignedVector : &blockUnalignedVector; if (!dstAlignment) { blockAlign = 0; *vectorBlock = params.cols / vectorSize; blockRest = params.cols % vectorSize; } else if (params.cols > 2 * vectorSize) { blockAlign = (vectorInc - dstAlignment) / pixelSize; const int restCols = params.cols - blockAlign; if (restCols > 0) { *vectorBlock = restCols / vectorSize; blockRest = restCols % vectorSize; } else { blockAlign = params.cols; *vectorBlock = 0; blockRest = 0; } } #if BLOCKDEBUG totalBlockAlign += blockAlign; totalBlockAlignedVector += blockAlignedVector; totalBlockUnalignedVector += blockUnalignedVector; totalBlockRest += blockRest; #endif for(int i = 0; i < blockAlign; i++) { Compositor::template compositeOnePixelScalar(src, dst, mask, params.opacity, paramsWrapper); src += srcLinearInc; dst += linearInc; if(useMask) { mask++; } } for (int i = 0; i < blockAlignedVector; i++) { Compositor::template compositeVector(src, dst, mask, params.opacity, paramsWrapper); src += srcVectorInc; dst += vectorInc; if (useMask) { mask += vectorSize; } } for (int i = 0; i < blockUnalignedVector; i++) { Compositor::template compositeVector(src, dst, mask, params.opacity, paramsWrapper); src += srcVectorInc; dst += vectorInc; if (useMask) { mask += vectorSize; } } for(int i = 0; i < blockRest; i++) { Compositor::template compositeOnePixelScalar(src, dst, mask, params.opacity, paramsWrapper); src += srcLinearInc; dst += linearInc; if (useMask) { mask++; } } srcRowStart += params.srcRowStride; dstRowStart += params.dstRowStride; if (useMask) { maskRowStart += params.maskRowStride; } } #if BLOCKDEBUG dbgPigment << "I" << "rows:" << params.rows << "\tpad(S):" << totalBlockAlign << "\tbav(V):" << totalBlockAlignedVector << "\tbuv(V):" << totalBlockUnalignedVector << "\tres(S)" << totalBlockRest; // << srcAlignment << dstAlignment; #endif if (!params.srcRowStride) { Vc::free(reinterpret_cast(const_cast(srcRowStart))); } } template static void genericComposite32(const KoCompositeOp::ParameterInfo& params) { genericComposite(params); } template static void genericComposite128(const KoCompositeOp::ParameterInfo& params) { genericComposite(params); } }; namespace KoStreamedMathFunctions { template ALWAYS_INLINE void clearPixel(quint8* dst); template<> ALWAYS_INLINE void clearPixel<4>(quint8* dst) { quint32 *d = reinterpret_cast(dst); *d = 0; } template<> ALWAYS_INLINE void clearPixel<16>(quint8* dst) { quint64 *d = reinterpret_cast(dst); d[0] = 0; d[1] = 0; } template ALWAYS_INLINE void copyPixel(const quint8 *src, quint8* dst); template<> ALWAYS_INLINE void copyPixel<4>(const quint8 *src, quint8* dst) { const quint32 *s = reinterpret_cast(src); quint32 *d = reinterpret_cast(dst); *d = *s; } template<> ALWAYS_INLINE void copyPixel<16>(const quint8 *src, quint8* dst) { const quint64 *s = reinterpret_cast(src); quint64 *d = reinterpret_cast(dst); d[0] = s[0]; d[1] = s[1]; } } #endif /* __KOSTREAMED_MATH_H */ diff --git a/libs/pigment/tests/CCSGraph.cpp b/libs/pigment/tests/CCSGraph.cpp index 55f43011c2..6a574af35e 100644 --- a/libs/pigment/tests/CCSGraph.cpp +++ b/libs/pigment/tests/CCSGraph.cpp @@ -1,119 +1,119 @@ /* * Copyright (c) 2007-2008 Cyrille Berger * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; 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 #include #include #include #include #include "KoColorSpaceRegistry.h" #include "KoColorConversionSystem.h" #include #include #include struct FriendOfColorSpaceRegistry { static QString toDot() { return KoColorSpaceRegistry::instance()->colorConversionSystem()->toDot(); } static QString bestPathToDot(const QString &srcKey, const QString &dstKey) { return KoColorSpaceRegistry::instance()->colorConversionSystem()->bestPathToDot(srcKey, dstKey); } }; int main(int argc, char** argv) { QCoreApplication app(argc, argv); QCommandLineParser parser; parser.addVersionOption(); parser.addHelpOption(); // Initialize the list of options parser.addOption(QCommandLineOption(QStringList() << QLatin1String("graphs"), i18n("return the list of available graphs"))); parser.addOption(QCommandLineOption(QStringList() << QLatin1String("graph"), i18n("specify the type of graph (see --graphs to get the full list, the default is full)"), QLatin1String("type"), QLatin1String("full"))); parser.addOption(QCommandLineOption(QStringList() << QLatin1String("key"), i18n("specify the key of the source color space"), QLatin1String("key"), QString())); parser.addOption(QCommandLineOption(QStringList() << QLatin1String("key"), i18n("specify the key of the destination color space"), QLatin1String("key"), QString())); parser.addOption(QCommandLineOption(QStringList() << QLatin1String("output"), i18n("specify the output (can be ps or dot, the default is ps)"), QLatin1String("type"), QLatin1String("ps"))); parser.addPositionalArgument(QLatin1String("outputfile"), i18n("name of the output file")); parser.process(app); // PORTING SCRIPT: move this to after any parser.addOption if (parser.isSet("graphs")) { // Don't change those lines to use dbgPigment derivatives, they need to be outputted // to stdout not stderr. std::cout << "full : show all the connection on the graph" << std::endl; std::cout << "bestpath : show the best path for a given transformation" << std::endl; exit(EXIT_SUCCESS); } QString graphType = parser.value("graph"); QString outputType = parser.value("output"); if (parser.positionalArguments().count() != 1) { errorPigment << "No output file name specified"; parser.showHelp(); exit(EXIT_FAILURE); } QString outputFileName = parser.positionalArguments()[0]; // Generate the graph QString dot; if (graphType == "full") { dot = FriendOfColorSpaceRegistry::toDot(); } else if (graphType == "bestpath") { QString srcKey = parser.value("src-key"); QString dstKey = parser.value("dst-key"); if (srcKey.isEmpty() || dstKey.isEmpty()) { errorPigment << "src-key and dst-key must be specified for the graph bestpath"; exit(EXIT_FAILURE); } else { dot = FriendOfColorSpaceRegistry::bestPathToDot(srcKey, dstKey); } } else { errorPigment << "Unknown graph type : " << graphType.toLatin1(); exit(EXIT_FAILURE); } if (outputType == "dot") { QFile file(outputFileName); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) exit(EXIT_FAILURE); QTextStream out(&file); out << dot; } else if (outputType == "ps" || outputType == "svg") { QTemporaryFile file; if (!file.open()) { exit(EXIT_FAILURE); } QTextStream out(&file); out << dot; QString cmd = QString("dot -T%1 %2 -o %3").arg(outputType).arg(file.fileName()).arg(outputFileName); file.close(); if (QProcess::execute(cmd) != 0) { - errorPigment << "An error has occurred when executing : '" << cmd << "' the most likely cause is that 'dot' command is missing, and that you should install graphviz (from http://www.graphiz.org)"; + errorPigment << "An error has occurred when executing : '" << cmd << "' the most likely cause is that 'dot' command is missing, and that you should install graphviz (from http://www.graphviz.org)"; } } else { errorPigment << "Unknown output type : " << outputType; exit(EXIT_FAILURE); } } diff --git a/libs/psd/psd.h b/libs/psd/psd.h index de131447f8..acb2b937e2 100644 --- a/libs/psd/psd.h +++ b/libs/psd/psd.h @@ -1,1168 +1,1169 @@ /* * Copyright (c) 2010 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. */ /* * Constants and defines taken from gimp and psdparse */ #ifndef PSD_H #define PSD_H #include #include #include #include #include #include #include #include "kritapsd_export.h" class KoPattern; const int MAX_CHANNELS = 56; typedef qint32 Fixed; /* Represents a fixed point implied decimal */ /** * Image color/depth modes */ enum psd_color_mode { Bitmap = 0, Grayscale=1, Indexed=2, RGB=3, CMYK=4, MultiChannel=7, DuoTone=8, Lab=9, Gray16, RGB48, Lab48, CMYK64, DeepMultichannel, Duotone16, COLORMODE_UNKNOWN = 9000 }; /** * Color samplers, apparently distict from PSDColormode */ namespace psd_color_sampler { enum PSDColorSamplers { RGB, HSB, CMYK, PANTONE, // LAB FOCOLTONE, // CMYK TRUMATCH, // CMYK TOYO, // LAB LAB, GRAYSCALE, HKS, // CMYK DIC, // LAB TOTAL_INK, MONITOR_RGB, DUOTONE, OPACITY, ANPA = 3000 // LAB }; } // EFFECTS enum psd_gradient_style { psd_gradient_style_linear, // 'Lnr ' psd_gradient_style_radial, // 'Rdl ' psd_gradient_style_angle, // 'Angl' psd_gradient_style_reflected, // 'Rflc' psd_gradient_style_diamond // 'Dmnd' }; enum psd_color_stop_type { psd_color_stop_type_foreground_color, // 'FrgC' psd_color_stop_type_background_Color, // 'BckC' psd_color_stop_type_user_stop // 'UsrS' }; enum psd_technique_type { psd_technique_softer, psd_technique_precise, psd_technique_slope_limit, }; enum psd_stroke_position { psd_stroke_outside, psd_stroke_inside, psd_stroke_center }; enum psd_fill_type { psd_fill_solid_color, psd_fill_gradient, psd_fill_pattern, }; enum psd_glow_source { psd_glow_center, psd_glow_edge, }; enum psd_bevel_style { psd_bevel_outer_bevel, psd_bevel_inner_bevel, psd_bevel_emboss, psd_bevel_pillow_emboss, psd_bevel_stroke_emboss, }; enum psd_direction { psd_direction_up, psd_direction_down }; enum psd_section_type { psd_other = 0, psd_open_folder, psd_closed_folder, psd_bounding_divider }; // GRADIENT MAP // Each color stop struct psd_gradient_color_stop { qint32 location; // Location of color stop qint32 midpoint; // Midpoint of color stop QColor actual_color; psd_color_stop_type color_stop_type; }; // Each transparency stop struct psd_gradient_transparency_stop { qint32 location; // Location of transparency stop qint32 midpoint; // Midpoint of transparency stop qint8 opacity; // Opacity of transparency stop }; // Gradient settings (Photoshop 6.0) struct psd_layer_gradient_map { bool reverse; // Is gradient reverse bool dithered; // Is gradient dithered qint32 name_length; quint16 *name; // Name of the gradient: Unicode string, padded qint8 number_color_stops; // Number of color stops to follow psd_gradient_color_stop * color_stop; qint8 number_transparency_stops;// Number of transparency stops to follow psd_gradient_transparency_stop * transparency_stop; qint8 expansion_count; // Expansion count ( = 2 for Photoshop 6.0) qint8 interpolation; // Interpolation if length above is non-zero qint8 length; // Length (= 32 for Photoshop 6.0) qint8 mode; // Mode for this gradient qint32 random_number_seed; // Random number seed qint8 showing_transparency_flag;// Flag for showing transparency qint8 using_vector_color_flag;// Flag for using vector color qint32 roughness_factor; // Roughness factor QColor min_color; QColor max_color; QColor lookup_table[256]; }; struct psd_gradient_color { qint32 smoothness; qint32 name_length; quint16 * name; // Name of the gradient: Unicode string, padded qint8 number_color_stops; // Number of color stops to follow psd_gradient_color_stop * color_stop; qint8 number_transparency_stops;// Number of transparency stops to follow psd_gradient_transparency_stop *transparency_stop; }; struct psd_pattern { psd_color_mode color_mode = Bitmap; // The image mode of the file. quint8 height = 0; // Point: vertical, 2 bytes and horizontal, 2 bytes quint8 width = 0; QString name; QString uuid; qint32 version = 0; quint8 top = 0; // Rectangle: top, left, bottom, right quint8 left = 0; quint8 bottom = 0; quint8 right = 0; qint32 max_channel = 0; // Max channels qint32 channel_number = 0; QVector color_table; }; struct psd_layer_effects_context { psd_layer_effects_context() : keep_original(false) { } bool keep_original; }; #define PSD_LOOKUP_TABLE_SIZE 256 // dsdw, isdw: http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/PhotoshopFileFormats.htm#50577409_22203 class KRITAPSD_EXPORT psd_layer_effects_shadow_base { public: psd_layer_effects_shadow_base() : m_invertsSelection(false) , m_edgeHidden(true) , m_effectEnabled(false) , m_blendMode(COMPOSITE_MULT) , m_color(Qt::black) , m_nativeColor(Qt::black) , m_opacity(75) , m_angle(120) , m_useGlobalLight(true) , m_distance(21) , m_spread(0) , m_size(21) , m_antiAliased(0) , m_noise(0) , m_knocksOut(false) , m_fillType(psd_fill_solid_color) , m_technique(psd_technique_softer) , m_range(100) , m_jitter(0) , m_gradient(0) { for(int i = 0; i < PSD_LOOKUP_TABLE_SIZE; ++i) { m_contourLookupTable[i] = i; } } virtual ~psd_layer_effects_shadow_base() { } QPoint calculateOffset(const psd_layer_effects_context *context) const; void setEffectEnabled(bool value) { m_effectEnabled = value; } bool effectEnabled() const { return m_effectEnabled; } QString blendMode() const { return m_blendMode; } QColor color() const { return m_color; } QColor nativeColor() const { return m_nativeColor; } qint32 opacity() const { return m_opacity; } qint32 angle() const { return m_angle; } bool useGlobalLight() const { return m_useGlobalLight; } qint32 distance() const { return m_distance; } qint32 spread() const { return m_spread; } qint32 size() const { return m_size; } const quint8* contourLookupTable() const { return m_contourLookupTable; } bool antiAliased() const { return m_antiAliased; } qint32 noise() const { return m_noise; } bool knocksOut() const { return m_knocksOut; } bool invertsSelection() const { return m_invertsSelection; } bool edgeHidden() const { return m_edgeHidden; } psd_fill_type fillType() const { return m_fillType; } psd_technique_type technique() const { return m_technique; } qint32 range() const { return m_range; } qint32 jitter() const { return m_jitter; } KoAbstractGradientSP gradient() const { return m_gradient; } public: void setBlendMode(QString value) { m_blendMode = value; } void setColor(QColor value) { m_color = value; } void setNativeColor(QColor value) { m_nativeColor = value; } void setOpacity(qint32 value) { m_opacity = value; } void setAngle(qint32 value) { m_angle = value; } void setUseGlobalLight(bool value) { m_useGlobalLight = value; } void setDistance(qint32 value) { m_distance = value; } void setSpread(qint32 value) { m_spread = value; } void setSize(qint32 value) { m_size = value; } void setContourLookupTable(const quint8* value) { memcpy(m_contourLookupTable, value, PSD_LOOKUP_TABLE_SIZE * sizeof(quint8)); } void setAntiAliased(bool value) { m_antiAliased = value; } void setNoise(qint32 value) { m_noise = value; } void setKnocksOut(bool value) { m_knocksOut = value; } void setInvertsSelection(bool value) { m_invertsSelection = value; } void setEdgeHidden(bool value) { m_edgeHidden = value; } void setFillType(psd_fill_type value) { m_fillType = value; } void setTechnique(psd_technique_type value) { m_technique = value; } void setRange(qint32 value) { m_range = value; } void setJitter(qint32 value) { m_jitter = value; } void setGradient(KoAbstractGradientSP value) { m_gradient = value; } virtual void scaleLinearSizes(qreal scale) { m_distance *= scale; m_size *= scale; } private: // internal bool m_invertsSelection; bool m_edgeHidden; private: bool m_effectEnabled; // Effect enabled QString m_blendMode; // already in Krita format! QColor m_color; QColor m_nativeColor; qint32 m_opacity; // Opacity as a percent (0...100) qint32 m_angle; // Angle in degrees bool m_useGlobalLight; // Use this angle in all of the layer effects qint32 m_distance; // Distance in pixels qint32 m_spread; // Intensity as a percent qint32 m_size; // Blur value in pixels quint8 m_contourLookupTable[PSD_LOOKUP_TABLE_SIZE]; bool m_antiAliased; qint32 m_noise; bool m_knocksOut; // for Outer/Inner Glow psd_fill_type m_fillType; psd_technique_type m_technique; qint32 m_range; qint32 m_jitter; KoAbstractGradientSP m_gradient; }; class KRITAPSD_EXPORT psd_layer_effects_shadow_common : public psd_layer_effects_shadow_base { public: /// FIXME: 'using' is not supported by MSVC, so please refactor in /// some other way to ensure that the setters are not used /// in the classes we don't want // using psd_layer_effects_shadow_base::setBlendMode; // using psd_layer_effects_shadow_base::setColor; // using psd_layer_effects_shadow_base::setOpacity; // using psd_layer_effects_shadow_base::setAngle; // using psd_layer_effects_shadow_base::setUseGlobalLight; // using psd_layer_effects_shadow_base::setDistance; // using psd_layer_effects_shadow_base::setSpread; // using psd_layer_effects_shadow_base::setSize; // using psd_layer_effects_shadow_base::setContourLookupTable; // using psd_layer_effects_shadow_base::setAntiAliased; // using psd_layer_effects_shadow_base::setNoise; }; class KRITAPSD_EXPORT psd_layer_effects_drop_shadow : public psd_layer_effects_shadow_common { public: /// FIXME: 'using' is not supported by MSVC, so please refactor in /// some other way to ensure that the setters are not used /// in the classes we don't want //using psd_layer_effects_shadow_base::setKnocksOut; }; // isdw: http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/PhotoshopFileFormats.htm#50577409_22203 class KRITAPSD_EXPORT psd_layer_effects_inner_shadow : public psd_layer_effects_shadow_common { public: psd_layer_effects_inner_shadow() { - setKnocksOut(true); + setKnocksOut(false); setInvertsSelection(true); setEdgeHidden(false); } }; class KRITAPSD_EXPORT psd_layer_effects_glow_common : public psd_layer_effects_shadow_base { public: psd_layer_effects_glow_common() { setKnocksOut(true); setDistance(0); setBlendMode(COMPOSITE_LINEAR_DODGE); setColor(Qt::white); } /// FIXME: 'using' is not supported by MSVC, so please refactor in /// some other way to ensure that the setters are not used /// in the classes we don't want // using psd_layer_effects_shadow_base::setBlendMode; // using psd_layer_effects_shadow_base::setColor; // using psd_layer_effects_shadow_base::setOpacity; // using psd_layer_effects_shadow_base::setSpread; // using psd_layer_effects_shadow_base::setSize; // using psd_layer_effects_shadow_base::setContourLookupTable; // using psd_layer_effects_shadow_base::setAntiAliased; // using psd_layer_effects_shadow_base::setNoise; // using psd_layer_effects_shadow_base::setFillType; // using psd_layer_effects_shadow_base::setTechnique; // using psd_layer_effects_shadow_base::setRange; // using psd_layer_effects_shadow_base::setJitter; // using psd_layer_effects_shadow_base::setGradient; }; // oglw: http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/PhotoshopFileFormats.htm#50577409_25738 class KRITAPSD_EXPORT psd_layer_effects_outer_glow : public psd_layer_effects_glow_common { }; // iglw: http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/PhotoshopFileFormats.htm#50577409_27692 class KRITAPSD_EXPORT psd_layer_effects_inner_glow : public psd_layer_effects_glow_common { public: psd_layer_effects_inner_glow() : m_source(psd_glow_edge) { setInvertsSelection(true); setEdgeHidden(false); + setKnocksOut(false); } psd_glow_source source() const { return m_source; } void setSource(psd_glow_source value) { m_source = value; } private: psd_glow_source m_source; }; struct psd_layer_effects_satin : public psd_layer_effects_shadow_base { psd_layer_effects_satin() { setInvert(false); setUseGlobalLight(false); setDistance(8); setSize(7); setSpread(0); setKnocksOut(true); setEdgeHidden(false); setBlendMode(COMPOSITE_LINEAR_BURN); } /// FIXME: 'using' is not supported by MSVC, so please refactor in /// some other way to ensure that the setters are not used /// in the classes we don't want // using psd_layer_effects_shadow_base::setBlendMode; // using psd_layer_effects_shadow_base::setColor; // using psd_layer_effects_shadow_base::setOpacity; // // NOTE: no global light setting explicitly! // using psd_layer_effects_shadow_base::setAngle; // using psd_layer_effects_shadow_base::setDistance; // using psd_layer_effects_shadow_base::setSize; // using psd_layer_effects_shadow_base::setContourLookupTable; // using psd_layer_effects_shadow_base::setAntiAliased; bool invert() const { return m_invert; } void setInvert(bool value) { m_invert = value; } private: bool m_invert; }; struct psd_pattern_info { qint32 name_length; quint16 * name; quint8 identifier[256]; }; // bevl: http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/PhotoshopFileFormats.htm#50577409_31889 struct psd_layer_effects_bevel_emboss : public psd_layer_effects_shadow_base { psd_layer_effects_bevel_emboss() : m_style(psd_bevel_inner_bevel), m_technique(psd_technique_softer), m_depth(100), m_direction(psd_direction_up), m_soften(0), m_altitude(30), m_glossAntiAliased(false), m_highlightBlendMode(COMPOSITE_SCREEN), m_highlightColor(Qt::white), m_highlightOpacity(75), m_shadowBlendMode(COMPOSITE_MULT), m_shadowColor(Qt::black), m_shadowOpacity(75), m_contourEnabled(false), m_contourRange(100), m_textureEnabled(false), m_texturePattern(0), m_textureScale(100), m_textureDepth(100), m_textureInvert(false), m_textureAlignWithLayer(true), m_textureHorizontalPhase(0), m_textureVerticalPhase(0) { for(int i = 0; i < PSD_LOOKUP_TABLE_SIZE; ++i) { m_glossContourLookupTable[i] = i; } } /// FIXME: 'using' is not supported by MSVC, so please refactor in /// some other way to ensure that the setters are not used /// in the classes we don't want // using psd_layer_effects_shadow_base::setSize; // using psd_layer_effects_shadow_base::setAngle; // using psd_layer_effects_shadow_base::setUseGlobalLight; // using psd_layer_effects_shadow_base::setContourLookupTable; // using psd_layer_effects_shadow_base::setAntiAliased; psd_bevel_style style() const { return m_style; } void setStyle(psd_bevel_style value) { m_style = value; } psd_technique_type technique() const { return m_technique; } void setTechnique(psd_technique_type value) { m_technique = value; } int depth() const { return m_depth; } void setDepth(int value) { m_depth = value; } psd_direction direction() const { return m_direction; } void setDirection(psd_direction value) { m_direction = value; } int soften() const { return m_soften; } void setSoften(int value) { m_soften = value; } int altitude() const { return m_altitude; } void setAltitude(int value) { m_altitude = value; } const quint8* glossContourLookupTable() const { return m_glossContourLookupTable; } void setGlossContourLookupTable(const quint8 *value) { memcpy(m_glossContourLookupTable, value, PSD_LOOKUP_TABLE_SIZE * sizeof(quint8)); } bool glossAntiAliased() const { return m_glossAntiAliased; } void setGlossAntiAliased(bool value) { m_glossAntiAliased = value; } QString highlightBlendMode() const { return m_highlightBlendMode; } void setHighlightBlendMode(QString value) { m_highlightBlendMode = value; } QColor highlightColor() const { return m_highlightColor; } void setHighlightColor(QColor value) { m_highlightColor = value; } qint32 highlightOpacity() const { return m_highlightOpacity; } void setHighlightOpacity(qint32 value) { m_highlightOpacity = value; } QString shadowBlendMode() const { return m_shadowBlendMode; } void setShadowBlendMode(QString value) { m_shadowBlendMode = value; } QColor shadowColor() const { return m_shadowColor; } void setShadowColor(QColor value) { m_shadowColor = value; } qint32 shadowOpacity() const { return m_shadowOpacity; } void setShadowOpacity(qint32 value) { m_shadowOpacity = value; } bool contourEnabled() const { return m_contourEnabled; } void setContourEnabled(bool value) { m_contourEnabled = value; } int contourRange() const { return m_contourRange; } void setContourRange(int value) { m_contourRange = value; } bool textureEnabled() const { return m_textureEnabled; } void setTextureEnabled(bool value) { m_textureEnabled = value; } KoPattern* texturePattern() const { return m_texturePattern; } void setTexturePattern(KoPattern *value) { m_texturePattern = value; } int textureScale() const { return m_textureScale; } void setTextureScale(int value) { m_textureScale = value; } int textureDepth() const { return m_textureDepth; } void setTextureDepth(int value) { m_textureDepth = value; } bool textureInvert() const { return m_textureInvert; } void setTextureInvert(bool value) { m_textureInvert = value; } bool textureAlignWithLayer() const { return m_textureAlignWithLayer; } void setTextureAlignWithLayer(bool value) { m_textureAlignWithLayer = value; } void setTexturePhase(const QPointF &phase) { m_textureHorizontalPhase = phase.x(); m_textureVerticalPhase = phase.y(); } QPointF texturePhase() const { return QPointF(m_textureHorizontalPhase, m_textureVerticalPhase); } int textureHorizontalPhase() const { return m_textureHorizontalPhase; } void setTextureHorizontalPhase(int value) { m_textureHorizontalPhase = value; } int textureVerticalPhase() const { return m_textureVerticalPhase; } void setTextureVerticalPhase(int value) { m_textureVerticalPhase = value; } void scaleLinearSizes(qreal scale) override { psd_layer_effects_shadow_base::scaleLinearSizes(scale); m_soften *= scale; m_textureScale *= scale; } private: psd_bevel_style m_style; psd_technique_type m_technique; int m_depth; psd_direction m_direction; // Up or down int m_soften; // Blur value in pixels. int m_altitude; quint8 m_glossContourLookupTable[256]; bool m_glossAntiAliased; QString m_highlightBlendMode; // already in Krita format QColor m_highlightColor; qint32 m_highlightOpacity; // Highlight opacity as a percent QString m_shadowBlendMode; // already in Krita format QColor m_shadowColor; qint32 m_shadowOpacity; // Shadow opacity as a percent bool m_contourEnabled; int m_contourRange; bool m_textureEnabled; KoPattern *m_texturePattern; int m_textureScale; int m_textureDepth; bool m_textureInvert; bool m_textureAlignWithLayer; int m_textureHorizontalPhase; // 0..100% int m_textureVerticalPhase; // 0..100% }; struct psd_layer_effects_overlay_base : public psd_layer_effects_shadow_base { psd_layer_effects_overlay_base() : m_scale(100), m_alignWithLayer(true), m_reverse(false), m_style(psd_gradient_style_linear), m_gradientXOffset(0), m_gradientYOffset(0), m_pattern(0), m_horizontalPhase(0), m_verticalPhase(0) { setUseGlobalLight(false); } /// FIXME: 'using' is not supported by MSVC, so please refactor in /// some other way to ensure that the setters are not used /// in the classes we don't want // using psd_layer_effects_shadow_base::setBlendMode; // using psd_layer_effects_shadow_base::setOpacity; int scale() const { return m_scale; } bool alignWithLayer() const { return m_alignWithLayer; } bool reverse() const { return m_reverse; } psd_gradient_style style() const { return m_style; } int gradientXOffset() const { return m_gradientXOffset; } int gradientYOffset() const { return m_gradientYOffset; } KoPattern* pattern() const { return m_pattern; } int horizontalPhase() const { return m_horizontalPhase; } int verticalPhase() const { return m_verticalPhase; } // refactor that public: void setScale(int value) { m_scale = value; } void setAlignWithLayer(bool value) { m_alignWithLayer = value; } void setReverse(bool value) { m_reverse = value; } void setStyle(psd_gradient_style value) { m_style = value; } void setGradientOffset(const QPointF &pt) { m_gradientXOffset = qRound(pt.x()); m_gradientYOffset = qRound(pt.y()); } QPointF gradientOffset() const { return QPointF(m_gradientXOffset, m_gradientYOffset); } void setPattern(KoPattern *value) { m_pattern = value; } void setPatternPhase(const QPointF &phase) { m_horizontalPhase = phase.x(); m_verticalPhase = phase.y(); } QPointF patternPhase() const { return QPointF(m_horizontalPhase, m_verticalPhase); } void scaleLinearSizes(qreal scale) override { psd_layer_effects_shadow_base::scaleLinearSizes(scale); m_scale *= scale; } private: // Gradient+Pattern int m_scale; bool m_alignWithLayer; // Gradient bool m_reverse; psd_gradient_style m_style; int m_gradientXOffset; // 0..100% int m_gradientYOffset; // 0..100% // Pattern KoPattern *m_pattern; int m_horizontalPhase; // 0..100% int m_verticalPhase; // 0..100% protected: /// FIXME: 'using' is not supported by MSVC, so please refactor in /// some other way to ensure that the setters are not used /// in the classes we don't want // must be called in the derived classes' c-tor // using psd_layer_effects_shadow_base::setFillType; }; // sofi: http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/PhotoshopFileFormats.htm#50577409_70055 struct psd_layer_effects_color_overlay : public psd_layer_effects_overlay_base { psd_layer_effects_color_overlay() { setFillType(psd_fill_solid_color); setColor(Qt::white); } /// FIXME: 'using' is not supported by MSVC, so please refactor in /// some other way to ensure that the setters are not used /// in the classes we don't want // using psd_layer_effects_shadow_base::setColor; }; struct psd_layer_effects_gradient_overlay : public psd_layer_effects_overlay_base { psd_layer_effects_gradient_overlay() { setFillType(psd_fill_gradient); setAngle(90); setReverse(false); setScale(100); setAlignWithLayer(true); setStyle(psd_gradient_style_linear); } public: /// FIXME: 'using' is not supported by MSVC, so please refactor in /// some other way to ensure that the setters are not used /// in the classes we don't want // using psd_layer_effects_shadow_base::setGradient; // using psd_layer_effects_shadow_base::setAngle; // using psd_layer_effects_overlay_base::setReverse; // using psd_layer_effects_overlay_base::setScale; // using psd_layer_effects_overlay_base::setAlignWithLayer; // using psd_layer_effects_overlay_base::setStyle; // using psd_layer_effects_overlay_base::setGradientOffset; // using psd_layer_effects_overlay_base::gradientOffset; }; struct psd_layer_effects_pattern_overlay : public psd_layer_effects_overlay_base { psd_layer_effects_pattern_overlay() { setFillType(psd_fill_pattern); setScale(100); setAlignWithLayer(true); } /// FIXME: 'using' is not supported by MSVC, so please refactor in /// some other way to ensure that the setters are not used /// in the classes we don't want // using psd_layer_effects_overlay_base::setScale; // using psd_layer_effects_overlay_base::setAlignWithLayer; // using psd_layer_effects_overlay_base::setPattern; // using psd_layer_effects_overlay_base::setPatternPhase; // using psd_layer_effects_overlay_base::patternPhase; private: // These are unused /*int m_scale; bool m_alignWithLayer; KoPattern *m_pattern; int m_horizontalPhase; int m_verticalPhase;*/ }; struct psd_layer_effects_stroke : public psd_layer_effects_overlay_base { psd_layer_effects_stroke() : m_position(psd_stroke_outside) { setFillType(psd_fill_solid_color); setColor(Qt::black); setAngle(90); setReverse(false); setScale(100); setAlignWithLayer(true); setStyle(psd_gradient_style_linear); setScale(100); setAlignWithLayer(true); } /// FIXME: 'using' is not supported by MSVC, so please refactor in /// some other way to ensure that the setters are not used /// in the classes we don't want // using psd_layer_effects_shadow_base::setFillType; // using psd_layer_effects_shadow_base::setSize; // using psd_layer_effects_shadow_base::setColor; // using psd_layer_effects_shadow_base::setGradient; // using psd_layer_effects_shadow_base::setAngle; // using psd_layer_effects_overlay_base::setReverse; // using psd_layer_effects_overlay_base::setScale; // using psd_layer_effects_overlay_base::setAlignWithLayer; // using psd_layer_effects_overlay_base::setStyle; // using psd_layer_effects_overlay_base::setGradientOffset; // using psd_layer_effects_overlay_base::gradientOffset; // using psd_layer_effects_overlay_base::setPattern; // using psd_layer_effects_overlay_base::setPatternPhase; // using psd_layer_effects_overlay_base::patternPhase; psd_stroke_position position() const { return m_position; } void setPosition(psd_stroke_position value) { m_position = value; } private: psd_stroke_position m_position; }; /** * Convert PsdColorMode to pigment colormodelid and colordepthid. * @see KoColorModelStandardIds * * @return a QPair containing ColorModelId and ColorDepthID */ QPair KRITAPSD_EXPORT psd_colormode_to_colormodelid(psd_color_mode colormode, quint16 channelDepth); /** * Convert the Photoshop blend mode strings to Pigment compositeop id's */ QString KRITAPSD_EXPORT psd_blendmode_to_composite_op(const QString& blendmode); QString KRITAPSD_EXPORT composite_op_to_psd_blendmode(const QString& compositeOp); #endif // PSD_H diff --git a/libs/ui/KisApplication.cpp b/libs/ui/KisApplication.cpp index 4dad1894e1..209a89d592 100644 --- a/libs/ui/KisApplication.cpp +++ b/libs/ui/KisApplication.cpp @@ -1,874 +1,874 @@ /* * 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_MACOS #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 "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 #include "widgets/KisScreenColorPicker.h" #include "KisDlgInternalColorSelector.h" #include #include namespace { const QTime appStartTime(QTime::currentTime()); } class KisApplication::Private { public: Private() {} QPointer splashScreen; KisAutoSaveRecoveryDialog *autosaveDialog {0}; QPointer mainWindow; // The first mainwindow we create on startup bool batchRun {false}; }; class KisApplication::ResetStarting { public: ResetStarting(KisSplashScreen *splash, int fileCount) : m_splash(splash) , m_fileCount(fileCount) { } ~ResetStarting() { if (m_splash) { m_splash->hide(); } } QPointer m_splash; int m_fileCount; }; KisApplication::KisApplication(const QString &key, int &argc, char **argv) : QtSingleApplication(key, argc, argv) , d(new Private) { #ifdef Q_OS_MACOS 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("krita")); 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(); } #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();"; // All Krita's resource types KoResourcePaths::addResourceType("markers", "data", "/styles/"); KoResourcePaths::addResourceType("kis_pics", "data", "/pics/"); KoResourcePaths::addResourceType("kis_images", "data", "/images/"); 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("kis_actions", "data", "/pykrita"); KoResourcePaths::addResourceType("icc_profiles", "data", "/color/icc"); KoResourcePaths::addResourceType("icc_profiles", "data", "/profiles/"); 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"); KoResourcePaths::addResourceType("ko_gamutmasks", "data", "/gamutmasks/", true); // // 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/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/gamutmasks/"); } void KisApplication::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()"; 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();"; KoShapeRegistry* r = KoShapeRegistry::instance(); r->add(new KisShapeSelectionFactory()); KisActionRegistry::instance(); KisFilterRegistry::instance(); KisGeneratorRegistry::instance(); KisPaintOpRegistry::instance(); KoColorSpaceRegistry::instance(); } void KisApplication::loadGuiPlugins() { // qDebug() << "loadGuiPlugins();"; // Load the krita-specific tools setSplashScreenLoadingText(i18n("Loading Plugins for Krita/Tool...")); processEvents(); // 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"; 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"; KisExiv2::initialize(); } bool KisApplication::start(const KisApplicationArguments &args) { KisConfig cfg(false); #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" && opengl != "TRY_OPENGL") { cfg.setCanvasState("OPENGL_FAILED"); } setSplashScreenLoadingText(i18n("Initializing Globals")); processEvents(); initializeGlobals(args); const bool doNewImage = args.doNewImage(); const bool doTemplate = args.doTemplate(); const bool exportAs = args.exportAs(); const bool exportSequence = args.exportSequence(); const QString exportFileName = args.exportFileName(); d->batchRun = (exportAs || exportSequence || !exportFileName.isEmpty()); const bool needsMainWindow = (!exportAs && !exportSequence); // only show the mainWindow when no command-line mode option is passed bool showmainWindow = (!exportAs && !exportSequence); // would be !batchRun; const bool showSplashScreen = !d->batchRun && qEnvironmentVariableIsEmpty("NOSPLASH"); if (showSplashScreen && d->splashScreen) { d->splashScreen->show(); d->splashScreen->repaint(); processEvents(); } KoHashGeneratorProvider::instance()->setGenerator("MD5", new KisMD5Generator()); KConfigGroup group(KSharedConfig::openConfig(), "theme"); Digikam::ThemeManager themeManager; themeManager.setCurrentTheme(group.readEntry("Theme", "Krita dark")); ResetStarting resetStarting(d->splashScreen, args.filenames().count()); // remove the splash when done Q_UNUSED(resetStarting); // Make sure we can save resources and tags setSplashScreenLoadingText(i18n("Adding resource types")); processEvents(); addResourceTypes(); // Load the plugins loadPlugins(); // Load all resources loadResources(); // Load all the tags loadResourceTags(); // Load the gui plugins loadGuiPlugins(); KisPart *kisPart = KisPart::instance(); if (needsMainWindow) { // show a mainWindow asap, if we want that setSplashScreenLoadingText(i18n("Loading Main Window...")); processEvents(); bool sessionNeeded = true; auto sessionMode = cfg.sessionOnStartup(); if (!args.session().isEmpty()) { sessionNeeded = !kisPart->restoreSession(args.session()); } else if (sessionMode == KisConfig::SOS_ShowSessionManager) { showmainWindow = false; sessionNeeded = false; kisPart->showSessionManager(); } else if (sessionMode == KisConfig::SOS_PreviousSession) { KConfigGroup sessionCfg = KSharedConfig::openConfig()->group("session"); const QString &sessionName = sessionCfg.readEntry("previousSession"); sessionNeeded = !kisPart->restoreSession(sessionName); } if (sessionNeeded) { kisPart->startBlankSession(); } if (!args.windowLayout().isEmpty()) { KoResourceServer * rserver = KisResourceServerProvider::instance()->windowLayoutServer(); KisWindowLayoutResource* windowLayout = rserver->resourceByName(args.windowLayout()); if (windowLayout) { windowLayout->applyLayout(); } } if (showmainWindow) { d->mainWindow = kisPart->currentMainwindow(); if (!args.workspace().isEmpty()) { KoResourceServer * rserver = KisResourceServerProvider::instance()->workspaceServer(); KisWorkspaceResource* workspace = rserver->resourceByName(args.workspace()); if (workspace) { d->mainWindow->restoreWorkspace(workspace); } } if (args.canvasOnly()) { d->mainWindow->viewManager()->switchCanvasOnly(true); } if (args.fullScreen()) { d->mainWindow->showFullScreen(); } } else { d->mainWindow = kisPart->createMainWindow(); } } short int numberOfOpenDocuments = 0; // number of documents open // Check for autosave files that can be restored, if we're not running a batchrun (test) if (!d->batchRun) { checkAutosaveFiles(); } setSplashScreenLoadingText(QString()); // done loading, so clear out label processEvents(); //configure the unit manager KisSpinBoxUnitManagerFactory::setDefaultUnitManagerBuilder(new KisDocumentAwareSpinBoxUnitManagerBuilder()); connect(this, &KisApplication::aboutToQuit, &KisSpinBoxUnitManagerFactory::clearUnitManagerBuilder); //ensure the builder is destroyed when the application leave. //the new syntax slot syntax allow to connect to a non q_object static method. // Create a new image, if needed if (doNewImage) { KisDocument *doc = args.image(); if (doc) { kisPart->addDocument(doc); d->mainWindow->addViewAndNotifyLoadingCompleted(doc); } } // Get the command line arguments which we have to parse int argsCount = args.filenames().count(); if (argsCount > 0) { // Loop through arguments for (int argNumber = 0; argNumber < argsCount; argNumber++) { QString fileName = args.filenames().at(argNumber); // are we just trying to open a template? if (doTemplate) { // called in mix with batch options? ignore and silently skip if (d->batchRun) { continue; } if (createNewDocFromTemplate(fileName, d->mainWindow)) { ++numberOfOpenDocuments; } // now try to load } else { if (exportAs) { QString outputMimetype = KisMimeDatabase::mimeTypeForFile(exportFileName, false); if (outputMimetype == "application/octetstream") { dbgKrita << i18n("Mimetype not found, try using the -mimetype option") << endl; return false; } KisDocument *doc = kisPart->createDocument(); doc->setFileBatchMode(d->batchRun); bool result = doc->openUrl(QUrl::fromLocalFile(fileName)); if (!result) { errKrita << "Could not load " << fileName << ":" << doc->errorMessage(); QTimer::singleShot(0, this, SLOT(quit())); return false; } if (exportFileName.isEmpty()) { errKrita << "Export destination is not specified for" << fileName << "Please specify export destination with --export-filename option"; QTimer::singleShot(0, this, SLOT(quit())); return false; } qApp->processEvents(); // For vector layers to be updated doc->setFileBatchMode(true); if (!doc->exportDocumentSync(QUrl::fromLocalFile(exportFileName), outputMimetype.toLatin1())) { errKrita << "Could not export " << fileName << "to" << exportFileName << ":" << doc->errorMessage(); } QTimer::singleShot(0, this, SLOT(quit())); return true; } else if (exportSequence) { KisDocument *doc = kisPart->createDocument(); doc->setFileBatchMode(d->batchRun); doc->openUrl(QUrl::fromLocalFile(fileName)); qApp->processEvents(); // For vector layers to be updated if (!doc->image()->animationInterface()->hasAnimation()) { errKrita << "This file has no animation." << endl; QTimer::singleShot(0, this, SLOT(quit())); return false; } doc->setFileBatchMode(true); int sequenceStart = 0; KisAsyncAnimationFramesSaveDialog exporter(doc->image(), doc->image()->animationInterface()->fullClipRange(), exportFileName, sequenceStart, 0); exporter.setBatchMode(d->batchRun); KisAsyncAnimationFramesSaveDialog::Result result = exporter.regenerateRange(0); if (result == KisAsyncAnimationFramesSaveDialog::RenderFailed) { errKrita << i18n("Failed to render animation frames!") << endl; } QTimer::singleShot(0, this, SLOT(quit())); return true; } 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++; } } } } } } // fixes BUG:369308 - Krita crashing on splash screen when loading. // trying to open a file before Krita has loaded can cause it to hang and crash if (d->splashScreen) { d->splashScreen->displayLinks(true); d->splashScreen->displayRecentFiles(true); } // not calling this before since the program will quit there. return true; } KisApplication::~KisApplication() { } void KisApplication::setSplashScreen(QWidget *splashScreen) { d->splashScreen = qobject_cast(splashScreen); } void KisApplication::setSplashScreenLoadingText(QString textToLoad) { if (d->splashScreen) { //d->splashScreen->loadingLabel->setText(textToLoad); d->splashScreen->setLoadingText(textToLoad); d->splashScreen->repaint(); } } void KisApplication::hideSplashScreen() { if (d->splashScreen) { // hide the splashscreen to see the dialog d->splashScreen->hide(); } } bool KisApplication::notify(QObject *receiver, QEvent *event) { try { return QApplication::notify(receiver, event); } catch (std::exception &e) { qWarning("Error %s sending event %i to object %s", e.what(), event->type(), qPrintable(receiver->objectName())); } catch (...) { qWarning("Error sending event %i to object %s", event->type(), qPrintable(receiver->objectName())); } return false; } void KisApplication::remoteArguments(QByteArray message, QObject *socket) { Q_UNUSED(socket); // check if we have any mainwindow KisMainWindow *mw = qobject_cast(qApp->activeWindow()); if (!mw) { mw = KisPart::instance()->mainWindows().first(); } if (!mw) { return; } KisApplicationArguments args = KisApplicationArguments::deserialize(message); const bool doTemplate = args.doTemplate(); const int argsCount = args.filenames().count(); if (argsCount > 0) { // Loop through arguments for (int argNumber = 0; argNumber < argsCount; ++argNumber) { QString filename = args.filenames().at(argNumber); // are we just trying to open a template? if (doTemplate) { createNewDocFromTemplate(filename, mw); } else if (QFile(filename).exists()) { KisMainWindow::OpenFlags flags = d->batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; mw->openDocument(QUrl::fromLocalFile(filename), flags); } } } } void KisApplication::fileOpenRequested(const QString &url) { KisMainWindow *mainWindow = KisPart::instance()->mainWindows().first(); if (mainWindow) { KisMainWindow::OpenFlags flags = d->batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; mainWindow->openDocument(QUrl::fromLocalFile(url), flags); } } void KisApplication::checkAutosaveFiles() { if (d->batchRun) return; #ifdef Q_OS_WIN QDir dir = QDir::temp(); #else QDir dir = QDir::home(); #endif // 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! // Hidden autosave files QStringList filters = QStringList() << QString(".krita-*-*-autosave.kra"); // all autosave files for our application QStringList autosaveFiles = dir.entryList(filters, QDir::Files | QDir::Hidden); - // Visibile autosave files + // Visible autosave files filters = QStringList() << QString("krita-*-*-autosave.kra"); autosaveFiles += dir.entryList(filters, QDir::Files); // Allow the user to make their selection if (autosaveFiles.size() > 0) { if (d->splashScreen) { // hide the splashscreen to see the dialog d->splashScreen->hide(); } d->autosaveDialog = new KisAutoSaveRecoveryDialog(autosaveFiles, activeWindow()); QDialog::DialogCode result = (QDialog::DialogCode) d->autosaveDialog->exec(); if (result == QDialog::Accepted) { QStringList filesToRecover = d->autosaveDialog->recoverableFiles(); Q_FOREACH (const QString &autosaveFile, autosaveFiles) { if (!filesToRecover.contains(autosaveFile)) { + KisUsageLogger::log(QString("Removing autosave file %1").arg(dir.absolutePath() + "/" + autosaveFile)); QFile::remove(dir.absolutePath() + "/" + autosaveFile); } } autosaveFiles = filesToRecover; } else { autosaveFiles.clear(); } if (autosaveFiles.size() > 0) { QList autosaveUrls; Q_FOREACH (const QString &autoSaveFile, autosaveFiles) { const QUrl url = QUrl::fromLocalFile(dir.absolutePath() + QLatin1Char('/') + autoSaveFile); autosaveUrls << url; } if (d->mainWindow) { Q_FOREACH (const QUrl &url, autosaveUrls) { KisMainWindow::OpenFlags flags = d->batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; d->mainWindow->openDocument(url, flags | KisMainWindow::RecoveryFile); } } } // cleanup delete d->autosaveDialog; d->autosaveDialog = nullptr; } } bool KisApplication::createNewDocFromTemplate(const QString &fileName, KisMainWindow *mainWindow) { QString templatePath; const QUrl templateUrl = QUrl::fromLocalFile(fileName); if (QFile::exists(fileName)) { templatePath = templateUrl.toLocalFile(); dbgUI << "using full path..."; } else { QString desktopName(fileName); const QString templatesResourcePath = QStringLiteral("templates/"); QStringList paths = KoResourcePaths::findAllResources("data", templatesResourcePath + "*/" + desktopName); if (paths.isEmpty()) { paths = KoResourcePaths::findAllResources("data", templatesResourcePath + desktopName); } if (paths.isEmpty()) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("No template found for: %1", desktopName)); } else if (paths.count() > 1) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Too many templates found for: %1", desktopName)); } else { templatePath = paths.at(0); } } if (!templatePath.isEmpty()) { QUrl templateBase; templateBase.setPath(templatePath); KDesktopFile templateInfo(templatePath); QString templateName = templateInfo.readUrl(); QUrl templateURL; templateURL.setPath(templateBase.adjusted(QUrl::RemoveFilename|QUrl::StripTrailingSlash).path() + '/' + templateName); if (templateURL.scheme().isEmpty()) { templateURL.setScheme("file"); } 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/KisDocument.cpp b/libs/ui/KisDocument.cpp index 7f74ee868b..141598f756 100644 --- a/libs/ui/KisDocument.cpp +++ b/libs/ui/KisDocument.cpp @@ -1,2258 +1,2257 @@ /* 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 #include #include #include #include #include // Krita Image #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 "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" #include #include #include "kis_simple_stroke_strategy.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) : q(_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*/) , globalAssistantsColor(KisConfig(true).defaultAssistantsColor()) , savingLock(&savingMutex) , batchMode(false) { if (QLocale().measurementSystem() == QLocale::ImperialSystem) { unit = KoUnit::Inch; } else { unit = KoUnit::Centimeter; } } Private(const Private &rhs, KisDocument *_q) : q(_q) , docInfo(new KoDocumentInfo(*rhs.docInfo, _q)) , importExportManager(new KisImportExportManager(_q)) , autoSaveTimer(new QTimer(_q)) , undoStack(new UndoStack(_q)) , nserver(new KisNameServer(*rhs.nserver)) , preActivatedNode(0) // the node is from another hierarchy! , imageIdleWatcher(2000 /*ms*/) , savingLock(&savingMutex) { copyFromImpl(rhs, _q, CONSTRUCT); } ~Private() { // Don't delete m_d->shapeController because it's in a QObject hierarchy. delete nserver; } KisDocument *q = 0; KoDocumentInfo *docInfo = 0; KoUnit unit; KisImportExportManager *importExportManager = 0; // The filter-manager to use when loading/saving [for the options] QByteArray mimeType; // The actual mimetype of the document QByteArray outputMimeType; // The mimetype to use when saving QTimer *autoSaveTimer; QString lastErrorMessage; // see openFile() QString lastWarningMessage; int autoSaveDelay = 300; // in seconds, 0 to disable. bool modifiedAfterAutosave = false; bool isAutosaving = false; bool disregardAutosaveFailure = false; int autoSaveFailureCount = 0; KUndo2Stack *undoStack = 0; KisGuidesConfig guidesConfig; KisMirrorAxisConfig mirrorAxisConfig; 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; QColor globalAssistantsColor; QList paletteList; bool ownsPaletteList = false; KisGridConfig gridConfig; StdLockableWrapper savingLock; bool modifiedWhileSaving = false; QScopedPointer backgroundSaveDocument; QPointer savingUpdater; QFuture childSavingFuture; KritaUtils::ExportFileJob backgroundSaveJob; bool isRecovered = false; bool batchMode { false }; void syncDecorationsWrapperLayerState(); void setImageAndInitIdleWatcher(KisImageSP _image) { image = _image; imageIdleWatcher.setTrackedImage(image); if (image) { imageIdleConnection.reset( new KisSignalAutoConnection( &imageIdleWatcher, SIGNAL(startedIdleMode()), image.data(), SLOT(explicitRegenerateLevelOfDetail()))); } } void copyFrom(const Private &rhs, KisDocument *q); void copyFromImpl(const Private &rhs, KisDocument *q, KisDocument::CopyPolicy policy); /// clones the palette list oldList /// the ownership of the returned KoColorSet * belongs to the caller QList clonePaletteList(const QList &oldList); class StrippedSafeSavingLocker; }; void KisDocument::Private::syncDecorationsWrapperLayerState() { if (!this->image) return; KisImageSP image = this->image; KisDecorationsWrapperLayerSP decorationsLayer = KisLayerUtils::findNodeByType(image->root()); const bool needsDecorationsWrapper = gridConfig.showGrid() || (guidesConfig.showGuides() && guidesConfig.hasGuides()) || !assistants.isEmpty(); struct SyncDecorationsWrapperStroke : public KisSimpleStrokeStrategy { SyncDecorationsWrapperStroke(KisDocument *document, bool needsDecorationsWrapper) : KisSimpleStrokeStrategy("sync-decorations-wrapper", kundo2_noi18n("start-isolated-mode")), m_document(document), m_needsDecorationsWrapper(needsDecorationsWrapper) { this->enableJob(JOB_INIT, true, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); setClearsRedoOnStart(false); } void initStrokeCallback() { KisDecorationsWrapperLayerSP decorationsLayer = KisLayerUtils::findNodeByType(m_document->image()->root()); if (m_needsDecorationsWrapper && !decorationsLayer) { m_document->image()->addNode(new KisDecorationsWrapperLayer(m_document)); } else if (!m_needsDecorationsWrapper && decorationsLayer) { m_document->image()->removeNode(decorationsLayer); } } private: KisDocument *m_document = 0; bool m_needsDecorationsWrapper = false; }; KisStrokeId id = image->startStroke(new SyncDecorationsWrapperStroke(q, needsDecorationsWrapper)); image->endStroke(id); } void KisDocument::Private::copyFrom(const Private &rhs, KisDocument *q) { copyFromImpl(rhs, q, KisDocument::REPLACE); } void KisDocument::Private::copyFromImpl(const Private &rhs, KisDocument *q, KisDocument::CopyPolicy policy) { if (policy == REPLACE) { delete docInfo; } docInfo = (new KoDocumentInfo(*rhs.docInfo, q)); unit = rhs.unit; mimeType = rhs.mimeType; outputMimeType = rhs.outputMimeType; if (policy == REPLACE) { q->setGuidesConfig(rhs.guidesConfig); q->setMirrorAxisConfig(rhs.mirrorAxisConfig); q->setModified(rhs.modified); q->setAssistants(KisPaintingAssistant::cloneAssistantList(rhs.assistants)); q->setGridConfig(rhs.gridConfig); } else { // in CONSTRUCT mode, we cannot use the functions of KisDocument // because KisDocument does not yet have a pointer to us. guidesConfig = rhs.guidesConfig; mirrorAxisConfig = rhs.mirrorAxisConfig; modified = rhs.modified; assistants = KisPaintingAssistant::cloneAssistantList(rhs.assistants); gridConfig = rhs.gridConfig; } m_bAutoDetectedMime = rhs.m_bAutoDetectedMime; m_url = rhs.m_url; m_file = rhs.m_file; readwrite = rhs.readwrite; firstMod = rhs.firstMod; lastMod = rhs.lastMod; // XXX: the display properties will be shared between different snapshots globalAssistantsColor = rhs.globalAssistantsColor; if (policy == REPLACE) { QList newPaletteList = clonePaletteList(rhs.paletteList); q->setPaletteList(newPaletteList, /* emitSignal = */ true); // we still do not own palettes if we did not } else { paletteList = rhs.paletteList; } batchMode = rhs.batchMode; } QList KisDocument::Private::clonePaletteList(const QList &oldList) { QList newList; Q_FOREACH (KoColorSet *palette, oldList) { newList << new KoColorSet(*palette); } return newList; } 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(QEventLoop::ExcludeUserInputEvents); // one more try... m_locked = std::try_lock(m_imageLock, m_savingLock) < 0; } } ~StrippedSafeSavingLocker() { if (m_locked) { m_imageLock.unlock(); m_savingLock.unlock(); } } bool successfullyLocked() const { return m_locked; } private: bool m_locked; KisImageSP m_image; StdLockableWrapper m_savingLock; KisImageBarrierLockAdapter m_imageLock; }; KisDocument::KisDocument() : d(new Private(this)) { connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); connect(d->undoStack, SIGNAL(cleanChanged(bool)), this, SLOT(slotUndoStackCleanChanged(bool))); connect(d->autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave())); setObjectName(newObjectName()); // preload the krita resources KisResourceServerProvider::instance(); d->shapeController = new KisShapeController(this, d->nserver); d->koShapeController = new KoShapeController(0, d->shapeController); d->shapeController->resourceManager()->setGlobalShapeController(d->koShapeController); slotConfigChanged(); } KisDocument::KisDocument(const KisDocument &rhs) : QObject(), d(new Private(*rhs.d, this)) { copyFromDocumentImpl(rhs, CONSTRUCT); } KisDocument::~KisDocument() { // wait until all the pending operations are in progress waitForSavingToComplete(); /** * Push a timebomb, which will try to release the memory after * the document has been deleted */ KisPaintDevice::createMemoryReleaseObject()->deleteLater(); d->autoSaveTimer->disconnect(this); d->autoSaveTimer->stop(); delete d->importExportManager; // Despite being QObject they needs to be deleted before the image delete d->shapeController; delete d->koShapeController; if (d->image) { d->image->notifyAboutToBeDeleted(); /** * WARNING: We should wait for all the internal image jobs to * finish before entering KisImage's destructor. The problem is, * while execution of KisImage::~KisImage, all the weak shared * pointers pointing to the image enter an inconsistent * state(!). The shared counter is already zero and destruction * has started, but the weak reference doesn't know about it, * because KisShared::~KisShared hasn't been executed yet. So all * the threads running in background and having weak pointers will * enter the KisImage's destructor as well. */ d->image->requestStrokeCancellation(); d->image->waitForDone(); // clear undo commands that can still point to the image d->undoStack->clear(); d->image->waitForDone(); KisImageWSP sanityCheckPointer = d->image; Q_UNUSED(sanityCheckPointer); // The following line trigger the deletion of the image d->image.clear(); // check if the image has actually been deleted KIS_SAFE_ASSERT_RECOVER_NOOP(!sanityCheckPointer.isValid()); } if (d->ownsPaletteList) { qDeleteAll(d->paletteList); } 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, ImportExportCodes::NoAccessToWrite, i18n("%1 cannot be written to. Please save under a different name.", job.filePath)); //return ImportExportCodes::NoAccessToWrite; return false; } KisConfig cfg(true); if (cfg.backupFile() && filePathInfo.exists()) { QString backupDir; switch(cfg.readEntry("backupfilelocation", 0)) { case 1: backupDir = QStandardPaths::writableLocation(QStandardPaths::HomeLocation); break; case 2: backupDir = QStandardPaths::writableLocation(QStandardPaths::TempLocation); break; default: // Do nothing: the empty string is user file location break; } int numOfBackupsKept = cfg.readEntry("numberofbackupfiles", 1); QString suffix = cfg.readEntry("backupfilesuffix", "~"); if (numOfBackupsKept == 1) { if (!KBackup::simpleBackupFile(job.filePath, backupDir, suffix)) { qWarning() << "Failed to create simple backup file!" << job.filePath << backupDir << suffix; KisUsageLogger::log(QString("Failed to create a simple backup for %1 in %2.").arg(job.filePath).arg(backupDir.isEmpty() ? "the same location as the file" : backupDir)); return false; } else { KisUsageLogger::log(QString("Create a simple backup for %1 in %2.").arg(job.filePath).arg(backupDir.isEmpty() ? "the same location as the file" : backupDir)); } } else if (numOfBackupsKept > 2) { if (!KBackup::numberedBackupFile(job.filePath, backupDir, suffix, numOfBackupsKept)) { qWarning() << "Failed to create numbered backup file!" << job.filePath << backupDir << suffix; KisUsageLogger::log(QString("Failed to create a numbered backup for %2.").arg(job.filePath).arg(backupDir.isEmpty() ? "the same location as the file" : backupDir)); return false; } else { KisUsageLogger::log(QString("Create a simple backup for %1 in %2.").arg(job.filePath).arg(backupDir.isEmpty() ? "the same location as the file" : backupDir)); } } } //KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!job.mimeType.isEmpty(), false); if (job.mimeType.isEmpty()) { KisImportExportErrorCode error = ImportExportCodes::FileFormatIncorrect; slotCompleteSavingDocument(job, error, error.errorMessage()); return false; } const QString actionName = job.flags & KritaUtils::SaveIsExporting ? i18n("Exporting Document...") : i18n("Saving Document..."); bool started = initiateSavingInBackground(actionName, this, SLOT(slotCompleteSavingDocument(KritaUtils::ExportFileJob, KisImportExportErrorCode ,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; } KisUsageLogger::log(QString("Exporting Document: %1 as %2. %3 * %4 pixels, %5 layers, %6 frames, %7 framerate. Export configuration: %8") .arg(url.toLocalFile()) .arg(QString::fromLatin1(mimeType)) .arg(d->image->width()) .arg(d->image->height()) .arg(d->image->nlayers()) .arg(d->image->animationInterface()->totalLength()) .arg(d->image->animationInterface()->framerate()) .arg(exportConfiguration ? exportConfiguration->toXML() : "No configuration")); 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; KisUsageLogger::log(QString("Saving Document %9 as %1 (mime: %2). %3 * %4 pixels, %5 layers. %6 frames, %7 framerate. Export configuration: %8") .arg(_url.toLocalFile()) .arg(QString::fromLatin1(mimeType)) .arg(d->image->width()) .arg(d->image->height()) .arg(d->image->nlayers()) .arg(d->image->animationInterface()->totalLength()) .arg(d->image->animationInterface()->framerate()) .arg(exportConfiguration ? exportConfiguration->toXML() : "No configuration") .arg(url().toLocalFile())); 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).isOk()) { qWarning() << "serializeToByteArray():: Could not export to our native format"; } return byteArray; } void KisDocument::slotCompleteSavingDocument(const KritaUtils::ExportFileJob &job, KisImportExportErrorCode status, const QString &errorMessage) { if (status.isCancelled()) return; const QString fileName = QFileInfo(job.filePath).fileName(); if (!status.isOk()) { 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, exportErrorToUserMessage(status, errorMessage))); } } else { if (!(job.flags & KritaUtils::SaveIsExporting)) { const QString existingAutoSaveBaseName = localFilePath(); const bool wasRecovered = isRecovered(); setUrl(QUrl::fromLocalFile(job.filePath)); setLocalFilePath(job.filePath); setMimeType(job.mimeType); updateEditingTime(true); if (!d->modifiedWhileSaving) { /** * If undo stack is already clean/empty, it doesn't emit any * signals, so we might forget update document modified state * (which was set, e.g. while recovering an autosave file) */ if (d->undoStack->isClean()) { setModified(false); } else { d->undoStack->setClean(); } } setRecovered(false); removeAutoSaveFiles(existingAutoSaveBaseName, wasRecovered); } 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(QEventLoop::ExcludeUserInputEvents); 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); } KisDocument *KisDocument::lockAndCreateSnapshot() { KisDocument *doc = lockAndCloneForSaving(); if (doc) { // clone palette list doc->d->paletteList = doc->d->clonePaletteList(doc->d->paletteList); doc->d->ownsPaletteList = true; } return doc; } void KisDocument::copyFromDocument(const KisDocument &rhs) { copyFromDocumentImpl(rhs, REPLACE); } void KisDocument::copyFromDocumentImpl(const KisDocument &rhs, CopyPolicy policy) { if (policy == REPLACE) { d->copyFrom(*(rhs.d), this); d->undoStack->clear(); } else { // in CONSTRUCT mode, d should be already initialized 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())); d->shapeController = new KisShapeController(this, d->nserver); d->koShapeController = new KoShapeController(0, d->shapeController); d->shapeController->resourceManager()->setGlobalShapeController(d->koShapeController); } setObjectName(rhs.objectName()); slotConfigChanged(); if (rhs.d->image) { if (policy == REPLACE) { d->image->barrierLock(/* readOnly = */ false); rhs.d->image->barrierLock(/* readOnly = */ true); d->image->copyFromImage(*(rhs.d->image)); d->image->unlock(); rhs.d->image->unlock(); setCurrentImage(d->image, /* forceInitialUpdate = */ true); } else { // clone the image with keeping the GUIDs of the layers intact // NOTE: we expect the image to be locked! setCurrentImage(rhs.image()->clone(/* exactCopy = */ true), /* forceInitialUpdate = */ false); } } if (rhs.d->preActivatedNode) { QQueue linearizedNodes; KisLayerUtils::recursiveApplyNodes(rhs.d->image->root(), [&linearizedNodes](KisNodeSP node) { linearizedNodes.enqueue(node); }); KisLayerUtils::recursiveApplyNodes(d->image->root(), [&linearizedNodes, &rhs, this](KisNodeSP node) { KisNodeSP refNode = linearizedNodes.dequeue(); if (rhs.d->preActivatedNode.data() == refNode.data()) { d->preActivatedNode = node; } }); } // reinitialize references' signal connection KisReferenceImagesLayerSP referencesLayer = this->referenceImagesLayer(); setReferenceImagesLayer(referencesLayer, false); KisDecorationsWrapperLayerSP decorationsLayer = KisLayerUtils::findNodeByType(d->image->root()); if (decorationsLayer) { decorationsLayer->setDocument(this); } if (policy == REPLACE) { setModified(true); } } bool KisDocument::exportDocumentSync(const QUrl &url, const QByteArray &mimeType, KisPropertiesConfigurationSP exportConfiguration) { { /** - * The caller guarantees that noone else uses the document (usually, - * it is a temporary docuent created specifically for exporting), so + * The caller guarantees that no one else uses the document (usually, + * it is a temporary document created specifically for exporting), so * we don't need to copy or lock the document. Instead we should just * ensure the barrier lock is synced and then released. */ Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image); if (!locker.successfullyLocked()) { return false; } } d->savingImage = d->image; const QString fileName = url.toLocalFile(); KisImportExportErrorCode status = d->importExportManager-> exportDocument(fileName, fileName, mimeType, false, exportConfiguration); d->savingImage = 0; return status.isOk(); } bool KisDocument::initiateSavingInBackground(const QString actionName, const QObject *receiverObject, const char *receiverMethod, const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration) { return initiateSavingInBackground(actionName, receiverObject, receiverMethod, job, exportConfiguration, std::unique_ptr()); } bool KisDocument::initiateSavingInBackground(const QString actionName, const QObject *receiverObject, const char *receiverMethod, const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration, std::unique_ptr &&optionalClonedDocument) { KIS_ASSERT_RECOVER_RETURN_VALUE(job.isValid(), false); QScopedPointer clonedDocument; if (!optionalClonedDocument) { clonedDocument.reset(lockAndCloneForSaving()); } else { clonedDocument.reset(optionalClonedDocument.release()); } // we block saving until the current saving is finished! if (!clonedDocument || !d->savingMutex.tryLock()) { return false; } auto waitForImage = [] (KisImageSP image) { KisMainWindow *window = KisPart::instance()->currentMainwindow(); if (window) { if (window->viewManager()) { window->viewManager()->blockUntilOperationsFinishedForced(image); } } }; { KisNodeSP newRoot = clonedDocument->image()->root(); KIS_SAFE_ASSERT_RECOVER(!KisLayerUtils::hasDelayedNodeWithUpdates(newRoot)) { KisLayerUtils::forceAllDelayedNodesUpdate(newRoot); waitForImage(clonedDocument->image()); } } KIS_SAFE_ASSERT_RECOVER(clonedDocument->image()->isIdle()) { waitForImage(clonedDocument->image()); } 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(KisImportExportErrorCode, QString)), this, SLOT(slotChildCompletedSavingInBackground(KisImportExportErrorCode, QString))); connect(this, SIGNAL(sigCompleteBackgroundSaving(KritaUtils::ExportFileJob, KisImportExportErrorCode, 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(KisImportExportErrorCode status, const QString &errorMessage) { KIS_ASSERT_RECOVER_RETURN(isSaving()); KIS_ASSERT_RECOVER(d->backgroundSaveDocument) { d->savingMutex.unlock(); return; } if (d->backgroundSaveJob.flags & KritaUtils::SaveInAutosaveMode) { d->backgroundSaveDocument->d->isAutosaving = false; } d->backgroundSaveDocument.take()->deleteLater(); KIS_ASSERT_RECOVER(d->backgroundSaveJob.isValid()) { d->savingMutex.unlock(); return; } const KritaUtils::ExportFileJob job = d->backgroundSaveJob; d->backgroundSaveJob = KritaUtils::ExportFileJob(); // unlock at the very end d->savingMutex.unlock(); QFileInfo fi(job.filePath); KisUsageLogger::log(QString("Completed saving %1 (mime: %2). Result: %3. Size: %4. MD5 Hash: %5") .arg(job.filePath) .arg(QString::fromLatin1(job.mimeType)) .arg(!status.isOk() ? exportErrorToUserMessage(status, errorMessage) : "OK") .arg(fi.size()) .arg(QString::fromLatin1(KoMD5Generator().generateHash(job.filePath).toHex()))); emit sigCompleteBackgroundSaving(job, status, errorMessage); } void KisDocument::slotAutoSaveImpl(std::unique_ptr &&optionalClonedDocument) { if (!d->modified || !d->modifiedAfterAutosave) return; const QString autoSaveFileName = generateAutoSaveFileName(localFilePath()); emit statusBarMessage(i18n("Autosaving... %1", autoSaveFileName), successMessageTimeout); KisUsageLogger::log(QString("Autosaving: %1").arg(autoSaveFileName)); const bool hadClonedDocument = bool(optionalClonedDocument); bool started = false; if (d->image->isIdle() || hadClonedDocument) { started = initiateSavingInBackground(i18n("Autosaving..."), this, SLOT(slotCompleteAutoSaving(KritaUtils::ExportFileJob, KisImportExportErrorCode, QString)), KritaUtils::ExportFileJob(autoSaveFileName, nativeFormatMimeType(), KritaUtils::SaveIsExporting | KritaUtils::SaveInAutosaveMode), 0, std::move(optionalClonedDocument)); } else { emit statusBarMessage(i18n("Autosaving postponed: document is busy..."), errorMessageTimeout); } if (!started && !hadClonedDocument && d->autoSaveFailureCount >= 3) { KisCloneDocumentStroke *stroke = new KisCloneDocumentStroke(this); connect(stroke, SIGNAL(sigDocumentCloned(KisDocument*)), this, SLOT(slotInitiateAsyncAutosaving(KisDocument*)), Qt::BlockingQueuedConnection); KisStrokeId strokeId = d->image->startStroke(stroke); d->image->endStroke(strokeId); setInfiniteAutoSaveInterval(); } else if (!started) { setEmergencyAutoSaveInterval(); } else { d->modifiedAfterAutosave = false; } } void KisDocument::slotAutoSave() { slotAutoSaveImpl(std::unique_ptr()); } void KisDocument::slotInitiateAsyncAutosaving(KisDocument *clonedDocument) { slotAutoSaveImpl(std::unique_ptr(clonedDocument)); } void KisDocument::slotCompleteAutoSaving(const KritaUtils::ExportFileJob &job, KisImportExportErrorCode status, const QString &errorMessage) { Q_UNUSED(job); const QString fileName = QFileInfo(job.filePath).fileName(); if (!status.isOk()) { setEmergencyAutoSaveInterval(); emit statusBarMessage(i18nc("%1 --- failing file name, %2 --- error message", "Error during autosaving %1: %2", fileName, exportErrorToUserMessage(status, errorMessage)), errorMessageTimeout); } else { KisConfig cfg(true); d->autoSaveDelay = cfg.autoSaveInterval(); if (!d->modifiedWhileSaving) { d->autoSaveTimer->stop(); // until the next change d->autoSaveFailureCount = 0; } else { setNormalAutoSaveInterval(); } emit statusBarMessage(i18n("Finished autosaving %1", fileName), successMessageTimeout); } } bool KisDocument::startExportInBackground(const QString &actionName, const QString &location, const QString &realLocation, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { d->savingImage = d->image; KisMainWindow *window = KisPart::instance()->currentMainwindow(); if (window) { if (window->viewManager()) { d->savingUpdater = window->viewManager()->createThreadedUpdater(actionName); d->importExportManager->setUpdater(d->savingUpdater); } } KisImportExportErrorCode initializationStatus(ImportExportCodes::OK); d->childSavingFuture = d->importExportManager->exportDocumentAsyc(location, realLocation, mimeType, initializationStatus, showWarnings, exportConfiguration); if (!initializationStatus.isOk()) { if (d->savingUpdater) { d->savingUpdater->cancel(); } d->savingImage.clear(); emit sigBackgroundSavingFinished(initializationStatus, initializationStatus.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(ImportExportCodes::InternalError, ""); return; } KisImportExportErrorCode status = d->childSavingFuture.result(); const QString errorMessage = status.errorMessage(); d->savingImage.clear(); d->childSavingFuture = QFuture(); d->lastErrorMessage.clear(); if (d->savingUpdater) { d->savingUpdater->setProgress(100); } emit sigBackgroundSavingFinished(status, errorMessage); } void KisDocument::setReadWrite(bool readwrite) { d->readwrite = readwrite; setNormalAutoSaveInterval(); Q_FOREACH (KisMainWindow *mainWindow, KisPart::instance()->mainWindows()) { mainWindow->setReadWrite(readwrite); } } void KisDocument::setAutoSaveDelay(int delay) { if (isReadWrite() && delay > 0) { d->autoSaveTimer->start(delay * 1000); } else { d->autoSaveTimer->stop(); } } void KisDocument::setNormalAutoSaveInterval() { setAutoSaveDelay(d->autoSaveDelay); d->autoSaveFailureCount = 0; } void KisDocument::setEmergencyAutoSaveInterval() { const int emergencyAutoSaveInterval = 10; /* sec */ setAutoSaveDelay(emergencyAutoSaveInterval); d->autoSaveFailureCount++; } void KisDocument::setInfiniteAutoSaveInterval() { setAutoSaveDelay(-1); } KoDocumentInfo *KisDocument::documentInfo() const { return d->docInfo; } bool KisDocument::isModified() const { return d->modified; } QPixmap KisDocument::generatePreview(const QSize& size) { KisImageSP image = d->image; if (d->savingImage) image = d->savingImage; if (image) { QRect bounds = image->bounds(); QSize newSize = bounds.size(); newSize.scale(size, Qt::KeepAspectRatio); QPixmap px = QPixmap::fromImage(image->convertToQImage(newSize, 0)); if (px.size() == QSize(0,0)) { px = QPixmap(newSize); QPainter gc(&px); QBrush checkBrush = QBrush(KisCanvasWidgetBase::createCheckersImage(newSize.width() / 5)); gc.fillRect(px.rect(), checkBrush); gc.end(); } return px; } return QPixmap(size); } QString KisDocument::generateAutoSaveFileName(const QString & path) const { QString retval; // Using the extension allows to avoid relying on the mime magic when opening const QString extension (".kra"); QString prefix = KisConfig(true).readEntry("autosavefileshidden") ? QString(".") : QString(); QRegularExpression autosavePattern1("^\\..+-autosave.kra$"); QRegularExpression autosavePattern2("^.+-autosave.kra$"); QFileInfo fi(path); QString dir = fi.absolutePath(); QString filename = fi.fileName(); if (path.isEmpty() || autosavePattern1.match(filename).hasMatch() || autosavePattern2.match(filename).hasMatch() || !fi.isWritable()) { // 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%7%3-%4-%5-autosave%6").arg(QDir::tempPath()).arg(QDir::separator()).arg("krita").arg(qApp->applicationPid()).arg(objectName()).arg(extension).arg(prefix); #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%7%3-%4-%5-autosave%6").arg(QDir::homePath()).arg(QDir::separator()).arg("krita").arg(qApp->applicationPid()).arg(objectName()).arg(extension).arg(prefix); #endif } else { retval = QString("%1%2%5%3-autosave%4").arg(dir).arg(QDir::separator()).arg(filename).arg(extension).arg(prefix); } //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; + //qDebug() <<"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 : + KisUsageLogger::log(QString("Removing autosave file: %1").arg(asf)); 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 (ret) { if (!(flags & DontAddToRecent)) { KisPart::instance()->addRecentURLToAllMainWindows(_url); } // 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()) && !fileBatchMode()) { 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(); KoUpdaterPtr updater; if (window && window->viewManager()) { updater = window->viewManager()->createUnthreadedUpdater(i18n("Opening document")); d->importExportManager->setUpdater(updater); } KisImportExportErrorCode status = d->importExportManager->importDocument(localFilePath(), typeName); if (!status.isOk()) { if (window && window->viewManager()) { updater->cancel(); } QString msg = status.errorMessage(); if (!msg.isEmpty() && !fileBatchMode()) { 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() && !fileBatchMode()) { DlgLoadMessages dlg(i18nc("@title:window", "Krita"), i18n("There were problems opening %1.", prettyPathOrUrl()), warningMessage().split("\n")); dlg.exec(); setUrl(QUrl()); } setMimeTypeAfterLoading(typeName); d->syncDecorationsWrapperLayerState(); emit sigLoadingFinished(); undoStack()->clear(); return true; } void KisDocument::autoSaveOnPause() { if (!d->modified || !d->modifiedAfterAutosave) return; const QString autoSaveFileName = generateAutoSaveFileName(localFilePath()); QUrl url("file:/" + autoSaveFileName); bool started = exportDocumentSync(url, nativeFormatMimeType()); if (started) { d->modifiedAfterAutosave = false; dbgAndroid << "autoSaveOnPause successful"; } else { qWarning() << "Could not auto-save when paused"; } } // 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(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(const QString &autosaveBaseName, bool wasRecovered) { - //qDebug() << "removeAutoSaveFiles"; // Eliminate any auto-save file QString asf = generateAutoSaveFileName(autosaveBaseName); // the one in the current dir - - //qDebug() << "\tfilename:" << asf << "exists:" << QFile::exists(asf); if (QFile::exists(asf)) { - //qDebug() << "\tremoving autosavefile" << asf; + KisUsageLogger::log(QString("Removing autosave file: %1").arg(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; + KisUsageLogger::log(QString("Removing autosave file: %1").arg(asf)); QFile::remove(asf); } QList expressions; expressions << QRegularExpression("^\\..+-autosave.kra$") << QRegularExpression("^.+-autosave.kra$"); Q_FOREACH(const QRegularExpression &rex, expressions) { if (wasRecovered && !autosaveBaseName.isEmpty() && rex.match(QFileInfo(autosaveBaseName).fileName()).hasMatch() && QFile::exists(autosaveBaseName)) { + KisUsageLogger::log(QString("Removing autosave file: %1").arg(autosaveBaseName)); QFile::remove(autosaveBaseName); } } } 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(true); if (d->undoStack->undoLimit() != cfg.undoStackLimit()) { if (!d->undoStack->isClean()) { d->undoStack->clear(); } d->undoStack->setUndoLimit(cfg.undoStackLimit()); } d->autoSaveDelay = cfg.autoSaveInterval(); setNormalAutoSaveInterval(); } void KisDocument::slotImageRootChanged() { d->syncDecorationsWrapperLayerState(); } void KisDocument::clearUndoHistory() { d->undoStack->clear(); } KisGridConfig KisDocument::gridConfig() const { return d->gridConfig; } void KisDocument::setGridConfig(const KisGridConfig &config) { if (d->gridConfig != config) { d->gridConfig = config; d->syncDecorationsWrapperLayerState(); emit sigGridConfigChanged(config); } } QList &KisDocument::paletteList() { return d->paletteList; } void KisDocument::setPaletteList(const QList &paletteList, bool emitSignal) { if (d->paletteList != paletteList) { QList oldPaletteList = d->paletteList; d->paletteList = paletteList; if (emitSignal) { emit sigPaletteListChanged(oldPaletteList, paletteList); } } } const KisGuidesConfig& KisDocument::guidesConfig() const { return d->guidesConfig; } void KisDocument::setGuidesConfig(const KisGuidesConfig &data) { if (d->guidesConfig == data) return; d->guidesConfig = data; d->syncDecorationsWrapperLayerState(); emit sigGuidesConfigChanged(d->guidesConfig); } const KisMirrorAxisConfig& KisDocument::mirrorAxisConfig() const { return d->mirrorAxisConfig; } void KisDocument::setMirrorAxisConfig(const KisMirrorAxisConfig &config) { if (d->mirrorAxisConfig == config) { return; } d->mirrorAxisConfig = config; setModified(true); emit sigMirrorAxisConfigChanged(); } 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, KisConfig::BackgroundStyle bgStyle, int numberOfLayers, const QString &description, const double imageResolution) { Q_ASSERT(cs); KisImageSP image; 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); KisLayerSP layer; if (bgStyle == KisConfig::RASTER_LAYER || bgStyle == KisConfig::FILL_LAYER) { KoColor strippedAlpha = bgColor; strippedAlpha.setOpacity(OPACITY_OPAQUE_U8); if (bgStyle == KisConfig::RASTER_LAYER) { layer = new KisPaintLayer(image.data(), "Background", OPACITY_OPAQUE_U8, cs);; layer->paintDevice()->setDefaultPixel(strippedAlpha); } else if (bgStyle == KisConfig::FILL_LAYER) { KisFilterConfigurationSP filter_config = KisGeneratorRegistry::instance()->get("color")->defaultConfiguration(); filter_config->setProperty("color", strippedAlpha.toQColor()); layer = new KisGeneratorLayer(image.data(), "Background Fill", filter_config, image->globalSelection()); } layer->setOpacity(bgColor.opacityU8()); if (numberOfLayers > 1) { //Lock bg layer if others are present. layer->setUserLocked(true); } } else { // KisConfig::CANVAS_COLOR (needs an unlocked starting layer). image->setDefaultProjectionColor(bgColor); layer = new KisPaintLayer(image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8, cs); } Q_CHECK_PTR(layer); image->addNode(layer.data(), image->rootLayer().data()); layer->setDirty(QRect(0, 0, width, height)); 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)); } KisConfig cfg(false); 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()); - KisUsageLogger::log(i18n("Created image \"%1\", %2 * %3 pixels, %4 dpi. Color model: %6 %5 (%7). Layers: %8" - , name - , width, height - , imageResolution * 72.0 - , image->colorSpace()->colorModelId().name(), image->colorSpace()->colorDepthId().name() - , image->colorSpace()->profile()->name() - , numberOfLayers)); + KisUsageLogger::log(QString("Created image \"%1\", %2 * %3 pixels, %4 dpi. Color model: %6 %5 (%7). Layers: %8") + .arg(name) + .arg(width).arg(height) + .arg(imageResolution * 72.0) + .arg(image->colorSpace()->colorModelId().name()) + .arg(image->colorSpace()->colorDepthId().name()) + .arg(image->colorSpace()->profile()->name()) + .arg(numberOfLayers)); 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); } } KoShapeControllerBase *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) { if (d->assistants != value) { d->assistants = value; d->syncDecorationsWrapperLayerState(); emit sigAssistantsChanged(); } } KisReferenceImagesLayerSP KisDocument::referenceImagesLayer() const { if (!d->image) return KisReferenceImagesLayerSP(); KisReferenceImagesLayerSP referencesLayer = KisLayerUtils::findNodeByType(d->image->root()); return referencesLayer; } void KisDocument::setReferenceImagesLayer(KisSharedPtr layer, bool updateImage) { KisReferenceImagesLayerSP currentReferenceLayer = referenceImagesLayer(); if (currentReferenceLayer == layer) { return; } if (currentReferenceLayer) { currentReferenceLayer->disconnect(this); } if (updateImage) { if (currentReferenceLayer) { d->image->removeNode(currentReferenceLayer); } if (layer) { d->image->addNode(layer); } } currentReferenceLayer = layer; if (currentReferenceLayer) { connect(currentReferenceLayer, SIGNAL(sigUpdateCanvas(QRectF)), this, SIGNAL(sigReferenceImagesChanged())); } emit sigReferenceImagesLayerChanged(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->setUndoStore(new KisDumbUndoStore()); d->image->disconnect(this); d->shapeController->setImage(0); d->image = 0; } if (!image) return; d->setImageAndInitIdleWatcher(image); d->image->setUndoStore(new KisDocumentUndoStore(this)); d->shapeController->setImage(image); setModified(false); connect(d->image, SIGNAL(sigImageModified()), this, SLOT(setImageModified()), Qt::UniqueConnection); connect(d->image, SIGNAL(sigLayersChangedAsync()), this, SLOT(slotImageRootChanged())); 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() { // we only set as modified if undo stack is not at clean state setModified(!d->undoStack->isClean()); } KisUndoStore* KisDocument::createUndoStore() { return new KisDocumentUndoStore(this); } bool KisDocument::isAutosaving() const { return d->isAutosaving; } QString KisDocument::exportErrorToUserMessage(KisImportExportErrorCode status, const QString &errorMessage) { return errorMessage.isEmpty() ? status.errorMessage() : errorMessage; } void KisDocument::setAssistantsGlobalColor(QColor color) { d->globalAssistantsColor = color; } QColor KisDocument::assistantsGlobalColor() { return d->globalAssistantsColor; } QRectF KisDocument::documentBounds() const { QRectF bounds = d->image->bounds(); KisReferenceImagesLayerSP referenceImagesLayer = this->referenceImagesLayer(); if (referenceImagesLayer) { bounds |= referenceImagesLayer->boundingImageRect(); } return bounds; } diff --git a/libs/ui/KisImageBuilderResult.h b/libs/ui/KisImageBuilderResult.h deleted file mode 100644 index cbe8f74a25..0000000000 --- a/libs/ui/KisImageBuilderResult.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2016 Laszlo Fazekas - * - * 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_IMAGEBUILDER_RESULT_H -#define KIS_IMAGEBUILDER_RESULT_H - -/** - * Image import/export plugins can use these results to report about success or failure. - */ -enum KisImageBuilder_Result { - KisImageBuilder_RESULT_FAILURE = -400, - KisImageBuilder_RESULT_NOT_EXIST = -300, - KisImageBuilder_RESULT_NOT_LOCAL = -200, - KisImageBuilder_RESULT_BAD_FETCH = -100, - KisImageBuilder_RESULT_INVALID_ARG = -50, - KisImageBuilder_RESULT_OK = 0, - KisImageBuilder_RESULT_PROGRESS = 1, - KisImageBuilder_RESULT_CANCEL = 50, - KisImageBuilder_RESULT_EMPTY = 100, - KisImageBuilder_RESULT_BUSY = 150, - KisImageBuilder_RESULT_NO_URI = 200, - KisImageBuilder_RESULT_UNSUPPORTED = 300, - KisImageBuilder_RESULT_INTR = 400, - KisImageBuilder_RESULT_PATH = 500, - KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE = 600 -}; - - - -#endif diff --git a/libs/ui/KisImportExportFilter.cpp b/libs/ui/KisImportExportFilter.cpp index 2885797d7a..d8c5bf8b19 100644 --- a/libs/ui/KisImportExportFilter.cpp +++ b/libs/ui/KisImportExportFilter.cpp @@ -1,349 +1,290 @@ /* This file is part of the KDE libraries Copyright (C) 2001 Werner Trobin 2002 Werner Trobin 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 "KisImportExportFilter.h" #include #include #include #include #include "KisImportExportManager.h" #include #include #include #include #include "KoUpdater.h" #include #include "kis_config.h" #include #include const QString KisImportExportFilter::ImageContainsTransparencyTag = "ImageContainsTransparency"; const QString KisImportExportFilter::ColorModelIDTag = "ColorModelID"; const QString KisImportExportFilter::ColorDepthIDTag = "ColorDepthID"; const QString KisImportExportFilter::sRGBTag = "sRGB"; class Q_DECL_HIDDEN KisImportExportFilter::Private { public: QPointer updater; QByteArray mime; QString filename; QString realFilename; bool batchmode; QMap capabilities; Private() : updater(0), mime("") , batchmode(false) {} ~Private() { qDeleteAll(capabilities); } }; KisImportExportFilter::KisImportExportFilter(QObject *parent) : QObject(parent) , d(new Private) { } KisImportExportFilter::~KisImportExportFilter() { if (d->updater) { d->updater->setProgress(100); } delete d; } QString KisImportExportFilter::filename() const { return d->filename; } QString KisImportExportFilter::realFilename() const { return d->realFilename; } bool KisImportExportFilter::batchMode() const { return d->batchmode; } void KisImportExportFilter::setBatchMode(bool batchmode) { d->batchmode = batchmode; } void KisImportExportFilter::setFilename(const QString &filename) { d->filename = filename; } void KisImportExportFilter::setRealFilename(const QString &filename) { d->realFilename = filename; } void KisImportExportFilter::setMimeType(const QString &mime) { d->mime = mime.toLatin1(); } QByteArray KisImportExportFilter::mimeType() const { return d->mime; } -QString KisImportExportFilter::conversionStatusString(ConversionStatus status) -{ - QString msg; - switch (status) { - case OK: break; - - case FilterCreationError: - msg = i18n("Krita does not support this file format"); break; - - case CreationError: - msg = i18n("Could not create the output document"); break; - - case FileNotFound: - msg = i18n("File not found"); break; - - case StorageCreationError: - msg = i18n("Cannot create storage"); break; - - case BadMimeType: - msg = i18n("Bad MIME type"); break; - - case WrongFormat: - msg = i18n("Format not recognized"); break; - - case NotImplemented: - msg = i18n("Not implemented"); break; - - case ParsingError: - msg = i18n("Parsing error"); break; - - case InvalidFormat: - msg = i18n("Invalid file format"); break; - - case InternalError: - case UsageError: - msg = i18n("Internal error"); break; - - case ProgressCancelled: - msg = i18n("Cancelled by user"); break; - - case BadConversionGraph: - - msg = i18n("Unknown file type"); break; - - case UnsupportedVersion: - - msg = i18n("Unsupported file version"); break; - - case UserCancelled: - - // intentionally we do not prompt the error message here - break; - - - default: msg = i18n("Unknown error"); break; - } - return msg; -} - KisPropertiesConfigurationSP KisImportExportFilter::defaultConfiguration(const QByteArray &from, const QByteArray &to) const { Q_UNUSED(from); Q_UNUSED(to); return 0; } KisPropertiesConfigurationSP KisImportExportFilter::lastSavedConfiguration(const QByteArray &from, const QByteArray &to) const { KisPropertiesConfigurationSP cfg = defaultConfiguration(from, to); const QString filterConfig = KisConfig(true).exportConfigurationXML(to); if (cfg && !filterConfig.isEmpty()) { cfg->fromXML(filterConfig, false); } return cfg; } KisConfigWidget *KisImportExportFilter::createConfigurationWidget(QWidget *, const QByteArray &from, const QByteArray &to) const { Q_UNUSED(from); Q_UNUSED(to); return 0; } QMap KisImportExportFilter::exportChecks() { qDeleteAll(d->capabilities); initializeCapabilities(); return d->capabilities; } QString KisImportExportFilter::verify(const QString &fileName) const { QFileInfo fi(fileName); if (!fi.exists()) { return i18n("%1 does not exist after writing. Try saving again under a different name, in another location.", fileName); } if (!fi.isReadable()) { return i18n("%1 is not readable", fileName); } if (fi.size() < 10) { return i18n("%1 is smaller than 10 bytes, it must be corrupt. Try saving again under a different name, in another location.", fileName); } QFile f(fileName); f.open(QFile::ReadOnly); QByteArray ba = f.read(std::min(f.size(), (qint64)1000)); bool found = false; for(int i = 0; i < ba.size(); ++i) { if (ba.at(i) > 0) { found = true; break; } } if (!found) { return i18n("%1 has only zero bytes in the first 1000 bytes, it's probably corrupt. Try saving again under a different name, in another location.", fileName); } return QString(); } void KisImportExportFilter::setUpdater(QPointer updater) { d->updater = updater; } QPointer KisImportExportFilter::updater() { return d->updater; } void KisImportExportFilter::setProgress(int value) { if (d->updater) { d->updater->setValue(value); } } void KisImportExportFilter::initializeCapabilities() { // XXX: Initialize everything to fully supported? } void KisImportExportFilter::addCapability(KisExportCheckBase *capability) { d->capabilities[capability->id()] = capability; } void KisImportExportFilter::addSupportedColorModels(QList > supportedColorModels, const QString &name, KisExportCheckBase::Level level) { Q_ASSERT(level != KisExportCheckBase::SUPPORTED); QString layerMessage; QString imageMessage; QList allColorModels = KoColorSpaceRegistry::instance()->colorModelsList(KoColorSpaceRegistry::AllColorSpaces); Q_FOREACH(const KoID &colorModelID, allColorModels) { QList allColorDepths = KoColorSpaceRegistry::instance()->colorDepthList(colorModelID.id(), KoColorSpaceRegistry::AllColorSpaces); Q_FOREACH(const KoID &colorDepthID, allColorDepths) { KisExportCheckFactory *colorModelCheckFactory = KisExportCheckRegistry::instance()->get("ColorModelCheck/" + colorModelID.id() + "/" + colorDepthID.id()); KisExportCheckFactory *colorModelPerLayerCheckFactory = KisExportCheckRegistry::instance()->get("ColorModelPerLayerCheck/" + colorModelID.id() + "/" + colorDepthID.id()); if(!colorModelCheckFactory || !colorModelPerLayerCheckFactory) { qWarning() << "No factory for" << colorModelID << colorDepthID; continue; } if (supportedColorModels.contains(QPair(colorModelID, colorDepthID))) { addCapability(colorModelCheckFactory->create(KisExportCheckBase::SUPPORTED)); addCapability(colorModelPerLayerCheckFactory->create(KisExportCheckBase::SUPPORTED)); } else { if (level == KisExportCheckBase::PARTIALLY) { imageMessage = i18nc("image conversion warning", "%1 cannot save images with color model %2 and depth %3. The image will be converted." ,name, colorModelID.name(), colorDepthID.name()); layerMessage = i18nc("image conversion warning", "%1 cannot save layers with color model %2 and depth %3. The layers will be converted or skipped." ,name, colorModelID.name(), colorDepthID.name()); } else { imageMessage = i18nc("image conversion warning", "%1 cannot save images with color model %2 and depth %3. The image will not be saved." ,name, colorModelID.name(), colorDepthID.name()); layerMessage = i18nc("image conversion warning", "%1 cannot save layers with color model %2 and depth %3. The layers will be skipped." , name, colorModelID.name(), colorDepthID.name()); } addCapability(colorModelCheckFactory->create(level, imageMessage)); addCapability(colorModelPerLayerCheckFactory->create(level, layerMessage)); } } } } QString KisImportExportFilter::verifyZiPBasedFiles(const QString &fileName, const QStringList &filesToCheck) const { QScopedPointer store(KoStore::createStore(fileName, KoStore::Read, KIS_MIME_TYPE, KoStore::Zip)); if (!store || store->bad()) { return i18n("Could not open the saved file %1. Please try to save again in a different location.", fileName); } Q_FOREACH(const QString &file, filesToCheck) { if (!store->hasFile(file)) { return i18n("File %1 is missing in %2 and is broken. Please try to save again in a different location.", file, fileName); } } return QString(); } diff --git a/libs/ui/KisImportExportFilter.h b/libs/ui/KisImportExportFilter.h index 455f47cb35..d7b0947d95 100644 --- a/libs/ui/KisImportExportFilter.h +++ b/libs/ui/KisImportExportFilter.h @@ -1,191 +1,160 @@ /* This file is part of the Calligra libraries Copyright (C) 2001 Werner Trobin 2002 Werner Trobin 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_IMPORT_EXPORT_FILTER_H #define KIS_IMPORT_EXPORT_FILTER_H -#include "KisImageBuilderResult.h" - #include #include #include #include #include #include #include #include #include #include #include #include class KoUpdater; class KisDocument; class KisConfigWidget; #include "kritaui_export.h" #include "KisImportExportErrorCode.h" /** * @brief The base class for import and export filters. * * Derive your filter class from this base class and implement * the @ref convert() method. Don't forget to specify the Q_OBJECT * macro in your class even if you don't use signals or slots. * This is needed as filters are created on the fly. * * @note Take care: The m_chain pointer is invalid while the constructor * runs due to the implementation -- @em don't use it in the constructor. * After the constructor, when running the @ref convert() method it's * guaranteed to be valid, so no need to check against 0. * * @note If the code is compiled in debug mode, setting CALLIGRA_DEBUG_FILTERS * environment variable to any value disables deletion of temporary files while * importing/exporting. This is useful for testing purposes. * * @author Werner Trobin * @todo the class has no constructor and therefore cannot initialize its private class */ class KRITAUI_EXPORT KisImportExportFilter : public QObject { Q_OBJECT public: static const QString ImageContainsTransparencyTag; static const QString ColorModelIDTag; static const QString ColorDepthIDTag; static const QString sRGBTag; public: - /** - * This enum is used to signal the return state of your filter. - * Return OK in @ref convert() in case everything worked as expected. - * Feel free to add some more error conditions @em before the last item - * if it's needed. - */ - enum ConversionStatus { OK, - UsageError, - CreationError, - FileNotFound, - StorageCreationError, - BadMimeType, - BadConversionGraph, - WrongFormat, - NotImplemented, - ParsingError, - InternalError, - UserCancelled, - InvalidFormat, - FilterCreationError, - ProgressCancelled, - UnsupportedVersion, - JustInCaseSomeBrokenCompilerUsesLessThanAByte = 255 - }; ~KisImportExportFilter() override; void setBatchMode(bool batchmode); void setFilename(const QString &filename); void setRealFilename(const QString &filename); void setMimeType(const QString &mime); void setUpdater(QPointer updater); QPointer updater(); /** * The filter chain calls this method to perform the actual conversion. * The passed mimetypes should be a pair of those you specified in your * .desktop file. * You @em have to implement this method to make the filter work. * * @return The error status, see the @ref #ConversionStatus enum. * KisImportExportFilter::OK means that everything is alright. */ virtual KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) = 0; - /** - * Get the text version of the status value - */ - static QString conversionStatusString(ConversionStatus status); - /** * @brief defaultConfiguration defines the default settings for the given import export filter * @param from The mimetype of the source file/document * @param to The mimetype of the destination file/document * @return a serializable KisPropertiesConfiguration object */ virtual KisPropertiesConfigurationSP defaultConfiguration(const QByteArray& from = "", const QByteArray& to = "") const; /** * @brief lastSavedConfiguration return the last saved configuration for this filter * @param from The mimetype of the source file/document * @param to The mimetype of the destination file/document * @return a serializable KisPropertiesConfiguration object */ KisPropertiesConfigurationSP lastSavedConfiguration(const QByteArray &from = "", const QByteArray &to = "") const; /** * @brief createConfigurationWidget creates a widget that can be used to define the settings for a given import/export filter * @param parent the owner of the widget; the caller is responsible for deleting * @param from The mimetype of the source file/document * @param to The mimetype of the destination file/document * * @return the widget */ virtual KisConfigWidget *createConfigurationWidget(QWidget *parent, const QByteArray& from = "", const QByteArray& to = "") const; /** * @brief generate and return the list of capabilities of this export filter. The list * @return returns the list of capabilities of this export filter */ virtual QMap exportChecks(); /// Override and return false for the filters that use a library that cannot handle file handles, only file names. virtual bool supportsIO() const { return true; } /// Verify whether the given file is correct and readable virtual QString verify(const QString &fileName) const; protected: /** * This is the constructor your filter has to call, obviously. */ KisImportExportFilter(QObject *parent = 0); QString filename() const; QString realFilename() const; bool batchMode() const; QByteArray mimeType() const; void setProgress(int value); virtual void initializeCapabilities(); void addCapability(KisExportCheckBase *capability); void addSupportedColorModels(QList > supportedColorModels, const QString &name, KisExportCheckBase::Level level = KisExportCheckBase::PARTIALLY); QString verifyZiPBasedFiles(const QString &fileName, const QStringList &filesToCheck) const; private: KisImportExportFilter(const KisImportExportFilter& rhs); KisImportExportFilter& operator=(const KisImportExportFilter& rhs); class Private; Private *const d; }; #endif diff --git a/libs/ui/KisImportExportManager.cpp b/libs/ui/KisImportExportManager.cpp index daa749ef19..88b62a3c9e 100644 --- a/libs/ui/KisImportExportManager.cpp +++ b/libs/ui/KisImportExportManager.cpp @@ -1,725 +1,725 @@ /* * 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 "KisImportExportManager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_config.h" #include "KisImportExportFilter.h" #include "KisDocument.h" #include #include #include "kis_painter.h" #include "kis_guides_config.h" #include "kis_grid_config.h" #include "kis_popup_button.h" #include #include "kis_async_action_feedback.h" #include "KisReferenceImagesLayer.h" // static cache for import and export mimetypes QStringList KisImportExportManager::m_importMimeTypes; QStringList KisImportExportManager::m_exportMimeTypes; class Q_DECL_HIDDEN KisImportExportManager::Private { public: KoUpdaterPtr updater; QString cachedExportFilterMimeType; QSharedPointer cachedExportFilter; }; struct KisImportExportManager::ConversionResult { ConversionResult() { } ConversionResult(const QFuture &futureStatus) : m_isAsync(true), m_futureStatus(futureStatus) { } ConversionResult(KisImportExportErrorCode status) : m_isAsync(false), m_status(status) { } bool isAsync() const { return m_isAsync; } QFuture futureStatus() const { // if the result is not async, then it means some failure happened, // just return a cancelled future KIS_SAFE_ASSERT_RECOVER_NOOP(m_isAsync || !m_status.isOk()); return m_futureStatus; } KisImportExportErrorCode status() const { return m_status; } void setStatus(KisImportExportErrorCode value) { m_status = value; } private: bool m_isAsync = false; QFuture m_futureStatus; KisImportExportErrorCode m_status = ImportExportCodes::InternalError; }; KisImportExportManager::KisImportExportManager(KisDocument* document) : m_document(document) , d(new Private) { } KisImportExportManager::~KisImportExportManager() { delete d; } KisImportExportErrorCode KisImportExportManager::importDocument(const QString& location, const QString& mimeType) { ConversionResult result = convert(Import, location, location, mimeType, false, 0, false); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!result.isAsync(), ImportExportCodes::InternalError); return result.status(); } KisImportExportErrorCode KisImportExportManager::exportDocument(const QString& location, const QString& realLocation, const QByteArray& mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { ConversionResult result = convert(Export, location, realLocation, mimeType, showWarnings, exportConfiguration, false); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!result.isAsync(), ImportExportCodes::InternalError); return result.status(); } QFuture KisImportExportManager::exportDocumentAsyc(const QString &location, const QString &realLocation, const QByteArray &mimeType, KisImportExportErrorCode &status, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { ConversionResult result = convert(Export, location, realLocation, mimeType, showWarnings, exportConfiguration, true); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(result.isAsync() || !result.status().isOk(), QFuture()); status = result.status(); return result.futureStatus(); } // The static method to figure out to which parts of the // graph this mimetype has a connection to. QStringList KisImportExportManager::supportedMimeTypes(Direction direction) { // Find the right mimetype by the extension QSet mimeTypes; // mimeTypes << KisDocument::nativeFormatMimeType() << "application/x-krita-paintoppreset" << "image/openraster"; if (direction == KisImportExportManager::Import) { if (m_importMimeTypes.isEmpty()) { QListlist = KoJsonTrader::instance()->query("Krita/FileFilter", ""); Q_FOREACH(QPluginLoader *loader, list) { QJsonObject json = loader->metaData().value("MetaData").toObject(); Q_FOREACH(const QString &mimetype, json.value("X-KDE-Import").toString().split(",", QString::SkipEmptyParts)) { //qDebug() << "Adding import mimetype" << mimetype << KisMimeDatabase::descriptionForMimeType(mimetype) << "from plugin" << loader; mimeTypes << mimetype; } } qDeleteAll(list); m_importMimeTypes = mimeTypes.toList(); } return m_importMimeTypes; } else if (direction == KisImportExportManager::Export) { if (m_exportMimeTypes.isEmpty()) { QListlist = KoJsonTrader::instance()->query("Krita/FileFilter", ""); Q_FOREACH(QPluginLoader *loader, list) { QJsonObject json = loader->metaData().value("MetaData").toObject(); Q_FOREACH(const QString &mimetype, json.value("X-KDE-Export").toString().split(",", QString::SkipEmptyParts)) { //qDebug() << "Adding export mimetype" << mimetype << KisMimeDatabase::descriptionForMimeType(mimetype) << "from plugin" << loader; mimeTypes << mimetype; } } qDeleteAll(list); m_exportMimeTypes = mimeTypes.toList(); } return m_exportMimeTypes; } return QStringList(); } KisImportExportFilter *KisImportExportManager::filterForMimeType(const QString &mimetype, KisImportExportManager::Direction direction) { int weight = -1; KisImportExportFilter *filter = 0; QListlist = KoJsonTrader::instance()->query("Krita/FileFilter", ""); Q_FOREACH(QPluginLoader *loader, list) { QJsonObject json = loader->metaData().value("MetaData").toObject(); QString directionKey = direction == Export ? "X-KDE-Export" : "X-KDE-Import"; if (json.value(directionKey).toString().split(",", QString::SkipEmptyParts).contains(mimetype)) { KLibFactory *factory = qobject_cast(loader->instance()); if (!factory) { warnUI << loader->errorString(); continue; } QObject* obj = factory->create(0); if (!obj || !obj->inherits("KisImportExportFilter")) { delete obj; continue; } KisImportExportFilter *f = qobject_cast(obj); if (!f) { delete obj; continue; } int w = json.value("X-KDE-Weight").toInt(); if (w > weight) { delete filter; filter = f; f->setObjectName(loader->fileName()); weight = w; } } } qDeleteAll(list); if (filter) { filter->setMimeType(mimetype); } return filter; } bool KisImportExportManager::batchMode(void) const { return m_document->fileBatchMode(); } void KisImportExportManager::setUpdater(KoUpdaterPtr updater) { d->updater = updater; } QString KisImportExportManager::askForAudioFileName(const QString &defaultDir, QWidget *parent) { KoFileDialog dialog(parent, KoFileDialog::ImportFiles, "ImportAudio"); if (!defaultDir.isEmpty()) { dialog.setDefaultDir(defaultDir); } QStringList mimeTypes; mimeTypes << "audio/mpeg"; mimeTypes << "audio/ogg"; mimeTypes << "audio/vorbis"; mimeTypes << "audio/vnd.wave"; mimeTypes << "audio/flac"; dialog.setMimeTypeFilters(mimeTypes); - dialog.setCaption(i18nc("@titile:window", "Open Audio")); + dialog.setCaption(i18nc("@title:window", "Open Audio")); return dialog.filename(); } KisImportExportManager::ConversionResult KisImportExportManager::convert(KisImportExportManager::Direction direction, const QString &location, const QString& realLocation, const QString &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration, bool isAsync) { // export configuration is supported for export only KIS_SAFE_ASSERT_RECOVER_NOOP(direction == Export || !bool(exportConfiguration)); QString typeName = mimeType; if (typeName.isEmpty()) { typeName = KisMimeDatabase::mimeTypeForFile(location, direction == KisImportExportManager::Export ? false : true); } QSharedPointer filter; /** * Fetching a filter from the registry is a really expensive operation, * because it blocks all the threads. Cache the filter if possible. */ if (direction == KisImportExportManager::Export && d->cachedExportFilter && d->cachedExportFilterMimeType == typeName) { filter = d->cachedExportFilter; } else { filter = toQShared(filterForMimeType(typeName, direction)); if (direction == Export) { d->cachedExportFilter = filter; d->cachedExportFilterMimeType = typeName; } } if (!filter) { return KisImportExportErrorCode(ImportExportCodes::FileFormatIncorrect); } filter->setFilename(location); filter->setRealFilename(realLocation); filter->setBatchMode(batchMode()); filter->setMimeType(typeName); if (!d->updater.isNull()) { // WARNING: The updater is not guaranteed to be persistent! If you ever want // to add progress reporting to "Save also as .kra", make sure you create // a separate KoProgressUpdater for that! // WARNING2: the failsafe completion of the updater happens in the destructor // the filter. filter->setUpdater(d->updater); } QByteArray from, to; if (direction == Export) { from = m_document->nativeFormatMimeType(); to = typeName.toLatin1(); } else { from = typeName.toLatin1(); to = m_document->nativeFormatMimeType(); } KIS_ASSERT_RECOVER_RETURN_VALUE( direction == Import || direction == Export, KisImportExportErrorCode(ImportExportCodes::InternalError)); // "bad conversion graph" ConversionResult result = KisImportExportErrorCode(ImportExportCodes::OK); if (direction == Import) { KisUsageLogger::log(QString("Importing %1 to %2. Location: %3. Real location: %4. Batchmode: %5") .arg(QString::fromLatin1(from)) .arg(QString::fromLatin1(to)) .arg(location) .arg(realLocation) .arg(batchMode())); // async importing is not yet supported! KIS_SAFE_ASSERT_RECOVER_NOOP(!isAsync); // FIXME: Dmitry says "this progress reporting code never worked. Initial idea was to implement it his way, but I stopped and didn't finish it" if (0 && !batchMode()) { KisAsyncActionFeedback f(i18n("Opening document..."), 0); result = f.runAction(std::bind(&KisImportExportManager::doImport, this, location, filter)); } else { result = doImport(location, filter); } if (result.status().isOk()) { KisImageSP image = m_document->image(); KisUsageLogger::log(QString("Loaded image from %1. Size: %2 * %3 pixels, %4 dpi. Color model: %6 %5 (%7). Layers: %8") .arg(QString::fromLatin1(from)) .arg(image->width()) .arg(image->height()) .arg(image->xRes()) .arg(image->colorSpace()->colorModelId().name()) .arg(image->colorSpace()->colorDepthId().name()) .arg(image->colorSpace()->profile()->name()) .arg(image->nlayers())); } else { KisUsageLogger::log(QString("Failed to load image from %1").arg(QString::fromLatin1(from))); } } else /* if (direction == Export) */ { if (!exportConfiguration) { exportConfiguration = filter->lastSavedConfiguration(from, to); } if (exportConfiguration) { fillStaticExportConfigurationProperties(exportConfiguration); } bool alsoAsKra = false; bool askUser = askUserAboutExportConfiguration(filter, exportConfiguration, from, to, batchMode(), showWarnings, &alsoAsKra); if (!batchMode() && !askUser) { return KisImportExportErrorCode(ImportExportCodes::Cancelled); } KisUsageLogger::log(QString("Converting from %1 to %2. Location: %3. Real location: %4. Batchmode: %5. Configuration: %6") .arg(QString::fromLatin1(from)) .arg(QString::fromLatin1(to)) .arg(location) .arg(realLocation) .arg(batchMode()) .arg(exportConfiguration ? exportConfiguration->toXML() : "none")); if (isAsync) { result = QtConcurrent::run(std::bind(&KisImportExportManager::doExport, this, location, filter, exportConfiguration, alsoAsKra)); // we should explicitly report that the exporting has been initiated result.setStatus(ImportExportCodes::OK); } else if (!batchMode()) { KisAsyncActionFeedback f(i18n("Saving document..."), 0); result = f.runAction(std::bind(&KisImportExportManager::doExport, this, location, filter, exportConfiguration, alsoAsKra)); } else { result = doExport(location, filter, exportConfiguration, alsoAsKra); } if (exportConfiguration && !batchMode() && showWarnings) { KisConfig(false).setExportConfiguration(typeName, exportConfiguration); } } return result; } void KisImportExportManager::fillStaticExportConfigurationProperties(KisPropertiesConfigurationSP exportConfiguration, KisImageSP image) { KisPaintDeviceSP dev = image->projection(); const KoColorSpace* cs = dev->colorSpace(); const bool isThereAlpha = KisPainter::checkDeviceHasTransparency(image->projection()); exportConfiguration->setProperty(KisImportExportFilter::ImageContainsTransparencyTag, isThereAlpha); exportConfiguration->setProperty(KisImportExportFilter::ColorModelIDTag, cs->colorModelId().id()); exportConfiguration->setProperty(KisImportExportFilter::ColorDepthIDTag, cs->colorDepthId().id()); const bool sRGB = (cs->profile()->name().contains(QLatin1String("srgb"), Qt::CaseInsensitive) && !cs->profile()->name().contains(QLatin1String("g10"))); exportConfiguration->setProperty(KisImportExportFilter::sRGBTag, sRGB); } void KisImportExportManager::fillStaticExportConfigurationProperties(KisPropertiesConfigurationSP exportConfiguration) { return fillStaticExportConfigurationProperties(exportConfiguration, m_document->image()); } bool KisImportExportManager::askUserAboutExportConfiguration( QSharedPointer filter, KisPropertiesConfigurationSP exportConfiguration, const QByteArray &from, const QByteArray &to, const bool batchMode, const bool showWarnings, bool *alsoAsKra) { // prevents the animation renderer from running this code const QString mimeUserDescription = KisMimeDatabase::descriptionForMimeType(to); QStringList warnings; QStringList errors; { KisPreExportChecker checker; checker.check(m_document->image(), filter->exportChecks()); warnings = checker.warnings(); errors = checker.errors(); } KisConfigWidget *wdg = 0; if (QThread::currentThread() == qApp->thread()) { wdg = filter->createConfigurationWidget(0, from, to); KisMainWindow *kisMain = KisPart::instance()->currentMainwindow(); if (wdg && kisMain) { KisViewManager *manager = kisMain->viewManager(); wdg->setView(manager); } } // Extra checks that cannot be done by the checker, because the checker only has access to the image. if (!m_document->assistants().isEmpty() && to != m_document->nativeFormatMimeType()) { warnings.append(i18nc("image conversion warning", "The image contains assistants. The assistants will not be saved.")); } if (m_document->referenceImagesLayer() && m_document->referenceImagesLayer()->shapeCount() > 0 && to != m_document->nativeFormatMimeType()) { warnings.append(i18nc("image conversion warning", "The image contains reference images. The reference images will not be saved.")); } if (m_document->guidesConfig().hasGuides() && to != m_document->nativeFormatMimeType()) { warnings.append(i18nc("image conversion warning", "The image contains guides. The guides will not be saved.")); } if (!m_document->gridConfig().isDefault() && to != m_document->nativeFormatMimeType()) { warnings.append(i18nc("image conversion warning", "The image contains a custom grid configuration. The configuration will not be saved.")); } if (!batchMode && !errors.isEmpty()) { QString error = "

" + i18n("Error: cannot save this image as a %1.", mimeUserDescription) + " " + i18n("Reasons:") + "

" + "

    "; Q_FOREACH(const QString &w, errors) { error += "\n
  • " + w + "
  • "; } error += "
"; QMessageBox::critical(KisPart::instance()->currentMainwindow(), i18nc("@title:window", "Krita: Export Error"), error); return false; } if (!batchMode && (wdg || !warnings.isEmpty())) { KoDialog dlg; dlg.setButtons(KoDialog::Ok | KoDialog::Cancel); dlg.setWindowTitle(mimeUserDescription); QWidget *page = new QWidget(&dlg); QVBoxLayout *layout = new QVBoxLayout(page); if (showWarnings && !warnings.isEmpty()) { QHBoxLayout *hLayout = new QHBoxLayout(); QLabel *labelWarning = new QLabel(); labelWarning->setPixmap(KisIconUtils::loadIcon("warning").pixmap(32, 32)); hLayout->addWidget(labelWarning); KisPopupButton *bn = new KisPopupButton(0); bn->setText(i18nc("Keep the extra space at the end of the sentence, please", "Warning: saving as %1 will lose information from your image. ", mimeUserDescription)); hLayout->addWidget(bn); layout->addLayout(hLayout); QTextBrowser *browser = new QTextBrowser(); browser->setMinimumWidth(bn->width()); bn->setPopupWidget(browser); QString warning = "

" + i18n("You will lose information when saving this image as a %1.", mimeUserDescription); if (warnings.size() == 1) { warning += " " + i18n("Reason:") + "

"; } else { warning += "
" + i18n("Reasons:") + "

"; } warning += "

    "; Q_FOREACH(const QString &w, warnings) { warning += "\n
  • " + w + "
  • "; } warning += "
"; browser->setHtml(warning); } if (wdg) { QGroupBox *box = new QGroupBox(i18n("Options")); QVBoxLayout *boxLayout = new QVBoxLayout(box); wdg->setConfiguration(exportConfiguration); boxLayout->addWidget(wdg); layout->addWidget(box); } QCheckBox *chkAlsoAsKra = 0; if (showWarnings && !warnings.isEmpty()) { chkAlsoAsKra = new QCheckBox(i18n("Also save your image as a Krita file.")); chkAlsoAsKra->setChecked(KisConfig(true).readEntry("AlsoSaveAsKra", false)); layout->addWidget(chkAlsoAsKra); } dlg.setMainWidget(page); dlg.resize(dlg.minimumSize()); if (showWarnings || wdg) { if (!dlg.exec()) { return false; } } *alsoAsKra = false; if (chkAlsoAsKra) { KisConfig(false).writeEntry("AlsoSaveAsKra", chkAlsoAsKra->isChecked()); *alsoAsKra = chkAlsoAsKra->isChecked(); } if (wdg) { *exportConfiguration = *wdg->configuration(); } } return true; } KisImportExportErrorCode KisImportExportManager::doImport(const QString &location, QSharedPointer filter) { QFile file(location); if (!file.exists()) { return ImportExportCodes::FileNotExist; } if (filter->supportsIO() && !file.open(QFile::ReadOnly)) { return KisImportExportErrorCode(KisImportExportErrorCannotRead(file.error())); } KisImportExportErrorCode status = filter->convert(m_document, &file, KisPropertiesConfigurationSP()); if (file.isOpen()) { file.close(); } return status; } KisImportExportErrorCode KisImportExportManager::doExport(const QString &location, QSharedPointer filter, KisPropertiesConfigurationSP exportConfiguration, bool alsoAsKra) { KisImportExportErrorCode status = doExportImpl(location, filter, exportConfiguration); if (alsoAsKra && status.isOk()) { QString kraLocation = location + ".kra"; QByteArray mime = m_document->nativeFormatMimeType(); QSharedPointer filter( filterForMimeType(QString::fromLatin1(mime), Export)); KIS_SAFE_ASSERT_RECOVER_NOOP(filter); if (filter) { filter->setFilename(kraLocation); KisPropertiesConfigurationSP kraExportConfiguration = filter->lastSavedConfiguration(mime, mime); status = doExportImpl(kraLocation, filter, kraExportConfiguration); } else { status = ImportExportCodes::FileFormatIncorrect; } } return status; } // Temporary workaround until QTBUG-57299 is fixed. // 02-10-2019 update: the bug is closed, but we've still seen this issue. // and without using QSaveFile the issue can still occur // when QFile::copy fails because Dropbox/Google/OneDrive // locks the target file. #ifndef Q_OS_WIN #define USE_QSAVEFILE #endif KisImportExportErrorCode KisImportExportManager::doExportImpl(const QString &location, QSharedPointer filter, KisPropertiesConfigurationSP exportConfiguration) { #ifdef USE_QSAVEFILE QSaveFile file(location); file.setDirectWriteFallback(true); if (filter->supportsIO() && !file.open(QFile::WriteOnly)) { #else QFileInfo fi(location); QTemporaryFile file(fi.absolutePath() + ".XXXXXX.kra"); if (filter->supportsIO() && !file.open()) { #endif KisImportExportErrorCannotWrite result(file.error()); #ifdef USE_QSAVEFILE file.cancelWriting(); #endif return result; } KisImportExportErrorCode status = filter->convert(m_document, &file, exportConfiguration); if (filter->supportsIO()) { if (!status.isOk()) { #ifdef USE_QSAVEFILE file.cancelWriting(); #endif } else { #ifdef USE_QSAVEFILE if (!file.commit()) { qWarning() << "Could not commit QSaveFile"; status = KisImportExportErrorCannotWrite(file.error()); } #else file.flush(); file.close(); QFile target(location); if (target.exists()) { // There should already be a .kra~ backup target.remove(); } if (!file.copy(location)) { file.setAutoRemove(false); return KisImportExportErrorCannotWrite(file.error()); } #endif } } // Do some minimal verification QString verificationResult = filter->verify(location); if (!verificationResult.isEmpty()) { status = KisImportExportErrorCode(ImportExportCodes::ErrorWhileWriting); m_document->setErrorMessage(verificationResult); } return status; } #include diff --git a/libs/ui/KisMainWindow.cpp b/libs/ui/KisMainWindow.cpp index a2dcee367f..90eb33725c 100644 --- a/libs/ui/KisMainWindow.cpp +++ b/libs/ui/KisMainWindow.cpp @@ -1,2764 +1,2764 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Torben Weis Copyright (C) 2000-2006 David Faure Copyright (C) 2007, 2009 Thomas zander Copyright (C) 2010 Benjamin Port 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" #include // qt includes #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 "kis_selection_manager.h" #include "kis_icon_utils.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KoDockFactoryBase.h" #include "KoDocumentInfoDlg.h" #include "KoDocumentInfo.h" #include "KoFileDialog.h" #include #include #include #include #include #include "KoToolDocker.h" #include "KoToolBoxDocker_p.h" #include #include #include #include #include #include #ifdef Q_OS_ANDROID #include #endif #include #include "dialogs/kis_about_application.h" #include "dialogs/kis_delayed_save_dialog.h" #include "dialogs/kis_dlg_preferences.h" #include "kis_action_manager.h" #include "KisApplication.h" #include "kis_canvas2.h" #include "kis_canvas_controller.h" #include "kis_canvas_resource_provider.h" #include "kis_clipboard.h" #include "kis_config.h" #include "kis_config_notifier.h" #include "kis_custom_image_widget.h" #include #include "kis_group_layer.h" #include "kis_image_from_clipboard_widget.h" #include "kis_image.h" #include #include "KisImportExportManager.h" #include "kis_mainwindow_observer.h" #include "kis_memory_statistics_server.h" #include "kis_node.h" #include "KisOpenPane.h" #include "kis_paintop_box.h" #include "KisPart.h" #include "KisPrintJob.h" #include "KisResourceServerProvider.h" #include "kis_signal_compressor_with_param.h" #include "kis_statusbar.h" #include "KisView.h" #include "KisViewManager.h" #include "thememanager.h" #include "kis_animation_importer.h" #include "dialogs/kis_dlg_import_image_sequence.h" #include #include "KisWindowLayoutManager.h" #include #include "KisWelcomePageWidget.h" #include #include #include "KisCanvasWindow.h" #include "kis_action.h" #include class ToolDockerFactory : public KoDockFactoryBase { public: ToolDockerFactory() : KoDockFactoryBase() { } QString id() const override { return "sharedtooldocker"; } QDockWidget* createDockWidget() override { KoToolDocker* dockWidget = new KoToolDocker(); return dockWidget; } DockPosition defaultDockPosition() const override { return DockRight; } }; class Q_DECL_HIDDEN KisMainWindow::Private { public: Private(KisMainWindow *parent, QUuid id) : q(parent) , id(id) , dockWidgetMenu(new KActionMenu(i18nc("@action:inmenu", "&Dockers"), parent)) , windowMenu(new KActionMenu(i18nc("@action:inmenu", "&Window"), parent)) , documentMenu(new KActionMenu(i18nc("@action:inmenu", "New &View"), parent)) , workspaceMenu(new KActionMenu(i18nc("@action:inmenu", "Wor&kspace"), parent)) , welcomePage(new KisWelcomePageWidget(parent)) , widgetStack(new QStackedWidget(parent)) , mdiArea(new QMdiArea(parent)) , windowMapper(new KisSignalMapper(parent)) , documentMapper(new KisSignalMapper(parent)) #ifdef Q_OS_ANDROID , fileManager(new KisAndroidFileManager(parent)) #endif { if (id.isNull()) this->id = QUuid::createUuid(); welcomeScroller = new QScrollArea(); welcomeScroller->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); welcomeScroller->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); welcomeScroller->setWidget(welcomePage); welcomeScroller->setWidgetResizable(true); widgetStack->addWidget(welcomeScroller); widgetStack->addWidget(mdiArea); mdiArea->setTabsMovable(true); mdiArea->setActivationOrder(QMdiArea::ActivationHistoryOrder); } ~Private() { qDeleteAll(toolbarList); } KisMainWindow *q {0}; QUuid id; KisViewManager *viewManager {0}; QPointer activeView; QList toolbarList; bool firstTime {true}; bool windowSizeDirty {false}; bool readOnly {false}; KisAction *showDocumentInfo {0}; KisAction *saveAction {0}; KisAction *saveActionAs {0}; // KisAction *printAction; // KisAction *printActionPreview; // KisAction *exportPdf {0}; KisAction *importAnimation {0}; KisAction *closeAll {0}; // KisAction *reloadFile; KisAction *importFile {0}; KisAction *exportFile {0}; KisAction *undo {0}; KisAction *redo {0}; KisAction *newWindow {0}; KisAction *close {0}; KisAction *mdiCascade {0}; KisAction *mdiTile {0}; KisAction *mdiNextWindow {0}; KisAction *mdiPreviousWindow {0}; KisAction *toggleDockers {0}; KisAction *toggleDockerTitleBars {0}; KisAction *toggleDetachCanvas {0}; KisAction *fullScreenMode {0}; KisAction *showSessionManager {0}; KisAction *expandingSpacers[2]; KActionMenu *dockWidgetMenu; KActionMenu *windowMenu; KActionMenu *documentMenu; KActionMenu *workspaceMenu; KHelpMenu *helpMenu {0}; KRecentFilesAction *recentFiles {0}; KoResourceModel *workspacemodel {0}; QScopedPointer undoActionsUpdateManager; QString lastExportLocation; QMap dockWidgetsMap; QByteArray dockerStateBeforeHiding; KoToolDocker *toolOptionsDocker {0}; QCloseEvent *deferredClosingEvent {0}; Digikam::ThemeManager *themeManager {0}; QScrollArea *welcomeScroller {0}; KisWelcomePageWidget *welcomePage {0}; QStackedWidget *widgetStack {0}; QMdiArea *mdiArea; QMdiSubWindow *activeSubWindow {0}; KisSignalMapper *windowMapper; KisSignalMapper *documentMapper; KisCanvasWindow *canvasWindow {0}; QByteArray lastExportedFormat; QScopedPointer > tabSwitchCompressor; QMutex savingEntryMutex; KConfigGroup windowStateConfig; QUuid workspaceBorrowedBy; KisSignalAutoConnectionsStore screenConnectionsStore; #ifdef Q_OS_ANDROID KisAndroidFileManager *fileManager; #endif KisActionManager * actionManager() { return viewManager->actionManager(); } QTabBar* findTabBarHACK() { QObjectList objects = mdiArea->children(); Q_FOREACH (QObject *object, objects) { QTabBar *bar = qobject_cast(object); if (bar) { return bar; } } return 0; } }; KisMainWindow::KisMainWindow(QUuid uuid) : KXmlGuiWindow() , d(new Private(this, uuid)) { auto rserver = KisResourceServerProvider::instance()->workspaceServer(); QSharedPointer adapter(new KoResourceServerAdapter(rserver)); d->workspacemodel = new KoResourceModel(adapter, this); connect(d->workspacemodel, &KoResourceModel::afterResourcesLayoutReset, this, [&]() { updateWindowMenu(); }); d->viewManager = new KisViewManager(this, actionCollection()); KConfigGroup group( KSharedConfig::openConfig(), "theme"); d->themeManager = new Digikam::ThemeManager(group.readEntry("Theme", "Krita dark"), this); d->windowStateConfig = KSharedConfig::openConfig()->group("MainWindow"); setStandardToolBarMenuEnabled(true); setTabPosition(Qt::AllDockWidgetAreas, QTabWidget::North); setDockNestingEnabled(true); qApp->setStartDragDistance(25); // 25 px is a distance that works well for Tablet and Mouse events #ifdef Q_OS_MACOS setUnifiedTitleAndToolBarOnMac(true); #endif connect(this, SIGNAL(restoringDone()), this, SLOT(forceDockTabFonts())); connect(this, SIGNAL(themeChanged()), d->viewManager, SLOT(updateIcons())); connect(KisPart::instance(), SIGNAL(documentClosed(QString)), SLOT(updateWindowMenu())); connect(KisPart::instance(), SIGNAL(documentOpened(QString)), SLOT(updateWindowMenu())); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), this, SLOT(configChanged())); actionCollection()->addAssociatedWidget(this); KoPluginLoader::instance()->load("Krita/ViewPlugin", "Type == 'Service' and ([X-Krita-Version] == 28)", KoPluginLoader::PluginsConfig(), d->viewManager, false); // Load the per-application plugins (Right now, only Python) We do this only once, when the first mainwindow is being created. KoPluginLoader::instance()->load("Krita/ApplicationPlugin", "Type == 'Service' and ([X-Krita-Version] == 28)", KoPluginLoader::PluginsConfig(), qApp, true); KoToolBoxFactory toolBoxFactory; QDockWidget *toolbox = createDockWidget(&toolBoxFactory); toolbox->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetClosable); KisConfig cfg(true); if (cfg.toolOptionsInDocker()) { ToolDockerFactory toolDockerFactory; d->toolOptionsDocker = qobject_cast(createDockWidget(&toolDockerFactory)); d->toolOptionsDocker->toggleViewAction()->setEnabled(true); } QMap dockwidgetActions; dockwidgetActions[toolbox->toggleViewAction()->text()] = toolbox->toggleViewAction(); Q_FOREACH (const QString & docker, KoDockRegistry::instance()->keys()) { KoDockFactoryBase *factory = KoDockRegistry::instance()->value(docker); QDockWidget *dw = createDockWidget(factory); dockwidgetActions[dw->toggleViewAction()->text()] = dw->toggleViewAction(); } if (d->toolOptionsDocker) { dockwidgetActions[d->toolOptionsDocker->toggleViewAction()->text()] = d->toolOptionsDocker->toggleViewAction(); } connect(KoToolManager::instance(), SIGNAL(toolOptionWidgetsChanged(KoCanvasController*,QList >)), this, SLOT(newOptionWidgets(KoCanvasController*,QList >))); Q_FOREACH (QString title, dockwidgetActions.keys()) { d->dockWidgetMenu->addAction(dockwidgetActions[title]); } Q_FOREACH (QDockWidget *wdg, dockWidgets()) { if ((wdg->features() & QDockWidget::DockWidgetClosable) == 0) { wdg->setVisible(true); } } Q_FOREACH (KoCanvasObserverBase* observer, canvasObservers()) { observer->setObservedCanvas(0); KisMainwindowObserver* mainwindowObserver = dynamic_cast(observer); if (mainwindowObserver) { mainwindowObserver->setViewManager(d->viewManager); } } // Load all the actions from the tool plugins Q_FOREACH(KoToolFactoryBase *toolFactory, KoToolRegistry::instance()->values()) { toolFactory->createActions(actionCollection()); } d->mdiArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); d->mdiArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); d->mdiArea->setTabPosition(QTabWidget::North); d->mdiArea->setTabsClosable(true); // Tab close button override // Windows just has a black X, and Ubuntu has a dark x that is hard to read // just switch this icon out for all OSs so it is easier to see d->mdiArea->setStyleSheet("QTabBar::close-button { image: url(:/pics/broken-preset.png) }"); setCentralWidget(d->widgetStack); d->widgetStack->setCurrentIndex(0); connect(d->mdiArea, SIGNAL(subWindowActivated(QMdiSubWindow*)), this, SLOT(subWindowActivated())); connect(d->windowMapper, SIGNAL(mapped(QWidget*)), this, SLOT(setActiveSubWindow(QWidget*))); connect(d->documentMapper, SIGNAL(mapped(QObject*)), this, SLOT(newView(QObject*))); d->canvasWindow = new KisCanvasWindow(this); actionCollection()->addAssociatedWidget(d->canvasWindow); createActions(); // the welcome screen needs to grab actions...so make sure this line goes after the createAction() so they exist d->welcomePage->setMainWindow(this); setAutoSaveSettings(d->windowStateConfig, false); subWindowActivated(); updateWindowMenu(); if (isHelpMenuEnabled() && !d->helpMenu) { // workaround for KHelpMenu (or rather KAboutData::applicationData()) internally // not using the Q*Application metadata ATM, which results e.g. in the bugreport wizard // not having the app version preset // fixed hopefully in KF5 5.22.0, patch pending QGuiApplication *app = qApp; KAboutData aboutData(app->applicationName(), app->applicationDisplayName(), app->applicationVersion()); aboutData.setOrganizationDomain(app->organizationDomain().toUtf8()); d->helpMenu = new KHelpMenu(this, aboutData, false); // workaround-less version: // d->helpMenu = new KHelpMenu(this, QString()/*unused*/, false); // The difference between using KActionCollection->addAction() is that // these actions do not get tied to the MainWindow. What does this all do? KActionCollection *actions = d->viewManager->actionCollection(); QAction *helpContentsAction = d->helpMenu->action(KHelpMenu::menuHelpContents); QAction *whatsThisAction = d->helpMenu->action(KHelpMenu::menuWhatsThis); QAction *reportBugAction = d->helpMenu->action(KHelpMenu::menuReportBug); QAction *switchLanguageAction = d->helpMenu->action(KHelpMenu::menuSwitchLanguage); QAction *aboutAppAction = d->helpMenu->action(KHelpMenu::menuAboutApp); QAction *aboutKdeAction = d->helpMenu->action(KHelpMenu::menuAboutKDE); if (helpContentsAction) { actions->addAction(helpContentsAction->objectName(), helpContentsAction); } if (whatsThisAction) { actions->addAction(whatsThisAction->objectName(), whatsThisAction); } if (reportBugAction) { actions->addAction(reportBugAction->objectName(), reportBugAction); } if (switchLanguageAction) { actions->addAction(switchLanguageAction->objectName(), switchLanguageAction); } if (aboutAppAction) { actions->addAction(aboutAppAction->objectName(), aboutAppAction); } if (aboutKdeAction) { actions->addAction(aboutKdeAction->objectName(), aboutKdeAction); } connect(d->helpMenu, SIGNAL(showAboutApplication()), SLOT(showAboutApplication())); } // KDE' libs 4''s help contents action is broken outside kde, for some reason... We can handle it just as easily ourselves QAction *helpAction = actionCollection()->action("help_contents"); helpAction->disconnect(); connect(helpAction, SIGNAL(triggered()), this, SLOT(showManual())); #if 0 //check for colliding shortcuts QSet existingShortcuts; Q_FOREACH (QAction* action, actionCollection()->actions()) { if(action->shortcut() == QKeySequence(0)) { continue; } dbgKrita << "shortcut " << action->text() << " " << action->shortcut(); Q_ASSERT(!existingShortcuts.contains(action->shortcut())); existingShortcuts.insert(action->shortcut()); } #endif configChanged(); // If we have customized the toolbars, load that first setLocalXMLFile(KoResourcePaths::locateLocal("data", "krita4.xmlgui")); setXMLFile(":/kxmlgui5/krita4.xmlgui"); guiFactory()->addClient(this); connect(guiFactory(), SIGNAL(makingChanges(bool)), SLOT(slotXmlGuiMakingChanges(bool))); // Create and plug toolbar list for Settings menu QList toolbarList; Q_FOREACH (QWidget* it, guiFactory()->containers("ToolBar")) { KToolBar * toolBar = ::qobject_cast(it); toolBar->setMovable(KisConfig(true).readEntry("LockAllDockerPanels", false)); if (toolBar) { if (toolBar->objectName() == "BrushesAndStuff") { toolBar->setEnabled(false); } KToggleAction* act = new KToggleAction(i18n("Show %1 Toolbar", toolBar->windowTitle()), this); actionCollection()->addAction(toolBar->objectName().toUtf8(), act); act->setCheckedState(KGuiItem(i18n("Hide %1 Toolbar", toolBar->windowTitle()))); connect(act, SIGNAL(toggled(bool)), this, SLOT(slotToolbarToggled(bool))); act->setChecked(!toolBar->isHidden()); toolbarList.append(act); } else { warnUI << "Toolbar list contains a " << it->metaObject()->className() << " which is not a toolbar!"; } } KToolBar::setToolBarsLocked(KisConfig(true).readEntry("LockAllDockerPanels", false)); plugActionList("toolbarlist", toolbarList); d->toolbarList = toolbarList; applyToolBarLayout(); d->viewManager->updateGUI(); d->viewManager->updateIcons(); QTimer::singleShot(1000, this, SLOT(checkSanity())); { using namespace std::placeholders; // For _1 placeholder std::function callback( std::bind(&KisMainWindow::switchTab, this, _1)); d->tabSwitchCompressor.reset( new KisSignalCompressorWithParam(500, callback, KisSignalCompressor::FIRST_INACTIVE)); } if (cfg.readEntry("CanvasOnlyActive", false)) { QString currentWorkspace = cfg.readEntry("CurrentWorkspace", "Default"); KoResourceServer * rserver = KisResourceServerProvider::instance()->workspaceServer(); KisWorkspaceResource* workspace = rserver->resourceByName(currentWorkspace); if (workspace) { restoreWorkspace(workspace); } cfg.writeEntry("CanvasOnlyActive", false); menuBar()->setVisible(true); } this->winId(); // Ensures the native window has been created. QWindow *window = this->windowHandle(); connect(window, SIGNAL(screenChanged(QScreen *)), this, SLOT(windowScreenChanged(QScreen *))); } KisMainWindow::~KisMainWindow() { // Q_FOREACH (QAction *ac, actionCollection()->actions()) { // QAction *action = qobject_cast(ac); // if (action) { // qDebug() << "", "").replace("", "") // << "\n\ticonText=" << action->iconText().replace("&", "&") // << "\n\tshortcut=" << action->shortcut().toString() // << "\n\tisCheckable=" << QString((action->isChecked() ? "true" : "false")) // << "\n\tstatusTip=" << action->statusTip() // << "\n/>\n" ; // } // else { // dbgKrita << "Got a non-qaction:" << ac->objectName(); // } // } // The doc and view might still exist (this is the case when closing the window) KisPart::instance()->removeMainWindow(this); delete d->viewManager; delete d; } QUuid KisMainWindow::id() const { return d->id; } void KisMainWindow::addView(KisView *view, QMdiSubWindow *subWindow) { if (d->activeView == view && !subWindow) return; if (d->activeView) { d->activeView->disconnect(this); } // register the newly created view in the input manager viewManager()->inputManager()->addTrackedCanvas(view->canvasBase()); showView(view, subWindow); updateCaption(); emit restoringDone(); if (d->activeView) { connect(d->activeView, SIGNAL(titleModified(QString,bool)), SLOT(slotDocumentTitleModified())); connect(d->viewManager->statusBar(), SIGNAL(memoryStatusUpdated()), this, SLOT(updateCaption())); } } void KisMainWindow::notifyChildViewDestroyed(KisView *view) { /** * If we are the last view of the window, Qt will not activate another tab - * before destroying tab/window. In ths case we should clear oll the dangling + * before destroying tab/window. In this case we should clear all the dangling * pointers manually by setting the current view to null */ viewManager()->inputManager()->removeTrackedCanvas(view->canvasBase()); if (view->canvasBase() == viewManager()->canvasBase()) { viewManager()->setCurrentView(0); } } void KisMainWindow::showView(KisView *imageView, QMdiSubWindow *subwin) { if (imageView && activeView() != imageView) { // XXX: find a better way to initialize this! imageView->setViewManager(d->viewManager); imageView->canvasBase()->setFavoriteResourceManager(d->viewManager->paintOpBox()->favoriteResourcesManager()); imageView->slotLoadingFinished(); if (!subwin) { subwin = d->mdiArea->addSubWindow(imageView); } else { subwin->setWidget(imageView); } imageView->setSubWindow(subwin); subwin->setAttribute(Qt::WA_DeleteOnClose, true); connect(subwin, SIGNAL(destroyed()), SLOT(updateWindowMenu())); KisConfig cfg(true); subwin->setOption(QMdiSubWindow::RubberBandMove, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); subwin->setOption(QMdiSubWindow::RubberBandResize, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); subwin->setWindowIcon(qApp->windowIcon()); /** * Hack alert! * * Here we explicitly request KoToolManager to emit all the tool * activation signals, to reinitialize the tool options docker. * * That is needed due to a design flaw we have in the * initialization procedure. The tool in the KoToolManager is * initialized in KisView::setViewManager() calls, which * happens early enough. During this call the tool manager * requests KoCanvasControllerWidget to emit the signal to * update the widgets in the tool docker. *But* at that moment * of time the view is not yet connected to the main window, * because it happens in KisViewManager::setCurrentView a bit * later. This fact makes the widgets updating signals be lost * and never reach the tool docker. * * So here we just explicitly call the tool activation stub. */ KoToolManager::instance()->initializeCurrentToolForCanvas(); if (d->mdiArea->subWindowList().size() == 1) { imageView->showMaximized(); } else { imageView->show(); } // No, no, no: do not try to call this _before_ the show() has // been called on the view; only when that has happened is the // opengl context active, and very bad things happen if we tell // the dockers to update themselves with a view if the opengl // context is not active. setActiveView(imageView); updateWindowMenu(); updateCaption(); } } void KisMainWindow::slotPreferences() { if (KisDlgPreferences::editPreferences()) { KisConfigNotifier::instance()->notifyConfigChanged(); KisConfigNotifier::instance()->notifyPixelGridModeChanged(); KisImageConfigNotifier::instance()->notifyConfigChanged(); // XXX: should this be changed for the views in other windows as well? Q_FOREACH (QPointer koview, KisPart::instance()->views()) { KisViewManager *view = qobject_cast(koview); if (view) { // Update the settings for all nodes -- they don't query // KisConfig directly because they need the settings during // compositing, and they don't connect to the config notifier // because nodes are not QObjects (because only one base class // can be a QObject). KisNode* node = dynamic_cast(view->image()->rootLayer().data()); node->updateSettings(); } } updateWindowMenu(); d->viewManager->showHideScrollbars(); } } void KisMainWindow::slotThemeChanged() { // save theme changes instantly KConfigGroup group( KSharedConfig::openConfig(), "theme"); group.writeEntry("Theme", d->themeManager->currentThemeName()); // reload action icons! Q_FOREACH (QAction *action, actionCollection()->actions()) { KisIconUtils::updateIcon(action); } if (d->mdiArea) { d->mdiArea->setPalette(qApp->palette()); for (int i=0; imdiArea->subWindowList().size(); i++) { QMdiSubWindow *window = d->mdiArea->subWindowList().at(i); if (window) { window->setPalette(qApp->palette()); KisView *view = qobject_cast(window->widget()); if (view) { view->slotThemeChanged(qApp->palette()); } } } } emit themeChanged(); } bool KisMainWindow::canvasDetached() const { return centralWidget() != d->widgetStack; } void KisMainWindow::setCanvasDetached(bool detach) { if (detach == canvasDetached()) return; QWidget *outgoingWidget = centralWidget() ? takeCentralWidget() : nullptr; QWidget *incomingWidget = d->canvasWindow->swapMainWidget(outgoingWidget); if (incomingWidget) { setCentralWidget(incomingWidget); } if (detach) { d->canvasWindow->show(); } else { d->canvasWindow->hide(); } } void KisMainWindow::slotFileSelected(QString path) { QString url = path; if (!url.isEmpty()) { bool res = openDocument(QUrl::fromLocalFile(url), Import); if (!res) { warnKrita << "Loading" << url << "failed"; } } } void KisMainWindow::slotEmptyFilePath() { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("The chosen file's location could not be found. Does it exist?")); } QWidget * KisMainWindow::canvasWindow() const { return d->canvasWindow; } void KisMainWindow::updateReloadFileAction(KisDocument *doc) { Q_UNUSED(doc); // d->reloadFile->setEnabled(doc && !doc->url().isEmpty()); } void KisMainWindow::setReadWrite(bool readwrite) { d->saveAction->setEnabled(readwrite); d->importFile->setEnabled(readwrite); d->readOnly = !readwrite; updateCaption(); } void KisMainWindow::addRecentURL(const QUrl &url) { // Add entry to recent documents list // (call coming from KisDocument because it must work with cmd line, template dlg, file/open, etc.) if (!url.isEmpty()) { bool ok = true; if (url.isLocalFile()) { QString path = url.adjusted(QUrl::StripTrailingSlash).toLocalFile(); const QStringList tmpDirs = KoResourcePaths::resourceDirs("tmp"); for (QStringList::ConstIterator it = tmpDirs.begin() ; ok && it != tmpDirs.end() ; ++it) { if (path.contains(*it)) { ok = false; // it's in the tmp resource } } const QStringList templateDirs = KoResourcePaths::findDirs("templates"); for (QStringList::ConstIterator it = templateDirs.begin() ; ok && it != templateDirs.end() ; ++it) { if (path.contains(*it)) { ok = false; // it's in the templates directory. break; } } } if (ok) { d->recentFiles->addUrl(url); } saveRecentFiles(); } } void KisMainWindow::saveRecentFiles() { // Save list of recent files KSharedConfigPtr config = KSharedConfig::openConfig(); d->recentFiles->saveEntries(config->group("RecentFiles")); config->sync(); // Tell all windows to reload their list, after saving // Doesn't work multi-process, but it's a start Q_FOREACH (KisMainWindow *mw, KisPart::instance()->mainWindows()) { if (mw != this) { mw->reloadRecentFileList(); } } } QList KisMainWindow::recentFilesUrls() { return d->recentFiles->urls(); } void KisMainWindow::clearRecentFiles() { d->recentFiles->clear(); d->welcomePage->populateRecentDocuments(); } void KisMainWindow::removeRecentUrl(const QUrl &url) { d->recentFiles->removeUrl(url); KSharedConfigPtr config = KSharedConfig::openConfig(); d->recentFiles->saveEntries(config->group("RecentFiles")); config->sync(); } void KisMainWindow::reloadRecentFileList() { d->recentFiles->loadEntries(KSharedConfig::openConfig()->group("RecentFiles")); } void KisMainWindow::updateCaption() { if (!d->mdiArea->activeSubWindow()) { updateCaption(QString(), false); } else if (d->activeView && d->activeView->document() && d->activeView->image()){ KisDocument *doc = d->activeView->document(); QString caption(doc->caption()); if (d->readOnly) { caption += " [" + i18n("Write Protected") + "] "; } if (doc->isRecovered()) { caption += " [" + i18n("Recovered") + "] "; } // show the file size for the document KisMemoryStatisticsServer::Statistics m_fileSizeStats = KisMemoryStatisticsServer::instance()->fetchMemoryStatistics(d->activeView ? d->activeView->image() : 0); if (m_fileSizeStats.imageSize) { caption += QString(" (").append( KFormat().formatByteSize(m_fileSizeStats.imageSize)).append( ")"); } updateCaption(caption, doc->isModified()); if (!doc->url().fileName().isEmpty()) { d->saveAction->setToolTip(i18n("Save as %1", doc->url().fileName())); } else { d->saveAction->setToolTip(i18n("Save")); } } } void KisMainWindow::updateCaption(const QString &caption, bool modified) { QString versionString = KritaVersionWrapper::versionString(true); QString title = caption; if (!title.contains(QStringLiteral("[*]"))) { // append the placeholder so that the modified mechanism works title.append(QStringLiteral(" [*]")); } if (d->mdiArea->activeSubWindow()) { #if defined(KRITA_ALPHA) || defined (KRITA_BETA) || defined (KRITA_RC) d->mdiArea->activeSubWindow()->setWindowTitle(QString("%1: %2").arg(versionString).arg(title)); #else d->mdiArea->activeSubWindow()->setWindowTitle(title); #endif d->mdiArea->activeSubWindow()->setWindowModified(modified); } else { #if defined(KRITA_ALPHA) || defined (KRITA_BETA) || defined (KRITA_RC) setWindowTitle(QString("%1: %2").arg(versionString).arg(title)); #else setWindowTitle(title); #endif } setWindowModified(modified); } KisView *KisMainWindow::activeView() const { if (d->activeView) { return d->activeView; } return 0; } bool KisMainWindow::openDocument(const QUrl &url, OpenFlags flags) { if (!QFile(url.toLocalFile()).exists()) { if (!(flags & BatchMode)) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("The file %1 does not exist.", url.url())); } d->recentFiles->removeUrl(url); //remove the file from the recent-opened-file-list saveRecentFiles(); return false; } return openDocumentInternal(url, flags); } bool KisMainWindow::openDocumentInternal(const QUrl &url, OpenFlags flags) { if (!url.isLocalFile()) { qWarning() << "KisMainWindow::openDocumentInternal. Not a local file:" << url; return false; } KisDocument *newdoc = KisPart::instance()->createDocument(); if (flags & BatchMode) { newdoc->setFileBatchMode(true); } d->firstTime = true; connect(newdoc, SIGNAL(completed()), this, SLOT(slotLoadCompleted())); connect(newdoc, SIGNAL(canceled(QString)), this, SLOT(slotLoadCanceled(QString))); KisDocument::OpenFlags openFlags = KisDocument::None; if (flags & RecoveryFile) { openFlags |= KisDocument::RecoveryFile; } bool openRet = !(flags & Import) ? newdoc->openUrl(url, openFlags) : newdoc->importDocument(url); if (!openRet) { delete newdoc; return false; } KisPart::instance()->addDocument(newdoc); updateReloadFileAction(newdoc); if (!QFileInfo(url.toLocalFile()).isWritable()) { setReadWrite(false); } return true; } void KisMainWindow::showDocument(KisDocument *document) { Q_FOREACH(QMdiSubWindow *subwindow, d->mdiArea->subWindowList()) { KisView *view = qobject_cast(subwindow->widget()); KIS_SAFE_ASSERT_RECOVER_NOOP(view); if (view) { if (view->document() == document) { setActiveSubWindow(subwindow); return; } } } addViewAndNotifyLoadingCompleted(document); } KisView* KisMainWindow::addViewAndNotifyLoadingCompleted(KisDocument *document, QMdiSubWindow *subWindow) { showWelcomeScreen(false); // see workaround in function header KisView *view = KisPart::instance()->createView(document, d->viewManager, this); addView(view, subWindow); emit guiLoadingFinished(); return view; } QStringList KisMainWindow::showOpenFileDialog(bool isImporting) { KoFileDialog dialog(this, KoFileDialog::ImportFiles, "OpenDocument"); dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)); dialog.setMimeTypeFilters(KisImportExportManager::supportedMimeTypes(KisImportExportManager::Import)); dialog.setCaption(isImporting ? i18n("Import Images") : i18n("Open Images")); return dialog.filenames(); } // Separate from openDocument to handle async loading (remote URLs) void KisMainWindow::slotLoadCompleted() { KisDocument *newdoc = qobject_cast(sender()); if (newdoc && newdoc->image()) { addViewAndNotifyLoadingCompleted(newdoc); disconnect(newdoc, SIGNAL(completed()), this, SLOT(slotLoadCompleted())); disconnect(newdoc, SIGNAL(canceled(QString)), this, SLOT(slotLoadCanceled(QString))); emit loadCompleted(); } } void KisMainWindow::slotLoadCanceled(const QString & errMsg) { dbgUI << "KisMainWindow::slotLoadCanceled"; if (!errMsg.isEmpty()) // empty when canceled by user QMessageBox::critical(this, i18nc("@title:window", "Krita"), errMsg); // ... can't delete the document, it's the one who emitted the signal... KisDocument* doc = qobject_cast(sender()); Q_ASSERT(doc); disconnect(doc, SIGNAL(completed()), this, SLOT(slotLoadCompleted())); disconnect(doc, SIGNAL(canceled(QString)), this, SLOT(slotLoadCanceled(QString))); } void KisMainWindow::slotSaveCanceled(const QString &errMsg) { dbgUI << "KisMainWindow::slotSaveCanceled"; if (!errMsg.isEmpty()) // empty when canceled by user QMessageBox::critical(this, i18nc("@title:window", "Krita"), errMsg); slotSaveCompleted(); } void KisMainWindow::slotSaveCompleted() { dbgUI << "KisMainWindow::slotSaveCompleted"; KisDocument* doc = qobject_cast(sender()); Q_ASSERT(doc); disconnect(doc, SIGNAL(completed()), this, SLOT(slotSaveCompleted())); disconnect(doc, SIGNAL(canceled(QString)), this, SLOT(slotSaveCanceled(QString))); if (d->deferredClosingEvent) { KXmlGuiWindow::closeEvent(d->deferredClosingEvent); } } bool KisMainWindow::hackIsSaving() const { StdLockableWrapper wrapper(&d->savingEntryMutex); std::unique_lock> l(wrapper, std::try_to_lock); return !l.owns_lock(); } bool KisMainWindow::installBundle(const QString &fileName) const { QFileInfo from(fileName); QFileInfo to(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/bundles/" + from.fileName()); if (to.exists()) { QFile::remove(to.canonicalFilePath()); } return QFile::copy(fileName, QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/bundles/" + from.fileName()); } bool KisMainWindow::saveDocument(KisDocument *document, bool saveas, bool isExporting) { if (!document) { return true; } /** * Make sure that we cannot enter this method twice! * * The lower level functions may call processEvents() so * double-entry is quite possible to achieve. Here we try to lock * the mutex, and if it is failed, just cancel saving. */ StdLockableWrapper wrapper(&d->savingEntryMutex); std::unique_lock> l(wrapper, std::try_to_lock); if (!l.owns_lock()) return false; // no busy wait for saving because it is dangerous! KisDelayedSaveDialog dlg(document->image(), KisDelayedSaveDialog::SaveDialog, 0, this); dlg.blockIfImageIsBusy(); if (dlg.result() == KisDelayedSaveDialog::Rejected) { return false; } else if (dlg.result() == KisDelayedSaveDialog::Ignored) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("You are saving a file while the image is " "still rendering. The saved file may be " "incomplete or corrupted.\n\n" "Please select a location where the original " "file will not be overridden!")); saveas = true; } if (document->isRecovered()) { saveas = true; } if (document->url().isEmpty()) { saveas = true; } connect(document, SIGNAL(completed()), this, SLOT(slotSaveCompleted())); connect(document, SIGNAL(canceled(QString)), this, SLOT(slotSaveCanceled(QString))); QByteArray nativeFormat = document->nativeFormatMimeType(); QByteArray oldMimeFormat = document->mimeType(); QUrl suggestedURL = document->url(); QStringList mimeFilter = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Export); mimeFilter = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Export); if (!mimeFilter.contains(oldMimeFormat)) { dbgUI << "KisMainWindow::saveDocument no export filter for" << oldMimeFormat; // --- don't setOutputMimeType in case the user cancels the Save As // dialog and then tries to just plain Save --- // suggest a different filename extension (yes, we fortunately don't all live in a world of magic :)) QString suggestedFilename = QFileInfo(suggestedURL.toLocalFile()).completeBaseName(); if (!suggestedFilename.isEmpty()) { // ".kra" looks strange for a name suggestedFilename = suggestedFilename + "." + KisMimeDatabase::suffixesForMimeType(KIS_MIME_TYPE).first(); suggestedURL = suggestedURL.adjusted(QUrl::RemoveFilename); suggestedURL.setPath(suggestedURL.path() + suggestedFilename); } // force the user to choose outputMimeType saveas = true; } bool ret = false; if (document->url().isEmpty() || isExporting || saveas) { // if you're just File/Save As'ing to change filter options you // don't want to be reminded about overwriting files etc. bool justChangingFilterOptions = false; KoFileDialog dialog(this, KoFileDialog::SaveFile, "SaveAs"); dialog.setCaption(isExporting ? i18n("Exporting") : i18n("Saving As")); //qDebug() << ">>>>>" << isExporting << d->lastExportLocation << d->lastExportedFormat << QString::fromLatin1(document->mimeType()); if (isExporting && !d->lastExportLocation.isEmpty() && !d->lastExportLocation.contains(QDir::tempPath())) { // Use the location where we last exported to, if it's set, as the opening location for the file dialog QString proposedPath = QFileInfo(d->lastExportLocation).absolutePath(); // If the document doesn't have a filename yet, use the title QString proposedFileName = suggestedURL.isEmpty() ? document->documentInfo()->aboutInfo("title") : QFileInfo(suggestedURL.toLocalFile()).completeBaseName(); // Use the last mimetype we exported to by default QString proposedMimeType = d->lastExportedFormat.isEmpty() ? "" : d->lastExportedFormat; QString proposedExtension = KisMimeDatabase::suffixesForMimeType(proposedMimeType).first().remove("*,"); // Set the default dir: this overrides the one loaded from the config file, since we're exporting and the lastExportLocation is not empty dialog.setDefaultDir(proposedPath + "/" + proposedFileName + "." + proposedExtension, true); dialog.setMimeTypeFilters(mimeFilter, proposedMimeType); } else { // Get the last used location for saving KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); QString proposedPath = group.readEntry("SaveAs", ""); // if that is empty, get the last used location for loading if (proposedPath.isEmpty()) { proposedPath = group.readEntry("OpenDocument", ""); } // If that is empty, too, use the Pictures location. if (proposedPath.isEmpty()) { proposedPath = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation); } // But only use that if the suggestedUrl, that is, the document's own url is empty, otherwise // open the location where the document currently is. dialog.setDefaultDir(suggestedURL.isEmpty() ? proposedPath : suggestedURL.toLocalFile(), true); // If exporting, default to all supported file types if user is exporting QByteArray default_mime_type = ""; if (!isExporting) { // otherwise use the document's mimetype, or if that is empty, kra, which is the savest. default_mime_type = document->mimeType().isEmpty() ? nativeFormat : document->mimeType(); } dialog.setMimeTypeFilters(mimeFilter, QString::fromLatin1(default_mime_type)); } QUrl newURL = QUrl::fromUserInput(dialog.filename()); if (newURL.isLocalFile()) { QString fn = newURL.toLocalFile(); if (QFileInfo(fn).completeSuffix().isEmpty()) { fn.append(KisMimeDatabase::suffixesForMimeType(nativeFormat).first()); newURL = QUrl::fromLocalFile(fn); } } if (document->documentInfo()->aboutInfo("title") == i18n("Unnamed")) { QString fn = newURL.toLocalFile(); QFileInfo info(fn); document->documentInfo()->setAboutInfo("title", info.completeBaseName()); } QByteArray outputFormat = nativeFormat; QString outputFormatString = KisMimeDatabase::mimeTypeForFile(newURL.toLocalFile(), false); outputFormat = outputFormatString.toLatin1(); if (!isExporting) { justChangingFilterOptions = (newURL == document->url()) && (outputFormat == document->mimeType()); } else { QString path = QFileInfo(d->lastExportLocation).absolutePath(); QString filename = QFileInfo(document->url().toLocalFile()).completeBaseName(); justChangingFilterOptions = (QFileInfo(newURL.toLocalFile()).absolutePath() == path) && (QFileInfo(newURL.toLocalFile()).completeBaseName() == filename) && (outputFormat == d->lastExportedFormat); } bool bOk = true; if (newURL.isEmpty()) { bOk = false; } if (bOk) { bool wantToSave = true; // don't change this line unless you know what you're doing :) if (!justChangingFilterOptions) { if (!document->isNativeFormat(outputFormat)) wantToSave = true; } if (wantToSave) { if (!isExporting) { // Save As ret = document->saveAs(newURL, outputFormat, true); if (ret) { dbgUI << "Successful Save As!"; KisPart::instance()->addRecentURLToAllMainWindows(newURL); setReadWrite(true); } else { dbgUI << "Failed Save As!"; } } else { // Export ret = document->exportDocument(newURL, outputFormat); if (ret) { d->lastExportLocation = newURL.toLocalFile(); d->lastExportedFormat = outputFormat; } } } // if (wantToSave) { else ret = false; } // if (bOk) { else ret = false; } else { // saving // We cannot "export" into the currently // opened document. We are not Gimp. KIS_ASSERT_RECOVER_NOOP(!isExporting); // be sure document has the correct outputMimeType! if (document->isModified()) { ret = document->save(true, 0); } if (!ret) { dbgUI << "Failed Save!"; } } updateReloadFileAction(document); updateCaption(); return ret; } void KisMainWindow::undo() { if (activeView()) { activeView()->document()->undoStack()->undo(); } } void KisMainWindow::redo() { if (activeView()) { activeView()->document()->undoStack()->redo(); } } void KisMainWindow::closeEvent(QCloseEvent *e) { if (hackIsSaving()) { e->setAccepted(false); return; } if (!KisPart::instance()->closingSession()) { QAction *action= d->viewManager->actionCollection()->action("view_show_canvas_only"); if ((action) && (action->isChecked())) { action->setChecked(false); } // Save session when last window is closed if (KisPart::instance()->mainwindowCount() == 1) { bool closeAllowed = KisPart::instance()->closeSession(); if (!closeAllowed) { e->setAccepted(false); return; } } } d->mdiArea->closeAllSubWindows(); QList childrenList = d->mdiArea->subWindowList(); if (childrenList.isEmpty()) { d->deferredClosingEvent = e; saveWindowState(true); d->canvasWindow->close(); } else { e->setAccepted(false); } } void KisMainWindow::saveWindowSettings() { KSharedConfigPtr config = KSharedConfig::openConfig(); if (d->windowSizeDirty ) { dbgUI << "KisMainWindow::saveWindowSettings"; KConfigGroup group = d->windowStateConfig; KWindowConfig::saveWindowSize(windowHandle(), group); config->sync(); d->windowSizeDirty = false; } if (!d->activeView || d->activeView->document()) { // Save toolbar position into the config file of the app, under the doc's component name KConfigGroup group = d->windowStateConfig; saveMainWindowSettings(group); // Save collapsible state of dock widgets for (QMap::const_iterator i = d->dockWidgetsMap.constBegin(); i != d->dockWidgetsMap.constEnd(); ++i) { if (i.value()->widget()) { KConfigGroup dockGroup = group.group(QString("DockWidget ") + i.key()); dockGroup.writeEntry("Collapsed", i.value()->widget()->isHidden()); dockGroup.writeEntry("Locked", i.value()->property("Locked").toBool()); dockGroup.writeEntry("DockArea", (int) dockWidgetArea(i.value())); dockGroup.writeEntry("xPosition", (int) i.value()->widget()->x()); dockGroup.writeEntry("yPosition", (int) i.value()->widget()->y()); dockGroup.writeEntry("width", (int) i.value()->widget()->width()); dockGroup.writeEntry("height", (int) i.value()->widget()->height()); } } } KSharedConfig::openConfig()->sync(); resetAutoSaveSettings(); // Don't let KMainWindow override the good stuff we wrote down } void KisMainWindow::resizeEvent(QResizeEvent * e) { d->windowSizeDirty = true; KXmlGuiWindow::resizeEvent(e); } void KisMainWindow::setActiveView(KisView* view) { d->activeView = view; updateCaption(); if (d->undoActionsUpdateManager) { d->undoActionsUpdateManager->setCurrentDocument(view ? view->document() : 0); } d->viewManager->setCurrentView(view); KisWindowLayoutManager::instance()->activeDocumentChanged(view->document()); } void KisMainWindow::dragMove(QDragMoveEvent * event) { QTabBar *tabBar = d->findTabBarHACK(); if (!tabBar && d->mdiArea->viewMode() == QMdiArea::TabbedView) { qWarning() << "WARNING!!! Cannot find QTabBar in the main window! Looks like Qt has changed behavior. Drag & Drop between multiple tabs might not work properly (tabs will not switch automatically)!"; } if (tabBar && tabBar->isVisible()) { QPoint pos = tabBar->mapFromGlobal(mapToGlobal(event->pos())); if (tabBar->rect().contains(pos)) { const int tabIndex = tabBar->tabAt(pos); if (tabIndex >= 0 && tabBar->currentIndex() != tabIndex) { d->tabSwitchCompressor->start(tabIndex); } } else if (d->tabSwitchCompressor->isActive()) { d->tabSwitchCompressor->stop(); } } } void KisMainWindow::dragLeave() { if (d->tabSwitchCompressor->isActive()) { d->tabSwitchCompressor->stop(); } } void KisMainWindow::switchTab(int index) { QTabBar *tabBar = d->findTabBarHACK(); if (!tabBar) return; tabBar->setCurrentIndex(index); } void KisMainWindow::showWelcomeScreen(bool show) { d->widgetStack->setCurrentIndex(!show); } void KisMainWindow::slotFileNew() { const QStringList mimeFilter = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Import); KisOpenPane *startupWidget = new KisOpenPane(this, mimeFilter, QStringLiteral("templates/")); startupWidget->setWindowModality(Qt::WindowModal); startupWidget->setWindowTitle(i18n("Create new document")); KisConfig cfg(true); int w = cfg.defImageWidth(); int h = cfg.defImageHeight(); const double resolution = cfg.defImageResolution(); const QString colorModel = cfg.defColorModel(); const QString colorDepth = cfg.defaultColorDepth(); const QString colorProfile = cfg.defColorProfile(); CustomDocumentWidgetItem item; item.widget = new KisCustomImageWidget(startupWidget, w, h, resolution, colorModel, colorDepth, colorProfile, i18n("Unnamed")); item.icon = "document-new"; item.title = i18n("Custom Document"); startupWidget->addCustomDocumentWidget(item.widget, item.title, "Custom Document", item.icon); QSize sz = KisClipboard::instance()->clipSize(); if (sz.isValid() && sz.width() != 0 && sz.height() != 0) { w = sz.width(); h = sz.height(); } item.widget = new KisImageFromClipboard(startupWidget, w, h, resolution, colorModel, colorDepth, colorProfile, i18n("Unnamed")); item.title = i18n("Create from Clipboard"); item.icon = "tab-new"; startupWidget->addCustomDocumentWidget(item.widget, item.title, "Create from ClipBoard", item.icon); // calls deleteLater connect(startupWidget, SIGNAL(documentSelected(KisDocument*)), KisPart::instance(), SLOT(startCustomDocument(KisDocument*))); // calls deleteLater connect(startupWidget, SIGNAL(openTemplate(QUrl)), KisPart::instance(), SLOT(openTemplate(QUrl))); startupWidget->exec(); // Cancel calls deleteLater... } void KisMainWindow::slotImportFile() { dbgUI << "slotImportFile()"; slotFileOpen(true); } void KisMainWindow::slotFileOpen(bool isImporting) { #ifndef Q_OS_ANDROID QStringList urls = showOpenFileDialog(isImporting); if (urls.isEmpty()) return; Q_FOREACH (const QString& url, urls) { if (!url.isEmpty()) { OpenFlags flags = isImporting ? Import : None; bool res = openDocument(QUrl::fromLocalFile(url), flags); if (!res) { warnKrita << "Loading" << url << "failed"; } } } #else Q_UNUSED(isImporting) d->fileManager->openImportFile(); connect(d->fileManager, SIGNAL(sigFileSelected(QString)), this, SLOT(slotFileSelected(QString))); connect(d->fileManager, SIGNAL(sigEmptyFilePath()), this, SLOT(slotEmptyFilePath())); #endif } void KisMainWindow::slotFileOpenRecent(const QUrl &url) { (void) openDocument(QUrl::fromLocalFile(url.toLocalFile()), None); } void KisMainWindow::slotFileSave() { if (saveDocument(d->activeView->document(), false, false)) { emit documentSaved(); } } void KisMainWindow::slotFileSaveAs() { if (saveDocument(d->activeView->document(), true, false)) { emit documentSaved(); } } void KisMainWindow::slotExportFile() { if (saveDocument(d->activeView->document(), true, true)) { emit documentSaved(); } } void KisMainWindow::slotShowSessionManager() { KisPart::instance()->showSessionManager(); } KoCanvasResourceProvider *KisMainWindow::resourceManager() const { return d->viewManager->canvasResourceProvider()->resourceManager(); } int KisMainWindow::viewCount() const { return d->mdiArea->subWindowList().size(); } const KConfigGroup &KisMainWindow::windowStateConfig() const { return d->windowStateConfig; } void KisMainWindow::saveWindowState(bool restoreNormalState) { if (restoreNormalState) { QAction *showCanvasOnly = d->viewManager->actionCollection()->action("view_show_canvas_only"); if (showCanvasOnly && showCanvasOnly->isChecked()) { showCanvasOnly->setChecked(false); } d->windowStateConfig.writeEntry("ko_geometry", saveGeometry().toBase64()); d->windowStateConfig.writeEntry("State", saveState().toBase64()); if (!d->dockerStateBeforeHiding.isEmpty()) { restoreState(d->dockerStateBeforeHiding); } statusBar()->setVisible(true); menuBar()->setVisible(true); saveWindowSettings(); } else { saveMainWindowSettings(d->windowStateConfig); } } bool KisMainWindow::restoreWorkspaceState(const QByteArray &state) { QByteArray oldState = saveState(); // needed because otherwise the layout isn't correctly restored in some situations Q_FOREACH (QDockWidget *dock, dockWidgets()) { dock->toggleViewAction()->setEnabled(true); dock->hide(); } bool success = KXmlGuiWindow::restoreState(state); if (!success) { KXmlGuiWindow::restoreState(oldState); return false; } return success; } bool KisMainWindow::restoreWorkspace(KisWorkspaceResource *workspace) { bool success = restoreWorkspaceState(workspace->dockerState()); if (activeKisView()) { activeKisView()->resourceProvider()->notifyLoadingWorkspace(workspace); } return success; } QByteArray KisMainWindow::borrowWorkspace(KisMainWindow *other) { QByteArray currentWorkspace = saveState(); if (!d->workspaceBorrowedBy.isNull()) { if (other->id() == d->workspaceBorrowedBy) { // We're swapping our original workspace back d->workspaceBorrowedBy = QUuid(); return currentWorkspace; } else { // Get our original workspace back before swapping with a third window KisMainWindow *borrower = KisPart::instance()->windowById(d->workspaceBorrowedBy); if (borrower) { QByteArray originalLayout = borrower->borrowWorkspace(this); borrower->restoreWorkspaceState(currentWorkspace); d->workspaceBorrowedBy = other->id(); return originalLayout; } } } d->workspaceBorrowedBy = other->id(); return currentWorkspace; } void KisMainWindow::swapWorkspaces(KisMainWindow *a, KisMainWindow *b) { QByteArray workspaceA = a->borrowWorkspace(b); QByteArray workspaceB = b->borrowWorkspace(a); a->restoreWorkspaceState(workspaceB); b->restoreWorkspaceState(workspaceA); } KisViewManager *KisMainWindow::viewManager() const { return d->viewManager; } void KisMainWindow::slotDocumentInfo() { if (!d->activeView->document()) return; KoDocumentInfo *docInfo = d->activeView->document()->documentInfo(); if (!docInfo) return; KoDocumentInfoDlg *dlg = d->activeView->document()->createDocumentInfoDialog(this, docInfo); if (dlg->exec()) { if (dlg->isDocumentSaved()) { d->activeView->document()->setModified(false); } else { d->activeView->document()->setModified(true); } d->activeView->document()->setTitleModified(); } delete dlg; } bool KisMainWindow::slotFileCloseAll() { Q_FOREACH (QMdiSubWindow *subwin, d->mdiArea->subWindowList()) { if (subwin) { if(!subwin->close()) return false; } } updateCaption(); return true; } void KisMainWindow::slotFileQuit() { // Do not close while KisMainWindow has the savingEntryMutex locked, bug409395. // After the background saving job is initiated, KisDocument blocks closing // while it saves itself. if (hackIsSaving()) { return; } KisPart::instance()->closeSession(); } void KisMainWindow::slotFilePrint() { if (!activeView()) return; KisPrintJob *printJob = activeView()->createPrintJob(); if (printJob == 0) return; applyDefaultSettings(printJob->printer()); QPrintDialog *printDialog = activeView()->createPrintDialog( printJob, this ); if (printDialog && printDialog->exec() == QDialog::Accepted) { printJob->printer().setPageMargins(0.0, 0.0, 0.0, 0.0, QPrinter::Point); printJob->printer().setPaperSize(QSizeF(activeView()->image()->width() / (72.0 * activeView()->image()->xRes()), activeView()->image()->height()/ (72.0 * activeView()->image()->yRes())), QPrinter::Inch); printJob->startPrinting(KisPrintJob::DeleteWhenDone); } else { delete printJob; } delete printDialog; } void KisMainWindow::slotFilePrintPreview() { if (!activeView()) return; KisPrintJob *printJob = activeView()->createPrintJob(); if (printJob == 0) return; /* Sets the startPrinting() slot to be blocking. The Qt print-preview dialog requires the printing to be completely blocking and only return when the full document has been printed. By default the KisPrintingDialog is non-blocking and multithreading, setting blocking to true will allow it to be used in the preview dialog */ printJob->setProperty("blocking", true); QPrintPreviewDialog *preview = new QPrintPreviewDialog(&printJob->printer(), this); printJob->setParent(preview); // will take care of deleting the job connect(preview, SIGNAL(paintRequested(QPrinter*)), printJob, SLOT(startPrinting())); preview->exec(); delete preview; } KisPrintJob* KisMainWindow::exportToPdf(QString pdfFileName) { if (!activeView()) return 0; if (!activeView()->document()) return 0; KoPageLayout pageLayout; pageLayout.width = 0; pageLayout.height = 0; pageLayout.topMargin = 0; pageLayout.bottomMargin = 0; pageLayout.leftMargin = 0; pageLayout.rightMargin = 0; if (pdfFileName.isEmpty()) { KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); QString defaultDir = group.readEntry("SavePdfDialog"); if (defaultDir.isEmpty()) defaultDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); QUrl startUrl = QUrl::fromLocalFile(defaultDir); KisDocument* pDoc = d->activeView->document(); /** if document has a file name, take file name and replace extension with .pdf */ if (pDoc && pDoc->url().isValid()) { startUrl = pDoc->url(); QString fileName = startUrl.toLocalFile(); fileName = fileName.replace( QRegExp( "\\.\\w{2,5}$", Qt::CaseInsensitive ), ".pdf" ); startUrl = startUrl.adjusted(QUrl::RemoveFilename); startUrl.setPath(startUrl.path() + fileName ); } QPointer layoutDlg(new KoPageLayoutDialog(this, pageLayout)); layoutDlg->setWindowModality(Qt::WindowModal); if (layoutDlg->exec() != QDialog::Accepted || !layoutDlg) { delete layoutDlg; return 0; } pageLayout = layoutDlg->pageLayout(); delete layoutDlg; KoFileDialog dialog(this, KoFileDialog::SaveFile, "OpenDocument"); dialog.setCaption(i18n("Export as PDF")); dialog.setDefaultDir(startUrl.toLocalFile()); dialog.setMimeTypeFilters(QStringList() << "application/pdf"); QUrl url = QUrl::fromUserInput(dialog.filename()); pdfFileName = url.toLocalFile(); if (pdfFileName.isEmpty()) return 0; } KisPrintJob *printJob = activeView()->createPrintJob(); if (printJob == 0) return 0; if (isHidden()) { printJob->setProperty("noprogressdialog", true); } applyDefaultSettings(printJob->printer()); // TODO for remote files we have to first save locally and then upload. printJob->printer().setOutputFileName(pdfFileName); printJob->printer().setDocName(pdfFileName); printJob->printer().setColorMode(QPrinter::Color); if (pageLayout.format == KoPageFormat::CustomSize) { printJob->printer().setPaperSize(QSizeF(pageLayout.width, pageLayout.height), QPrinter::Millimeter); } else { printJob->printer().setPaperSize(KoPageFormat::printerPageSize(pageLayout.format)); } printJob->printer().setPageMargins(pageLayout.leftMargin, pageLayout.topMargin, pageLayout.rightMargin, pageLayout.bottomMargin, QPrinter::Millimeter); switch (pageLayout.orientation) { case KoPageFormat::Portrait: printJob->printer().setOrientation(QPrinter::Portrait); break; case KoPageFormat::Landscape: printJob->printer().setOrientation(QPrinter::Landscape); break; } //before printing check if the printer can handle printing if (!printJob->canPrint()) { QMessageBox::critical(this, i18nc("@title:window", "Krita"), i18n("Cannot export to the specified file")); } printJob->startPrinting(KisPrintJob::DeleteWhenDone); return printJob; } void KisMainWindow::importAnimation() { if (!activeView()) return; KisDocument *document = activeView()->document(); if (!document) return; KisDlgImportImageSequence dlg(this, document); if (dlg.exec() == QDialog::Accepted) { QStringList files = dlg.files(); int firstFrame = dlg.firstFrame(); int step = dlg.step(); KoUpdaterPtr updater = !document->fileBatchMode() ? viewManager()->createUnthreadedUpdater(i18n("Import frames")) : 0; KisAnimationImporter importer(document->image(), updater); KisImportExportErrorCode status = importer.import(files, firstFrame, step); if (!status.isOk() && !status.isInternalError()) { QString msg = status.errorMessage(); if (!msg.isEmpty()) QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not finish import animation:\n%1", msg)); } activeView()->canvasBase()->refetchDataFromImage(); } } void KisMainWindow::slotConfigureToolbars() { saveWindowState(); KEditToolBar edit(factory(), this); connect(&edit, SIGNAL(newToolBarConfig()), this, SLOT(slotNewToolbarConfig())); (void) edit.exec(); applyToolBarLayout(); } void KisMainWindow::slotNewToolbarConfig() { applyMainWindowSettings(d->windowStateConfig); KXMLGUIFactory *factory = guiFactory(); Q_UNUSED(factory); // Check if there's an active view if (!d->activeView) return; plugActionList("toolbarlist", d->toolbarList); applyToolBarLayout(); } void KisMainWindow::slotToolbarToggled(bool toggle) { //dbgUI <<"KisMainWindow::slotToolbarToggled" << sender()->name() <<" toggle=" << true; // The action (sender) and the toolbar have the same name KToolBar * bar = toolBar(sender()->objectName()); if (bar) { if (toggle) { bar->show(); } else { bar->hide(); } if (d->activeView && d->activeView->document()) { saveWindowState(); } } else warnUI << "slotToolbarToggled : Toolbar " << sender()->objectName() << " not found!"; } void KisMainWindow::viewFullscreen(bool fullScreen) { KisConfig cfg(false); cfg.setFullscreenMode(fullScreen); if (fullScreen) { setWindowState(windowState() | Qt::WindowFullScreen); // set } else { setWindowState(windowState() & ~Qt::WindowFullScreen); // reset } d->fullScreenMode->setChecked(isFullScreen()); } void KisMainWindow::setMaxRecentItems(uint _number) { d->recentFiles->setMaxItems(_number); } void KisMainWindow::slotReloadFile() { KisDocument* document = d->activeView->document(); if (!document || document->url().isEmpty()) return; if (document->isModified()) { bool ok = QMessageBox::question(this, i18nc("@title:window", "Krita"), i18n("You will lose all changes made since your last save\n" "Do you want to continue?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes; if (!ok) return; } QUrl url = document->url(); saveWindowSettings(); if (!document->reload()) { QMessageBox::critical(this, i18nc("@title:window", "Krita"), i18n("Error: Could not reload this document")); } return; } QDockWidget* KisMainWindow::createDockWidget(KoDockFactoryBase* factory) { QDockWidget* dockWidget = 0; bool lockAllDockers = KisConfig(true).readEntry("LockAllDockerPanels", false); if (!d->dockWidgetsMap.contains(factory->id())) { dockWidget = factory->createDockWidget(); // It is quite possible that a dock factory cannot create the dock; don't // do anything in that case. if (!dockWidget) { warnKrita << "Could not create docker for" << factory->id(); return 0; } dockWidget->setFont(KoDockRegistry::dockFont()); dockWidget->setObjectName(factory->id()); dockWidget->setParent(this); if (lockAllDockers) { if (dockWidget->titleBarWidget()) { dockWidget->titleBarWidget()->setVisible(false); } dockWidget->setFeatures(QDockWidget::NoDockWidgetFeatures); } if (dockWidget->widget() && dockWidget->widget()->layout()) dockWidget->widget()->layout()->setContentsMargins(1, 1, 1, 1); Qt::DockWidgetArea side = Qt::RightDockWidgetArea; bool visible = true; switch (factory->defaultDockPosition()) { case KoDockFactoryBase::DockTornOff: dockWidget->setFloating(true); // position nicely? break; case KoDockFactoryBase::DockTop: side = Qt::TopDockWidgetArea; break; case KoDockFactoryBase::DockLeft: side = Qt::LeftDockWidgetArea; break; case KoDockFactoryBase::DockBottom: side = Qt::BottomDockWidgetArea; break; case KoDockFactoryBase::DockRight: side = Qt::RightDockWidgetArea; break; case KoDockFactoryBase::DockMinimized: default: side = Qt::RightDockWidgetArea; visible = false; } KConfigGroup group = d->windowStateConfig.group("DockWidget " + factory->id()); side = static_cast(group.readEntry("DockArea", static_cast(side))); if (side == Qt::NoDockWidgetArea) side = Qt::RightDockWidgetArea; addDockWidget(side, dockWidget); if (!visible) { dockWidget->hide(); } d->dockWidgetsMap.insert(factory->id(), dockWidget); } else { dockWidget = d->dockWidgetsMap[factory->id()]; } #ifdef Q_OS_MACOS dockWidget->setAttribute(Qt::WA_MacSmallSize, true); #endif dockWidget->setFont(KoDockRegistry::dockFont()); connect(dockWidget, SIGNAL(dockLocationChanged(Qt::DockWidgetArea)), this, SLOT(forceDockTabFonts())); return dockWidget; } void KisMainWindow::forceDockTabFonts() { Q_FOREACH (QObject *child, children()) { if (child->inherits("QTabBar")) { ((QTabBar *)child)->setFont(KoDockRegistry::dockFont()); } } } QList KisMainWindow::dockWidgets() const { return d->dockWidgetsMap.values(); } QDockWidget* KisMainWindow::dockWidget(const QString &id) { if (!d->dockWidgetsMap.contains(id)) return 0; return d->dockWidgetsMap[id]; } QList KisMainWindow::canvasObservers() const { QList observers; Q_FOREACH (QDockWidget *docker, dockWidgets()) { KoCanvasObserverBase *observer = dynamic_cast(docker); if (observer) { observers << observer; } else { warnKrita << docker << "is not a canvas observer"; } } return observers; } void KisMainWindow::toggleDockersVisibility(bool visible) { if (!visible) { d->dockerStateBeforeHiding = saveState(); Q_FOREACH (QObject* widget, children()) { if (widget->inherits("QDockWidget")) { QDockWidget* dw = static_cast(widget); if (dw->isVisible()) { dw->hide(); } } } } else { restoreState(d->dockerStateBeforeHiding); } } void KisMainWindow::slotDocumentTitleModified() { updateCaption(); updateReloadFileAction(d->activeView ? d->activeView->document() : 0); } void KisMainWindow::subWindowActivated() { bool enabled = (activeKisView() != 0); d->mdiCascade->setEnabled(enabled); d->mdiNextWindow->setEnabled(enabled); d->mdiPreviousWindow->setEnabled(enabled); d->mdiTile->setEnabled(enabled); d->close->setEnabled(enabled); d->closeAll->setEnabled(enabled); setActiveSubWindow(d->mdiArea->activeSubWindow()); Q_FOREACH (QToolBar *tb, toolBars()) { if (tb->objectName() == "BrushesAndStuff") { tb->setEnabled(enabled); } } /** * Qt has a weirdness, it has hardcoded shortcuts added to an action * in the window menu. We need to reset the shortcuts for that menu * to nothing, otherwise the shortcuts cannot be made configurable. * * See: https://bugs.kde.org/show_bug.cgi?id=352205 * https://bugs.kde.org/show_bug.cgi?id=375524 * https://bugs.kde.org/show_bug.cgi?id=398729 */ QMdiSubWindow *subWindow = d->mdiArea->currentSubWindow(); if (subWindow) { QMenu *menu = subWindow->systemMenu(); if (menu && menu->actions().size() == 8) { Q_FOREACH (QAction *action, menu->actions()) { action->setShortcut(QKeySequence()); } menu->actions().last()->deleteLater(); } } updateCaption(); d->actionManager()->updateGUI(); } void KisMainWindow::windowFocused() { /** * Notify selection manager so that it could update selection mask overlay */ if (viewManager() && viewManager()->selectionManager()) { viewManager()->selectionManager()->selectionChanged(); } KisPart *kisPart = KisPart::instance(); KisWindowLayoutManager *layoutManager = KisWindowLayoutManager::instance(); if (!layoutManager->primaryWorkspaceFollowsFocus()) return; QUuid primary = layoutManager->primaryWindowId(); if (primary.isNull()) return; if (d->id == primary) { if (!d->workspaceBorrowedBy.isNull()) { KisMainWindow *borrower = kisPart->windowById(d->workspaceBorrowedBy); if (!borrower) return; swapWorkspaces(this, borrower); } } else { if (d->workspaceBorrowedBy == primary) return; KisMainWindow *primaryWindow = kisPart->windowById(primary); if (!primaryWindow) return; swapWorkspaces(this, primaryWindow); } } void KisMainWindow::updateWindowMenu() { QMenu *menu = d->windowMenu->menu(); menu->clear(); menu->addAction(d->newWindow); menu->addAction(d->documentMenu); QMenu *docMenu = d->documentMenu->menu(); docMenu->clear(); QFontMetrics fontMetrics = docMenu->fontMetrics(); int fileStringWidth = int(QApplication::desktop()->screenGeometry(this).width() * .40f); Q_FOREACH (QPointer doc, KisPart::instance()->documents()) { if (doc) { QString title = fontMetrics.elidedText(doc->url().toDisplayString(QUrl::PreferLocalFile), Qt::ElideMiddle, fileStringWidth); if (title.isEmpty() && doc->image()) { title = doc->image()->objectName(); } QAction *action = docMenu->addAction(title); action->setIcon(qApp->windowIcon()); connect(action, SIGNAL(triggered()), d->documentMapper, SLOT(map())); d->documentMapper->setMapping(action, doc); } } menu->addAction(d->workspaceMenu); QMenu *workspaceMenu = d->workspaceMenu->menu(); workspaceMenu->clear(); auto workspaces = KisResourceServerProvider::instance()->workspaceServer()->resources(); auto m_this = this; for (auto &w : workspaces) { auto action = workspaceMenu->addAction(w->name()); connect(action, &QAction::triggered, this, [=]() { m_this->restoreWorkspace(w); }); } workspaceMenu->addSeparator(); connect(workspaceMenu->addAction(i18nc("@action:inmenu", "&Import Workspace...")), &QAction::triggered, this, [&]() { QString extensions = d->workspacemodel->extensions(); QStringList mimeTypes; for(const QString &suffix : extensions.split(":")) { mimeTypes << KisMimeDatabase::mimeTypeForSuffix(suffix); } KoFileDialog dialog(0, KoFileDialog::OpenFile, "OpenDocument"); dialog.setMimeTypeFilters(mimeTypes); dialog.setCaption(i18nc("@title:window", "Choose File to Add")); QString filename = dialog.filename(); d->workspacemodel->importResourceFile(filename); }); connect(workspaceMenu->addAction(i18nc("@action:inmenu", "&New Workspace...")), &QAction::triggered, [=]() { QString name = QInputDialog::getText(this, i18nc("@title:window", "New Workspace..."), i18nc("@label:textbox", "Name:")); if (name.isEmpty()) return; auto rserver = KisResourceServerProvider::instance()->workspaceServer(); KisWorkspaceResource* workspace = new KisWorkspaceResource(""); workspace->setDockerState(m_this->saveState()); d->viewManager->canvasResourceProvider()->notifySavingWorkspace(workspace); workspace->setValid(true); QString saveLocation = rserver->saveLocation(); bool newName = false; if(name.isEmpty()) { newName = true; name = i18n("Workspace"); } QFileInfo fileInfo(saveLocation + name + workspace->defaultFileExtension()); int i = 1; while (fileInfo.exists()) { fileInfo.setFile(saveLocation + name + QString("%1").arg(i) + workspace->defaultFileExtension()); i++; } workspace->setFilename(fileInfo.filePath()); if(newName) { name = i18n("Workspace %1", i); } workspace->setName(name); rserver->addResource(workspace); }); // TODO: What to do about delete? // workspaceMenu->addAction(i18nc("@action:inmenu", "&Delete Workspace...")); menu->addSeparator(); menu->addAction(d->close); menu->addAction(d->closeAll); if (d->mdiArea->viewMode() == QMdiArea::SubWindowView) { menu->addSeparator(); menu->addAction(d->mdiTile); menu->addAction(d->mdiCascade); } menu->addSeparator(); menu->addAction(d->mdiNextWindow); menu->addAction(d->mdiPreviousWindow); menu->addSeparator(); QList windows = d->mdiArea->subWindowList(); for (int i = 0; i < windows.size(); ++i) { QPointerchild = qobject_cast(windows.at(i)->widget()); if (child && child->document()) { QString text; if (i < 9) { text = i18n("&%1 %2", i + 1, fontMetrics.elidedText(child->document()->url().toDisplayString(QUrl::PreferLocalFile), Qt::ElideMiddle, fileStringWidth)); } else { text = i18n("%1 %2", i + 1, fontMetrics.elidedText(child->document()->url().toDisplayString(QUrl::PreferLocalFile), Qt::ElideMiddle, fileStringWidth)); } QAction *action = menu->addAction(text); action->setIcon(qApp->windowIcon()); action->setCheckable(true); action->setChecked(child == activeKisView()); connect(action, SIGNAL(triggered()), d->windowMapper, SLOT(map())); d->windowMapper->setMapping(action, windows.at(i)); } } bool showMdiArea = windows.count( ) > 0; if (!showMdiArea) { showWelcomeScreen(true); // see workaround in function in header // keep the recent file list updated when going back to welcome screen reloadRecentFileList(); d->welcomePage->populateRecentDocuments(); } // enable/disable the toolbox docker if there are no documents open Q_FOREACH (QObject* widget, children()) { if (widget->inherits("QDockWidget")) { QDockWidget* dw = static_cast(widget); if ( dw->objectName() == "ToolBox") { dw->setEnabled(showMdiArea); } } } updateCaption(); } void KisMainWindow::setActiveSubWindow(QWidget *window) { if (!window) return; QMdiSubWindow *subwin = qobject_cast(window); //dbgKrita << "setActiveSubWindow();" << subwin << d->activeSubWindow; if (subwin && subwin != d->activeSubWindow) { KisView *view = qobject_cast(subwin->widget()); //dbgKrita << "\t" << view << activeView(); if (view && view != activeView()) { d->mdiArea->setActiveSubWindow(subwin); setActiveView(view); } d->activeSubWindow = subwin; } updateWindowMenu(); d->actionManager()->updateGUI(); } void KisMainWindow::configChanged() { KisConfig cfg(true); QMdiArea::ViewMode viewMode = (QMdiArea::ViewMode)cfg.readEntry("mdi_viewmode", (int)QMdiArea::TabbedView); d->mdiArea->setViewMode(viewMode); Q_FOREACH (QMdiSubWindow *subwin, d->mdiArea->subWindowList()) { subwin->setOption(QMdiSubWindow::RubberBandMove, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); subwin->setOption(QMdiSubWindow::RubberBandResize, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); /** * Dirty workaround for a bug in Qt (checked on Qt 5.6.1): * * If you make a window "Show on top" and then switch to the tabbed mode - * the window will contiue to be painted in its initial "mid-screen" + * the window will continue to be painted in its initial "mid-screen" * position. It will persist here until you explicitly switch to its tab. */ if (viewMode == QMdiArea::TabbedView) { Qt::WindowFlags oldFlags = subwin->windowFlags(); Qt::WindowFlags flags = oldFlags; flags &= ~Qt::WindowStaysOnTopHint; flags &= ~Qt::WindowStaysOnBottomHint; if (flags != oldFlags) { subwin->setWindowFlags(flags); subwin->showMaximized(); } } } KConfigGroup group( KSharedConfig::openConfig(), "theme"); d->themeManager->setCurrentTheme(group.readEntry("Theme", "Krita dark")); d->actionManager()->updateGUI(); QString s = cfg.getMDIBackgroundColor(); KoColor c = KoColor::fromXML(s); QBrush brush(c.toQColor()); d->mdiArea->setBackground(brush); QString backgroundImage = cfg.getMDIBackgroundImage(); if (backgroundImage != "") { QImage image(backgroundImage); QBrush brush(image); d->mdiArea->setBackground(brush); } d->mdiArea->update(); } KisView* KisMainWindow::newView(QObject *document, QMdiSubWindow *subWindow) { KisDocument *doc = qobject_cast(document); KisView *view = addViewAndNotifyLoadingCompleted(doc, subWindow); d->actionManager()->updateGUI(); return view; } void KisMainWindow::newWindow() { KisMainWindow *mainWindow = KisPart::instance()->createMainWindow(); mainWindow->initializeGeometry(); mainWindow->show(); } void KisMainWindow::closeCurrentWindow() { if (d->mdiArea->currentSubWindow()) { d->mdiArea->currentSubWindow()->close(); d->actionManager()->updateGUI(); } } void KisMainWindow::checkSanity() { // print error if the lcms engine is not available if (!KoColorSpaceEngineRegistry::instance()->contains("icc")) { // need to wait 1 event since exiting here would not work. m_errorMessage = i18n("The Krita LittleCMS color management plugin is not installed. Krita will quit now."); m_dieOnError = true; QTimer::singleShot(0, this, SLOT(showErrorAndDie())); return; } KisPaintOpPresetResourceServer * rserver = KisResourceServerProvider::instance()->paintOpPresetServer(); if (rserver->resources().isEmpty()) { m_errorMessage = i18n("Krita cannot find any brush presets! Krita will quit now."); m_dieOnError = true; QTimer::singleShot(0, this, SLOT(showErrorAndDie())); return; } } void KisMainWindow::showErrorAndDie() { QMessageBox::critical(0, i18nc("@title:window", "Installation error"), m_errorMessage); if (m_dieOnError) { exit(10); } } void KisMainWindow::showAboutApplication() { KisAboutApplication dlg(this); dlg.exec(); } QPointer KisMainWindow::activeKisView() { if (!d->mdiArea) return 0; QMdiSubWindow *activeSubWindow = d->mdiArea->activeSubWindow(); //dbgKrita << "activeKisView" << activeSubWindow; if (!activeSubWindow) return 0; return qobject_cast(activeSubWindow->widget()); } void KisMainWindow::newOptionWidgets(KoCanvasController *controller, const QList > &optionWidgetList) { KIS_ASSERT_RECOVER_NOOP(controller == KoToolManager::instance()->activeCanvasController()); bool isOurOwnView = false; Q_FOREACH (QPointer view, KisPart::instance()->views()) { if (view && view->canvasController() == controller) { isOurOwnView = view->mainWindow() == this; } } if (!isOurOwnView) return; Q_FOREACH (QWidget *w, optionWidgetList) { #ifdef Q_OS_MACOS w->setAttribute(Qt::WA_MacSmallSize, true); #endif w->setFont(KoDockRegistry::dockFont()); } if (d->toolOptionsDocker) { d->toolOptionsDocker->setOptionWidgets(optionWidgetList); } else { d->viewManager->paintOpBox()->newOptionWidgets(optionWidgetList); } } void KisMainWindow::applyDefaultSettings(QPrinter &printer) { if (!d->activeView) return; QString title = d->activeView->document()->documentInfo()->aboutInfo("title"); if (title.isEmpty()) { QFileInfo info(d->activeView->document()->url().fileName()); title = info.completeBaseName(); } if (title.isEmpty()) { // #139905 title = i18n("%1 unsaved document (%2)", qApp->applicationDisplayName(), QLocale().toString(QDate::currentDate(), QLocale::ShortFormat)); } printer.setDocName(title); } void KisMainWindow::createActions() { KisActionManager *actionManager = d->actionManager(); actionManager->createStandardAction(KStandardAction::New, this, SLOT(slotFileNew())); actionManager->createStandardAction(KStandardAction::Open, this, SLOT(slotFileOpen())); actionManager->createStandardAction(KStandardAction::Quit, this, SLOT(slotFileQuit())); actionManager->createStandardAction(KStandardAction::ConfigureToolbars, this, SLOT(slotConfigureToolbars())); d->fullScreenMode = actionManager->createStandardAction(KStandardAction::FullScreen, this, SLOT(viewFullscreen(bool))); d->recentFiles = KStandardAction::openRecent(this, SLOT(slotFileOpenRecent(QUrl)), actionCollection()); connect(d->recentFiles, SIGNAL(recentListCleared()), this, SLOT(saveRecentFiles())); KSharedConfigPtr configPtr = KSharedConfig::openConfig(); d->recentFiles->loadEntries(configPtr->group("RecentFiles")); d->saveAction = actionManager->createStandardAction(KStandardAction::Save, this, SLOT(slotFileSave())); d->saveAction->setActivationFlags(KisAction::ACTIVE_IMAGE); d->saveActionAs = actionManager->createStandardAction(KStandardAction::SaveAs, this, SLOT(slotFileSaveAs())); d->saveActionAs->setActivationFlags(KisAction::ACTIVE_IMAGE); // d->printAction = actionManager->createStandardAction(KStandardAction::Print, this, SLOT(slotFilePrint())); // d->printAction->setActivationFlags(KisAction::ACTIVE_IMAGE); // d->printActionPreview = actionManager->createStandardAction(KStandardAction::PrintPreview, this, SLOT(slotFilePrintPreview())); // d->printActionPreview->setActivationFlags(KisAction::ACTIVE_IMAGE); d->undo = actionManager->createStandardAction(KStandardAction::Undo, this, SLOT(undo())); d->undo->setActivationFlags(KisAction::ACTIVE_IMAGE); d->redo = actionManager->createStandardAction(KStandardAction::Redo, this, SLOT(redo())); d->redo->setActivationFlags(KisAction::ACTIVE_IMAGE); d->undoActionsUpdateManager.reset(new KisUndoActionsUpdateManager(d->undo, d->redo)); d->undoActionsUpdateManager->setCurrentDocument(d->activeView ? d->activeView->document() : 0); // d->exportPdf = actionManager->createAction("file_export_pdf"); // connect(d->exportPdf, SIGNAL(triggered()), this, SLOT(exportToPdf())); d->importAnimation = actionManager->createAction("file_import_animation"); connect(d->importAnimation, SIGNAL(triggered()), this, SLOT(importAnimation())); d->closeAll = actionManager->createAction("file_close_all"); connect(d->closeAll, SIGNAL(triggered()), this, SLOT(slotFileCloseAll())); // d->reloadFile = actionManager->createAction("file_reload_file"); // d->reloadFile->setActivationFlags(KisAction::CURRENT_IMAGE_MODIFIED); // connect(d->reloadFile, SIGNAL(triggered(bool)), this, SLOT(slotReloadFile())); d->importFile = actionManager->createAction("file_import_file"); connect(d->importFile, SIGNAL(triggered(bool)), this, SLOT(slotImportFile())); d->exportFile = actionManager->createAction("file_export_file"); connect(d->exportFile, SIGNAL(triggered(bool)), this, SLOT(slotExportFile())); /* The following entry opens the document information dialog. Since the action is named so it intends to show data this entry should not have a trailing ellipses (...). */ d->showDocumentInfo = actionManager->createAction("file_documentinfo"); connect(d->showDocumentInfo, SIGNAL(triggered(bool)), this, SLOT(slotDocumentInfo())); d->themeManager->setThemeMenuAction(new KActionMenu(i18nc("@action:inmenu", "&Themes"), this)); d->themeManager->registerThemeActions(actionCollection()); connect(d->themeManager, SIGNAL(signalThemeChanged()), this, SLOT(slotThemeChanged())); connect(d->themeManager, SIGNAL(signalThemeChanged()), d->welcomePage, SLOT(slotUpdateThemeColors())); d->toggleDockers = actionManager->createAction("view_toggledockers"); KisConfig(true).showDockers(true); d->toggleDockers->setChecked(true); connect(d->toggleDockers, SIGNAL(toggled(bool)), SLOT(toggleDockersVisibility(bool))); d->toggleDetachCanvas = actionManager->createAction("view_detached_canvas"); d->toggleDetachCanvas->setChecked(false); connect(d->toggleDetachCanvas, SIGNAL(toggled(bool)), SLOT(setCanvasDetached(bool))); setCanvasDetached(false); actionCollection()->addAction("settings_dockers_menu", d->dockWidgetMenu); actionCollection()->addAction("window", d->windowMenu); d->mdiCascade = actionManager->createAction("windows_cascade"); connect(d->mdiCascade, SIGNAL(triggered()), d->mdiArea, SLOT(cascadeSubWindows())); d->mdiTile = actionManager->createAction("windows_tile"); connect(d->mdiTile, SIGNAL(triggered()), d->mdiArea, SLOT(tileSubWindows())); d->mdiNextWindow = actionManager->createAction("windows_next"); connect(d->mdiNextWindow, SIGNAL(triggered()), d->mdiArea, SLOT(activateNextSubWindow())); d->mdiPreviousWindow = actionManager->createAction("windows_previous"); connect(d->mdiPreviousWindow, SIGNAL(triggered()), d->mdiArea, SLOT(activatePreviousSubWindow())); d->newWindow = actionManager->createAction("view_newwindow"); connect(d->newWindow, SIGNAL(triggered(bool)), this, SLOT(newWindow())); d->close = actionManager->createStandardAction(KStandardAction::Close, this, SLOT(closeCurrentWindow())); d->showSessionManager = actionManager->createAction("file_sessions"); connect(d->showSessionManager, SIGNAL(triggered(bool)), this, SLOT(slotShowSessionManager())); actionManager->createStandardAction(KStandardAction::Preferences, this, SLOT(slotPreferences())); for (int i = 0; i < 2; i++) { d->expandingSpacers[i] = new KisAction(i18n("Expanding Spacer")); d->expandingSpacers[i]->setDefaultWidget(new QWidget(this)); d->expandingSpacers[i]->defaultWidget()->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); actionManager->addAction(QString("expanding_spacer_%1").arg(i), d->expandingSpacers[i]); } } void KisMainWindow::applyToolBarLayout() { const bool isPlastiqueStyle = style()->objectName() == "plastique"; Q_FOREACH (KToolBar *toolBar, toolBars()) { toolBar->layout()->setSpacing(4); if (isPlastiqueStyle) { toolBar->setContentsMargins(0, 0, 0, 2); } //Hide text for buttons with an icon in the toolbar Q_FOREACH (QAction *ac, toolBar->actions()){ if (ac->icon().pixmap(QSize(1,1)).isNull() == false){ ac->setPriority(QAction::LowPriority); }else { ac->setIcon(QIcon()); } } } } void KisMainWindow::initializeGeometry() { // if the user didn's specify the geometry on the command line (does anyone do that still?), // we first figure out some good default size and restore the x,y position. See bug 285804Z. KConfigGroup cfg = d->windowStateConfig; QByteArray geom = QByteArray::fromBase64(cfg.readEntry("ko_geometry", QByteArray())); if (!restoreGeometry(geom)) { const int scnum = QApplication::desktop()->screenNumber(parentWidget()); QRect desk = QGuiApplication::screens().at(scnum)->availableVirtualGeometry(); quint32 x = desk.x(); quint32 y = desk.y(); quint32 w = 0; quint32 h = 0; // Default size -- maximize on small screens, something useful on big screens const int deskWidth = desk.width(); if (deskWidth > 1024) { // a nice width, and slightly less than total available // height to compensate for the window decs w = (deskWidth / 3) * 2; h = (desk.height() / 3) * 2; } else { w = desk.width(); h = desk.height(); } x += (desk.width() - w) / 2; y += (desk.height() - h) / 2; move(x,y); setGeometry(geometry().x(), geometry().y(), w, h); } d->fullScreenMode->setChecked(isFullScreen()); } void KisMainWindow::showManual() { QDesktopServices::openUrl(QUrl("https://docs.krita.org")); } void KisMainWindow::windowScreenChanged(QScreen *screen) { emit screenChanged(); d->screenConnectionsStore.clear(); d->screenConnectionsStore.addConnection(screen, SIGNAL(physicalDotsPerInchChanged(qreal)), this, SIGNAL(screenChanged())); } void KisMainWindow::slotXmlGuiMakingChanges(bool finished) { if (finished) { subWindowActivated(); } } #include diff --git a/libs/ui/KisPaletteEditor.h b/libs/ui/KisPaletteEditor.h index a073381f1c..b11c05a977 100644 --- a/libs/ui/KisPaletteEditor.h +++ b/libs/ui/KisPaletteEditor.h @@ -1,146 +1,146 @@ /* * Copyright (c) 2018 Michael Zhou * * 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 KISPALETTEMANAGER_H #define KISPALETTEMANAGER_H #include #include #include #include class KoColorSet; class KisPaletteModel; class KisViewManager; class KisSwatchGroup; class KisViewManager; /** * @brief The PaletteEditor class * this class manipulates a KisPaletteModel using GUI elements and communicate * with KisDocument * * Changes made in this class won't be done to the palette if the palette is * read only (not editable, isEditable() == false) */ class KRITAUI_EXPORT KisPaletteEditor : public QObject { Q_OBJECT public: struct PaletteInfo; public: explicit KisPaletteEditor(QObject *parent = 0); ~KisPaletteEditor(); void setPaletteModel(KisPaletteModel *model); void setView(KisViewManager *view); void addPalette(); void importPalette(); void removePalette(KoColorSet *); /** * @brief rowNumberOfGroup * @param oriName the original name of a group at the creation of the instance * @return newest row number of the group */ int rowNumberOfGroup(const QString &oriName) const; /** * @brief oldNameFromNewName * @param newName the current name of a group * @return the name of the group at the creation of the instance */ QString oldNameFromNewName(const QString &newName) const; /** * @brief duplicateExistsFilename * @param filename the name of the file * @param global if this filename is going to be used for a global palette * @return true if the a palette in the resource system that has filename * name already exists else false */ bool duplicateExistsFilename(const QString &filename, bool global) const; QString relativePathFromSaveLocation() const; void rename(const QString &newName); void changeFilename(const QString &newName); void changeColCount(int); /** * @brief addGroup * @return new group's name if change accepted, empty string if cancelled */ QString addGroup(); /** * @brief removeGroup * @param name original group name * @return true if change accepted, false if cancelled */ bool removeGroup(const QString &name); /** * @brief renameGroup * @param oldName - * @return new name if change accpeted, empty string if cancelled + * @return new name if change accepted, empty string if cancelled */ QString renameGroup(const QString &oldName); void changeGroupRowCount(const QString &name, int newRowCount); void setGlobal(bool); void setReadOnly(bool); void setEntry(const KoColor &color, const QModelIndex &index); void removeEntry(const QModelIndex &index); void modifyEntry(const QModelIndex &index); void addEntry(const KoColor &color); bool isModified() const; /** * @brief getModifiedGroup * @param originalName name of the group at the creation of the instance * @return the modified group */ const KisSwatchGroup &getModifiedGroup(const QString &originalName) const; /** * @brief updatePalette * MUST be called to make the changes into the resource server */ void updatePalette(); private Q_SLOTS: void slotGroupNameChanged(const QString &newName); void slotPaletteChanged(); void slotSetDocumentModified(); private: QString newPaletteFileName(bool isGlobal, const QString &filename = QString()); QString newGroupName() const; void setNonGlobal(); void setGlobal(); bool duplicateExistsGroupName(const QString &name) const; bool duplicateExistsOriginalGroupName(const QString &name) const; void uploadPaletteList() const; QString filenameFromPath(const QString &path) const; private: struct Private; QScopedPointer m_d; }; #endif // KISPALETTEMANAGER_H diff --git a/libs/ui/KisWelcomePageWidget.cpp b/libs/ui/KisWelcomePageWidget.cpp index 7ab6bcd89a..179f67293c 100644 --- a/libs/ui/KisWelcomePageWidget.cpp +++ b/libs/ui/KisWelcomePageWidget.cpp @@ -1,456 +1,459 @@ /* This file is part of the KDE project * Copyright (C) 2018 Scott Petrovic * * 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 "KisWelcomePageWidget.h" #include #include #include #include #include #include +#include #include "kis_action_manager.h" #include "kactioncollection.h" #include "kis_action.h" #include "KConfigGroup" #include "KSharedConfig" #include #include #include "kis_icon_utils.h" #include "krita_utils.h" #include "KoStore.h" #include "kis_config.h" #include "KisDocument.h" #include #include #include KisWelcomePageWidget::KisWelcomePageWidget(QWidget *parent) : QWidget(parent) { setupUi(this); recentDocumentsListView->setDragEnabled(false); recentDocumentsListView->viewport()->setAutoFillBackground(false); recentDocumentsListView->setSpacing(2); // set up URLs that go to web browser manualLink->setTextFormat(Qt::RichText); manualLink->setTextInteractionFlags(Qt::TextBrowserInteraction); manualLink->setOpenExternalLinks(true); gettingStartedLink->setTextFormat(Qt::RichText); gettingStartedLink->setTextInteractionFlags(Qt::TextBrowserInteraction); gettingStartedLink->setOpenExternalLinks(true); supportKritaLink->setTextFormat(Qt::RichText); supportKritaLink->setTextInteractionFlags(Qt::TextBrowserInteraction); supportKritaLink->setOpenExternalLinks(true); userCommunityLink->setTextFormat(Qt::RichText); userCommunityLink->setTextInteractionFlags(Qt::TextBrowserInteraction); userCommunityLink->setOpenExternalLinks(true); kritaWebsiteLink->setTextFormat(Qt::RichText); kritaWebsiteLink->setTextInteractionFlags(Qt::TextBrowserInteraction); kritaWebsiteLink->setOpenExternalLinks(true); sourceCodeLink->setTextFormat(Qt::RichText); sourceCodeLink->setTextInteractionFlags(Qt::TextBrowserInteraction); sourceCodeLink->setOpenExternalLinks(true); poweredByKDELink->setTextFormat(Qt::RichText); poweredByKDELink->setTextInteractionFlags(Qt::TextBrowserInteraction); poweredByKDELink->setOpenExternalLinks(true); kdeIcon->setIconSize(QSize(20, 20)); kdeIcon->setIcon(KisIconUtils::loadIcon(QStringLiteral("kde")).pixmap(20)); versionNotificationLabel->setTextFormat(Qt::RichText); versionNotificationLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); versionNotificationLabel->setOpenExternalLinks(true); connect(chkShowNews, SIGNAL(toggled(bool)), newsWidget, SLOT(toggleNews(bool))); connect(newsWidget, SIGNAL(newsDataChanged()), this, SLOT(slotUpdateVersionMessage())); #ifdef Q_OS_ANDROID // checking this widgets crashes the app, so it is better for it to be hidden for now newsWidget->hide(); helpTitleLabel_2->hide(); chkShowNews->hide(); #endif // configure the News area KisConfig cfg(true); bool m_getNews = cfg.readEntry("FetchNews", false); chkShowNews->setChecked(m_getNews); setAcceptDrops(true); } KisWelcomePageWidget::~KisWelcomePageWidget() { } void KisWelcomePageWidget::setMainWindow(KisMainWindow* mainWin) { if (mainWin) { m_mainWindow = mainWin; // set the shortcut links from actions (only if a shortcut exists) if ( mainWin->viewManager()->actionManager()->actionByName("file_new")->shortcut().toString() != "") { newFileLinkShortcut->setText(QString("(") + mainWin->viewManager()->actionManager()->actionByName("file_new")->shortcut().toString() + QString(")")); } if (mainWin->viewManager()->actionManager()->actionByName("file_open")->shortcut().toString() != "") { openFileShortcut->setText(QString("(") + mainWin->viewManager()->actionManager()->actionByName("file_open")->shortcut().toString() + QString(")")); } connect(recentDocumentsListView, SIGNAL(clicked(QModelIndex)), this, SLOT(recentDocumentClicked(QModelIndex))); // we need the view manager to actually call actions, so don't create the connections // until after the view manager is set connect(newFileLink, SIGNAL(clicked(bool)), this, SLOT(slotNewFileClicked())); connect(openFileLink, SIGNAL(clicked(bool)), this, SLOT(slotOpenFileClicked())); connect(clearRecentFilesLink, SIGNAL(clicked(bool)), mainWin, SLOT(clearRecentFiles())); slotUpdateThemeColors(); // allows RSS news items to apply analytics tracking. newsWidget->setAnalyticsTracking("?" + analyticsString); } } void KisWelcomePageWidget::showDropAreaIndicator(bool show) { if (!show) { QString dropFrameStyle = "QFrame#dropAreaIndicator { border: 0px }"; dropFrameBorder->setStyleSheet(dropFrameStyle); } else { QColor textColor = qApp->palette().color(QPalette::Text); QColor backgroundColor = qApp->palette().color(QPalette::Background); QColor blendedColor = KritaUtils::blendColors(textColor, backgroundColor, 0.8); // QColor.name() turns it into a hex/web format QString dropFrameStyle = QString("QFrame#dropAreaIndicator { border: 2px dotted ").append(blendedColor.name()).append(" }") ; dropFrameBorder->setStyleSheet(dropFrameStyle); } } void KisWelcomePageWidget::slotUpdateThemeColors() { textColor = qApp->palette().color(QPalette::Text); backgroundColor = qApp->palette().color(QPalette::Background); // make the welcome screen labels a subtle color so it doesn't clash with the main UI elements blendedColor = KritaUtils::blendColors(textColor, backgroundColor, 0.8); blendedStyle = QString("color: ").append(blendedColor.name()); // what labels to change the color... startTitleLabel->setStyleSheet(blendedStyle); recentDocumentsLabel->setStyleSheet(blendedStyle); helpTitleLabel->setStyleSheet(blendedStyle); newFileLinkShortcut->setStyleSheet(blendedStyle); openFileShortcut->setStyleSheet(blendedStyle); clearRecentFilesLink->setStyleSheet(blendedStyle); recentDocumentsListView->setStyleSheet(blendedStyle); newFileLink->setStyleSheet(blendedStyle); openFileLink->setStyleSheet(blendedStyle); // giving the drag area messaging a dotted border QString dottedBorderStyle = QString("border: 2px dotted ").append(blendedColor.name()).append("; color:").append(blendedColor.name()).append( ";"); dragImageHereLabel->setStyleSheet(dottedBorderStyle); // make drop area QFrame have a dotted line dropFrameBorder->setObjectName("dropAreaIndicator"); QString dropFrameStyle = QString("QFrame#dropAreaIndicator { border: 4px dotted ").append(blendedColor.name()).append("}"); dropFrameBorder->setStyleSheet(dropFrameStyle); // only show drop area when we have a document over the empty area showDropAreaIndicator(false); // add icons for new and open settings to make them stand out a bit more openFileLink->setIconSize(QSize(30, 30)); newFileLink->setIconSize(QSize(30, 30)); openFileLink->setIcon(KisIconUtils::loadIcon("document-open")); newFileLink->setIcon(KisIconUtils::loadIcon("document-new")); kdeIcon->setIcon(KisIconUtils::loadIcon(QStringLiteral("kde")).pixmap(20)); // HTML links seem to be a bit more stubborn with theme changes... setting inline styles to help with color change userCommunityLink->setText(QString("") .append(i18n("User Community")).append("")); gettingStartedLink->setText(QString("") .append(i18n("Getting Started")).append("")); manualLink->setText(QString("") .append(i18n("User Manual")).append("")); supportKritaLink->setText(QString("") .append(i18n("Support Krita")).append("")); kritaWebsiteLink->setText(QString("") .append(i18n("Krita Website")).append("")); sourceCodeLink->setText(QString("") .append(i18n("Source Code")).append("")); poweredByKDELink->setText(QString("") .append(i18n("Powered by KDE")).append("")); slotUpdateVersionMessage(); // text set from RSS feed // re-populate recent files since they might have themed icons populateRecentDocuments(); } void KisWelcomePageWidget::populateRecentDocuments() { m_recentFilesModel.clear(); // clear existing data before it gets re-populated // grab recent files data int numRecentFiles = m_mainWindow->recentFilesUrls().length() > 5 ? 5 : m_mainWindow->recentFilesUrls().length(); // grab at most 5 for (int i = 0; i < numRecentFiles; i++ ) { QStandardItem *recentItem = new QStandardItem(1,2); // 1 row, 1 column recentItem->setIcon(KisIconUtils::loadIcon("document-export")); QString recentFileUrlPath = m_mainWindow->recentFilesUrls().at(i).toLocalFile(); QString fileName = recentFileUrlPath.split("/").last(); QList brokenUrls; if (m_thumbnailMap.contains(recentFileUrlPath)) { recentItem->setIcon(m_thumbnailMap[recentFileUrlPath]); } else { QFileInfo fi(recentFileUrlPath); if (fi.exists()) { - if (fi.suffix() == "ora" || fi.suffix() == "kra") { + QString mimeType = KisMimeDatabase::mimeTypeForFile(recentFileUrlPath); + if (mimeType == KisDocument::nativeFormatMimeType() + || mimeType == "image/openraster") { QScopedPointer store(KoStore::createStore(recentFileUrlPath, KoStore::Read)); if (store) { QString thumbnailpath; if (store->hasFile(QString("Thumbnails/thumbnail.png"))){ thumbnailpath = QString("Thumbnails/thumbnail.png"); } else if (store->hasFile(QString("preview.png"))) { thumbnailpath = QString("preview.png"); } if (!thumbnailpath.isEmpty()) { if (store->open(thumbnailpath)) { QByteArray bytes = store->read(store->size()); store->close(); QImage img; img.loadFromData(bytes); img.setDevicePixelRatio(devicePixelRatioF()); recentItem->setIcon(QIcon(QPixmap::fromImage(img))); } } } else { brokenUrls << m_mainWindow->recentFilesUrls().at(i); } } - else if (fi.suffix() == "tiff" || fi.suffix() == "tif") { + else if (mimeType == "image/tiff" || mimeType == "image/x-tiff") { // Workaround for a bug in Qt tiff QImageIO plugin QScopedPointer doc; doc.reset(KisPart::instance()->createDocument()); doc->setFileBatchMode(true); bool r = doc->openUrl(QUrl::fromLocalFile(recentFileUrlPath), KisDocument::DontAddToRecent); if (r) { KisPaintDeviceSP projection = doc->image()->projection(); recentItem->setIcon(QIcon(QPixmap::fromImage(projection->createThumbnail(48, 48, projection->exactBounds())))); } else { brokenUrls << m_mainWindow->recentFilesUrls().at(i); } } else { QImage img; img.setDevicePixelRatio(devicePixelRatioF()); img.load(recentFileUrlPath); if (!img.isNull()) { recentItem->setIcon(QIcon(QPixmap::fromImage(img.scaledToWidth(48)))); } else { brokenUrls << m_mainWindow->recentFilesUrls().at(i); } } if (brokenUrls.size() > 0 && brokenUrls.last().toLocalFile() != recentFileUrlPath) { m_thumbnailMap[recentFileUrlPath] = recentItem->icon(); } } } Q_FOREACH(const QUrl &url, brokenUrls) { m_mainWindow->removeRecentUrl(url); } // set the recent object with the data if (brokenUrls.isEmpty() || brokenUrls.last().toLocalFile() != recentFileUrlPath) { recentItem->setText(fileName); // what to display for the item recentItem->setToolTip(recentFileUrlPath); m_recentFilesModel.appendRow(recentItem); } } // hide clear and Recent files title if there are none bool hasRecentFiles = m_mainWindow->recentFilesUrls().length() > 0; recentDocumentsLabel->setVisible(hasRecentFiles); clearRecentFilesLink->setVisible(hasRecentFiles); recentDocumentsListView->setIconSize(QSize(48, 48)); recentDocumentsListView->setModel(&m_recentFilesModel); } void KisWelcomePageWidget::slotUpdateVersionMessage() { alertIcon->setIcon(KisIconUtils::loadIcon("warning")); alertIcon->setVisible(false); // find out if we need an update...or if this is a development version: // dev builds contain GIT hash in it and the word git // stable versions do not contain this. if (qApp->applicationVersion().contains("git")) { // Development build QString versionLabelText = QString("") .append(i18n("DEV BUILD")).append(""); versionNotificationLabel->setText(versionLabelText); alertIcon->setVisible(true); versionNotificationLabel->setVisible(true); } else if (newsWidget->hasUpdateAvailable()) { // build URL for label QString versionLabelText = QString("versionLink() + "?" + analyticsString + "version-update" + "\">") .append(i18n("New Version Available!")).append(""); versionNotificationLabel->setVisible(true); versionNotificationLabel->setText(versionLabelText); alertIcon->setVisible(true); } else { // no message needed... exit versionNotificationLabel->setVisible(false); return; } if (!blendedStyle.isNull()) { versionNotificationLabel->setStyleSheet(blendedStyle); } } void KisWelcomePageWidget::dragEnterEvent(QDragEnterEvent *event) { //qDebug() << "dragEnterEvent formats" << event->mimeData()->formats() << "urls" << event->mimeData()->urls() << "has images" << event->mimeData()->hasImage(); showDropAreaIndicator(true); if (event->mimeData()->hasUrls() || event->mimeData()->hasFormat("application/x-krita-node") || event->mimeData()->hasFormat("application/x-qt-image")) { event->accept(); } } void KisWelcomePageWidget::dropEvent(QDropEvent *event) { //qDebug() << "KisWelcomePageWidget::dropEvent() formats" << event->mimeData()->formats() << "urls" << event->mimeData()->urls() << "has images" << event->mimeData()->hasImage(); showDropAreaIndicator(false); if (event->mimeData()->hasUrls() && event->mimeData()->urls().size() > 0) { Q_FOREACH (const QUrl &url, event->mimeData()->urls()) { if (url.toLocalFile().endsWith(".bundle")) { bool r = m_mainWindow->installBundle(url.toLocalFile()); if (!r) { qWarning() << "Could not install bundle" << url.toLocalFile(); } } else { m_mainWindow->openDocument(url, KisMainWindow::None); } } } } void KisWelcomePageWidget::dragMoveEvent(QDragMoveEvent *event) { //qDebug() << "dragMoveEvent"; m_mainWindow->dragMoveEvent(event); if (event->mimeData()->hasUrls() || event->mimeData()->hasFormat("application/x-krita-node") || event->mimeData()->hasFormat("application/x-qt-image")) { event->accept(); } } void KisWelcomePageWidget::dragLeaveEvent(QDragLeaveEvent */*event*/) { //qDebug() << "dragLeaveEvent"; showDropAreaIndicator(false); m_mainWindow->dragLeave(); } void KisWelcomePageWidget::recentDocumentClicked(QModelIndex index) { QString fileUrl = index.data(Qt::ToolTipRole).toString(); m_mainWindow->openDocument(QUrl::fromLocalFile(fileUrl), KisMainWindow::None ); } void KisWelcomePageWidget::slotNewFileClicked() { m_mainWindow->slotFileNew(); } void KisWelcomePageWidget::slotOpenFileClicked() { m_mainWindow->slotFileOpen(); } diff --git a/libs/ui/canvas/kis_canvas2.cpp b/libs/ui/canvas/kis_canvas2.cpp index 5d87d911d3..176ec9a78a 100644 --- a/libs/ui/canvas/kis_canvas2.cpp +++ b/libs/ui/canvas/kis_canvas2.cpp @@ -1,1280 +1,1278 @@ /* This file is part of the KDE project * * Copyright (C) 2006, 2010 Boudewijn Rempt * Copyright (C) Lukáš Tvrdý , (C) 2010 * Copyright (C) 2011 Silvio Heinrich * * 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_canvas2.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_tool_proxy.h" #include "kis_coordinates_converter.h" #include "kis_prescaled_projection.h" #include "kis_image.h" #include "kis_image_barrier_locker.h" #include "kis_undo_adapter.h" #include "flake/kis_shape_layer.h" #include "kis_canvas_resource_provider.h" #include "KisViewManager.h" #include "kis_config.h" #include "kis_config_notifier.h" #include "kis_abstract_canvas_widget.h" #include "kis_qpainter_canvas.h" #include "kis_group_layer.h" #include "flake/kis_shape_controller.h" #include "kis_node_manager.h" #include "kis_selection.h" #include "kis_selection_component.h" #include "flake/kis_shape_selection.h" #include "kis_selection_mask.h" #include "kis_image_config.h" #include "kis_infinity_manager.h" #include "kis_signal_compressor.h" #include "kis_display_color_converter.h" #include "kis_exposure_gamma_correction_interface.h" #include "KisView.h" #include "kis_canvas_controller.h" #include "kis_grid_config.h" #include "kis_animation_player.h" #include "kis_animation_frame_cache.h" #include "opengl/kis_opengl_canvas2.h" #include "opengl/kis_opengl.h" #include "kis_fps_decoration.h" #include "KoColorConversionTransformation.h" #include "KisProofingConfiguration.h" #include #include #include "input/kis_input_manager.h" #include "kis_painting_assistants_decoration.h" #include "kis_canvas_updates_compressor.h" #include "KoZoomController.h" #include #include "opengl/kis_opengl_canvas_debugger.h" #include "kis_algebra_2d.h" #include "kis_image_signal_router.h" #include "KisSnapPixelStrategy.h" class Q_DECL_HIDDEN KisCanvas2::KisCanvas2Private { public: KisCanvas2Private(KoCanvasBase *parent, KisCoordinatesConverter* coordConverter, QPointer view, KoCanvasResourceProvider* resourceManager) : coordinatesConverter(coordConverter) , view(view) , shapeManager(parent) , selectedShapesProxy(&shapeManager) , toolProxy(parent) , displayColorConverter(resourceManager, view) , regionOfInterestUpdateCompressor(100, KisSignalCompressor::FIRST_INACTIVE) { } KisCoordinatesConverter *coordinatesConverter; QPointerview; KisAbstractCanvasWidget *canvasWidget = 0; KoShapeManager shapeManager; KisSelectedShapesProxy selectedShapesProxy; bool currentCanvasIsOpenGL; int openGLFilterMode; KisToolProxy toolProxy; KisPrescaledProjectionSP prescaledProjection; bool vastScrolling; KisSignalCompressor canvasUpdateCompressor; QRect savedUpdateRect; QBitArray channelFlags; KisProofingConfigurationSP proofingConfig; bool softProofing = false; bool gamutCheck = false; bool proofingConfigUpdated = false; KisPopupPalette *popupPalette = 0; KisDisplayColorConverter displayColorConverter; KisCanvasUpdatesCompressor projectionUpdatesCompressor; KisAnimationPlayer *animationPlayer; KisAnimationFrameCacheSP frameCache; bool lodAllowedInImage = false; bool bootstrapLodBlocked; QPointer currentlyActiveShapeManager; KisInputActionGroupsMask inputActionGroupsMask = AllActionGroup; KisSignalCompressor frameRenderStartCompressor; KisSignalCompressor regionOfInterestUpdateCompressor; QRect regionOfInterest; qreal regionOfInterestMargin = 0.25; QRect renderingLimit; int isBatchUpdateActive = 0; bool effectiveLodAllowedInImage() { return lodAllowedInImage && !bootstrapLodBlocked; } void setActiveShapeManager(KoShapeManager *shapeManager); }; namespace { KoShapeManager* fetchShapeManagerFromNode(KisNodeSP node) { KoShapeManager *shapeManager = 0; KisSelectionSP selection; if (KisLayer *layer = dynamic_cast(node.data())) { KisShapeLayer *shapeLayer = dynamic_cast(layer); if (shapeLayer) { shapeManager = shapeLayer->shapeManager(); } } else if (KisSelectionMask *mask = dynamic_cast(node.data())) { selection = mask->selection(); } if (!shapeManager && selection && selection->hasShapeSelection()) { KisShapeSelection *shapeSelection = dynamic_cast(selection->shapeSelection()); KIS_ASSERT_RECOVER_RETURN_VALUE(shapeSelection, 0); shapeManager = shapeSelection->shapeManager(); } return shapeManager; } } KisCanvas2::KisCanvas2(KisCoordinatesConverter *coordConverter, KoCanvasResourceProvider *resourceManager, KisMainWindow *mainWindow, KisView *view, KoShapeControllerBase *sc) : KoCanvasBase(sc, resourceManager) , m_d(new KisCanvas2Private(this, coordConverter, view, resourceManager)) { /** * While loading LoD should be blocked. Only when GUI has finished * loading and zoom level settled down, LoD is given a green * light. */ m_d->bootstrapLodBlocked = true; connect(mainWindow, SIGNAL(guiLoadingFinished()), SLOT(bootstrapFinished())); connect(mainWindow, SIGNAL(screenChanged()), SLOT(slotConfigChanged())); KisImageConfig config(false); m_d->canvasUpdateCompressor.setDelay(1000 / config.fpsLimit()); m_d->canvasUpdateCompressor.setMode(KisSignalCompressor::FIRST_ACTIVE); m_d->frameRenderStartCompressor.setDelay(1000 / config.fpsLimit()); m_d->frameRenderStartCompressor.setMode(KisSignalCompressor::FIRST_ACTIVE); snapGuide()->overrideSnapStrategy(KoSnapGuide::PixelSnapping, new KisSnapPixelStrategy()); } void KisCanvas2::setup() { // a bit of duplication from slotConfigChanged() KisConfig cfg(true); m_d->vastScrolling = cfg.vastScrolling(); m_d->lodAllowedInImage = cfg.levelOfDetailEnabled(); m_d->regionOfInterestMargin = KisImageConfig(true).animationCacheRegionOfInterestMargin(); createCanvas(cfg.useOpenGL()); setLodAllowedInCanvas(m_d->lodAllowedInImage); m_d->animationPlayer = new KisAnimationPlayer(this); connect(m_d->view->canvasController()->proxyObject, SIGNAL(moveDocumentOffset(QPoint)), SLOT(documentOffsetMoved(QPoint))); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); /** * We switch the shape manager every time vector layer or * shape selection is activated. Flake does not expect this * and connects all the signals of the global shape manager * to the clients in the constructor. To workaround this we * forward the signals of local shape managers stored in the * vector layers to the signals of global shape manager. So the * sequence of signal deliveries is the following: * * shapeLayer.m_d.canvas.m_shapeManager.selection() -> * shapeLayer -> * shapeController -> * globalShapeManager.selection() */ KisShapeController *kritaShapeController = static_cast(shapeController()->documentBase()); connect(kritaShapeController, SIGNAL(selectionChanged()), this, SLOT(slotSelectionChanged())); connect(kritaShapeController, SIGNAL(selectionContentChanged()), selectedShapesProxy(), SIGNAL(selectionContentChanged())); connect(kritaShapeController, SIGNAL(currentLayerChanged(const KoShapeLayer*)), selectedShapesProxy(), SIGNAL(currentLayerChanged(const KoShapeLayer*))); connect(&m_d->canvasUpdateCompressor, SIGNAL(timeout()), SLOT(slotDoCanvasUpdate())); connect(this, SIGNAL(sigCanvasCacheUpdated()), &m_d->frameRenderStartCompressor, SLOT(start())); connect(&m_d->frameRenderStartCompressor, SIGNAL(timeout()), SLOT(updateCanvasProjection())); connect(this, SIGNAL(sigContinueResizeImage(qint32,qint32)), SLOT(finishResizingImage(qint32,qint32))); connect(&m_d->regionOfInterestUpdateCompressor, SIGNAL(timeout()), SLOT(slotUpdateRegionOfInterest())); connect(m_d->view->document(), SIGNAL(sigReferenceImagesChanged()), this, SLOT(slotReferenceImagesChanged())); initializeFpsDecoration(); } void KisCanvas2::initializeFpsDecoration() { KisConfig cfg(true); const bool shouldShowDebugOverlay = (canvasIsOpenGL() && cfg.enableOpenGLFramerateLogging()) || cfg.enableBrushSpeedLogging(); if (shouldShowDebugOverlay && !decoration(KisFpsDecoration::idTag)) { addDecoration(new KisFpsDecoration(imageView())); if (cfg.enableBrushSpeedLogging()) { connect(KisStrokeSpeedMonitor::instance(), SIGNAL(sigStatsUpdated()), this, SLOT(updateCanvas())); } } else if (!shouldShowDebugOverlay && decoration(KisFpsDecoration::idTag)) { m_d->canvasWidget->removeDecoration(KisFpsDecoration::idTag); disconnect(KisStrokeSpeedMonitor::instance(), SIGNAL(sigStatsUpdated()), this, SLOT(updateCanvas())); } } KisCanvas2::~KisCanvas2() { if (m_d->animationPlayer->isPlaying()) { m_d->animationPlayer->forcedStopOnExit(); } delete m_d; } void KisCanvas2::setCanvasWidget(KisAbstractCanvasWidget *widget) { if (m_d->popupPalette) { m_d->popupPalette->setParent(widget->widget()); } if (m_d->canvasWidget != 0) { widget->setDecorations(m_d->canvasWidget->decorations()); // Redundant check for the constructor case, see below if(viewManager() != 0) viewManager()->inputManager()->removeTrackedCanvas(this); } m_d->canvasWidget = widget; // Either tmp was null or we are being called by KisCanvas2 constructor that is called by KisView // constructor, so the view manager still doesn't exists. if(m_d->canvasWidget != 0 && viewManager() != 0) viewManager()->inputManager()->addTrackedCanvas(this); if (!m_d->canvasWidget->decoration(INFINITY_DECORATION_ID)) { KisInfinityManager *manager = new KisInfinityManager(m_d->view, this); manager->setVisible(true); m_d->canvasWidget->addDecoration(manager); } widget->widget()->setAutoFillBackground(false); widget->widget()->setAttribute(Qt::WA_OpaquePaintEvent); widget->widget()->setMouseTracking(true); widget->widget()->setAcceptDrops(true); KoCanvasControllerWidget *controller = dynamic_cast(canvasController()); if (controller && controller->canvas() == this) { controller->changeCanvasWidget(widget->widget()); } } bool KisCanvas2::canvasIsOpenGL() const { return m_d->currentCanvasIsOpenGL; } KisOpenGL::FilterMode KisCanvas2::openGLFilterMode() const { return KisOpenGL::FilterMode(m_d->openGLFilterMode); } void KisCanvas2::gridSize(QPointF *offset, QSizeF *spacing) const { QTransform transform = coordinatesConverter()->imageToDocumentTransform(); const QPoint intSpacing = m_d->view->document()->gridConfig().spacing(); const QPoint intOffset = m_d->view->document()->gridConfig().offset(); QPointF size = transform.map(QPointF(intSpacing)); spacing->rwidth() = size.x(); spacing->rheight() = size.y(); *offset = transform.map(QPointF(intOffset)); } bool KisCanvas2::snapToGrid() const { return m_d->view->document()->gridConfig().snapToGrid(); } qreal KisCanvas2::rotationAngle() const { return m_d->coordinatesConverter->rotationAngle(); } bool KisCanvas2::xAxisMirrored() const { return m_d->coordinatesConverter->xAxisMirrored(); } bool KisCanvas2::yAxisMirrored() const { return m_d->coordinatesConverter->yAxisMirrored(); } void KisCanvas2::channelSelectionChanged() { KisImageSP image = this->image(); m_d->channelFlags = image->rootLayer()->channelFlags(); m_d->view->viewManager()->blockUntilOperationsFinishedForced(image); image->barrierLock(); m_d->canvasWidget->channelSelectionChanged(m_d->channelFlags); startUpdateInPatches(image->bounds()); image->unlock(); } void KisCanvas2::addCommand(KUndo2Command *command) { // This method exists to support flake-related operations m_d->view->document()->addCommand(command); } void KisCanvas2::KisCanvas2Private::setActiveShapeManager(KoShapeManager *shapeManager) { if (shapeManager != currentlyActiveShapeManager) { currentlyActiveShapeManager = shapeManager; selectedShapesProxy.setShapeManager(shapeManager); } } KoShapeManager* KisCanvas2::shapeManager() const { KoShapeManager *localShapeManager = this->localShapeManager(); // sanity check for consistency of the local shape manager KIS_SAFE_ASSERT_RECOVER (localShapeManager == m_d->currentlyActiveShapeManager) { localShapeManager = globalShapeManager(); } return localShapeManager ? localShapeManager : globalShapeManager(); } KoSelectedShapesProxy* KisCanvas2::selectedShapesProxy() const { return &m_d->selectedShapesProxy; } KoShapeManager* KisCanvas2::globalShapeManager() const { return &m_d->shapeManager; } KoShapeManager *KisCanvas2::localShapeManager() const { KisNodeSP node = m_d->view->currentNode(); KoShapeManager *localShapeManager = fetchShapeManagerFromNode(node); if (localShapeManager != m_d->currentlyActiveShapeManager) { m_d->setActiveShapeManager(localShapeManager); } return localShapeManager; } void KisCanvas2::updateInputMethodInfo() { // TODO call (the protected) QWidget::updateMicroFocus() on the proper canvas widget... } const KisCoordinatesConverter* KisCanvas2::coordinatesConverter() const { return m_d->coordinatesConverter; } KoViewConverter* KisCanvas2::viewConverter() const { return m_d->coordinatesConverter; } KisInputManager* KisCanvas2::globalInputManager() const { return m_d->view->globalInputManager(); } KisInputActionGroupsMask KisCanvas2::inputActionGroupsMask() const { return m_d->inputActionGroupsMask; } void KisCanvas2::setInputActionGroupsMask(KisInputActionGroupsMask mask) { m_d->inputActionGroupsMask = mask; } QWidget* KisCanvas2::canvasWidget() { return m_d->canvasWidget->widget(); } const QWidget* KisCanvas2::canvasWidget() const { return m_d->canvasWidget->widget(); } KoUnit KisCanvas2::unit() const { KoUnit unit(KoUnit::Pixel); KisImageWSP image = m_d->view->image(); if (image) { if (!qFuzzyCompare(image->xRes(), image->yRes())) { warnKrita << "WARNING: resolution of the image is anisotropic" << ppVar(image->xRes()) << ppVar(image->yRes()); } const qreal resolution = image->xRes(); unit.setFactor(resolution); } return unit; } KoToolProxy * KisCanvas2::toolProxy() const { return &m_d->toolProxy; } void KisCanvas2::createQPainterCanvas() { m_d->currentCanvasIsOpenGL = false; KisQPainterCanvas * canvasWidget = new KisQPainterCanvas(this, m_d->coordinatesConverter, m_d->view); m_d->prescaledProjection = new KisPrescaledProjection(); m_d->prescaledProjection->setCoordinatesConverter(m_d->coordinatesConverter); m_d->prescaledProjection->setMonitorProfile(m_d->displayColorConverter.monitorProfile(), m_d->displayColorConverter.renderingIntent(), m_d->displayColorConverter.conversionFlags()); m_d->prescaledProjection->setDisplayFilter(m_d->displayColorConverter.displayFilter()); canvasWidget->setPrescaledProjection(m_d->prescaledProjection); setCanvasWidget(canvasWidget); } void KisCanvas2::createOpenGLCanvas() { KisConfig cfg(true); m_d->openGLFilterMode = cfg.openGLFilteringMode(); m_d->currentCanvasIsOpenGL = true; KisOpenGLCanvas2 *canvasWidget = new KisOpenGLCanvas2(this, m_d->coordinatesConverter, 0, m_d->view->image(), &m_d->displayColorConverter); m_d->frameCache = KisAnimationFrameCache::getFrameCache(canvasWidget->openGLImageTextures()); setCanvasWidget(canvasWidget); } void KisCanvas2::createCanvas(bool useOpenGL) { // deinitialize previous canvas structures m_d->prescaledProjection = 0; m_d->frameCache = 0; KisConfig cfg(true); QDesktopWidget dw; const KoColorProfile *profile = cfg.displayProfile(dw.screenNumber(imageView())); m_d->displayColorConverter.notifyOpenGLCanvasIsActive(useOpenGL && KisOpenGL::hasOpenGL()); m_d->displayColorConverter.setMonitorProfile(profile); if (useOpenGL && !KisOpenGL::hasOpenGL()) { warnKrita << "Tried to create OpenGL widget when system doesn't have OpenGL\n"; useOpenGL = false; } m_d->displayColorConverter.notifyOpenGLCanvasIsActive(useOpenGL); if (useOpenGL) { createOpenGLCanvas(); if (cfg.canvasState() == "OPENGL_FAILED") { // Creating the opengl canvas failed, fall back warnKrita << "OpenGL Canvas initialization returned OPENGL_FAILED. Falling back to QPainter."; m_d->displayColorConverter.notifyOpenGLCanvasIsActive(false); createQPainterCanvas(); } } else { createQPainterCanvas(); } if (m_d->popupPalette) { m_d->popupPalette->setParent(m_d->canvasWidget->widget()); } } void KisCanvas2::initializeImage() { KisImageSP image = m_d->view->image(); m_d->displayColorConverter.setImageColorSpace(image->colorSpace()); m_d->coordinatesConverter->setImage(image); m_d->toolProxy.initializeImage(image); connect(image, SIGNAL(sigImageUpdated(QRect)), SLOT(startUpdateCanvasProjection(QRect)), Qt::DirectConnection); connect(image->signalRouter(), SIGNAL(sigNotifyBatchUpdateStarted()), SLOT(slotBeginUpdatesBatch()), Qt::DirectConnection); connect(image->signalRouter(), SIGNAL(sigNotifyBatchUpdateEnded()), SLOT(slotEndUpdatesBatch()), Qt::DirectConnection); connect(image->signalRouter(), SIGNAL(sigRequestLodPlanesSyncBlocked(bool)), SLOT(slotSetLodUpdatesBlocked(bool)), Qt::DirectConnection); connect(image, SIGNAL(sigProofingConfigChanged()), SLOT(slotChangeProofingConfig())); connect(image, SIGNAL(sigSizeChanged(QPointF,QPointF)), SLOT(startResizingImage()), Qt::DirectConnection); connect(image->undoAdapter(), SIGNAL(selectionChanged()), SLOT(slotTrySwitchShapeManager())); connect(image, SIGNAL(sigColorSpaceChanged(const KoColorSpace*)), SLOT(slotImageColorSpaceChanged())); connect(image, SIGNAL(sigProfileChanged(const KoColorProfile*)), SLOT(slotImageColorSpaceChanged())); connectCurrentCanvas(); } void KisCanvas2::connectCurrentCanvas() { KisImageWSP image = m_d->view->image(); if (!m_d->currentCanvasIsOpenGL) { Q_ASSERT(m_d->prescaledProjection); m_d->prescaledProjection->setImage(image); } startResizingImage(); setLodAllowedInCanvas(m_d->lodAllowedInImage); emit sigCanvasEngineChanged(); } void KisCanvas2::resetCanvas(bool useOpenGL) { // we cannot reset the canvas before it's created, but this method might be called, // for instance when setting the monitor profile. if (!m_d->canvasWidget) { return; } KisConfig cfg(true); bool needReset = (m_d->currentCanvasIsOpenGL != useOpenGL) || (m_d->currentCanvasIsOpenGL && m_d->openGLFilterMode != cfg.openGLFilteringMode()); if (needReset) { createCanvas(useOpenGL); connectCurrentCanvas(); notifyZoomChanged(); } updateCanvasWidgetImpl(); } void KisCanvas2::startUpdateInPatches(const QRect &imageRect) { /** - * We don't do patched loading for openGL canvas, becasue it loads - * the tiles, which are bascially "patches". Therefore, big chunks + * We don't do patched loading for openGL canvas, because it loads + * the tiles, which are basically "patches". Therefore, big chunks * of memory are never allocated. */ if (m_d->currentCanvasIsOpenGL) { startUpdateCanvasProjection(imageRect); } else { KisImageConfig imageConfig(true); int patchWidth = imageConfig.updatePatchWidth(); int patchHeight = imageConfig.updatePatchHeight(); for (int y = 0; y < imageRect.height(); y += patchHeight) { for (int x = 0; x < imageRect.width(); x += patchWidth) { QRect patchRect(x, y, patchWidth, patchHeight); startUpdateCanvasProjection(patchRect); } } } } void KisCanvas2::setDisplayFilter(QSharedPointer displayFilter) { m_d->displayColorConverter.setDisplayFilter(displayFilter); KisImageSP image = this->image(); m_d->view->viewManager()->blockUntilOperationsFinishedForced(image); image->barrierLock(); m_d->canvasWidget->setDisplayFilter(displayFilter); image->unlock(); } QSharedPointer KisCanvas2::displayFilter() const { return m_d->displayColorConverter.displayFilter(); } void KisCanvas2::slotImageColorSpaceChanged() { KisImageSP image = this->image(); m_d->view->viewManager()->blockUntilOperationsFinishedForced(image); m_d->displayColorConverter.setImageColorSpace(image->colorSpace()); image->barrierLock(); m_d->canvasWidget->notifyImageColorSpaceChanged(image->colorSpace()); image->unlock(); } KisDisplayColorConverter* KisCanvas2::displayColorConverter() const { return &m_d->displayColorConverter; } KisExposureGammaCorrectionInterface* KisCanvas2::exposureGammaCorrectionInterface() const { QSharedPointer displayFilter = m_d->displayColorConverter.displayFilter(); return displayFilter ? displayFilter->correctionInterface() : KisDumbExposureGammaCorrectionInterface::instance(); } void KisCanvas2::setProofingOptions(bool softProof, bool gamutCheck) { m_d->proofingConfig = this->image()->proofingConfiguration(); if (!m_d->proofingConfig) { KisImageConfig cfg(false); m_d->proofingConfig = cfg.defaultProofingconfiguration(); } KoColorConversionTransformation::ConversionFlags conversionFlags = m_d->proofingConfig->conversionFlags; if (this->image()->colorSpace()->colorDepthId().id().contains("U")) { conversionFlags.setFlag(KoColorConversionTransformation::SoftProofing, softProof); if (softProof) { conversionFlags.setFlag(KoColorConversionTransformation::GamutCheck, gamutCheck); } } m_d->proofingConfig->conversionFlags = conversionFlags; m_d->proofingConfigUpdated = true; startUpdateInPatches(this->image()->bounds()); } void KisCanvas2::slotSoftProofing(bool softProofing) { m_d->softProofing = softProofing; setProofingOptions(m_d->softProofing, m_d->gamutCheck); } void KisCanvas2::slotGamutCheck(bool gamutCheck) { m_d->gamutCheck = gamutCheck; setProofingOptions(m_d->softProofing, m_d->gamutCheck); } void KisCanvas2::slotChangeProofingConfig() { setProofingOptions(m_d->softProofing, m_d->gamutCheck); } void KisCanvas2::setProofingConfigUpdated(bool updated) { m_d->proofingConfigUpdated = updated; } bool KisCanvas2::proofingConfigUpdated() { return m_d->proofingConfigUpdated; } KisProofingConfigurationSP KisCanvas2::proofingConfiguration() const { if (!m_d->proofingConfig) { m_d->proofingConfig = this->image()->proofingConfiguration(); if (!m_d->proofingConfig) { m_d->proofingConfig = KisImageConfig(true).defaultProofingconfiguration(); } } return m_d->proofingConfig; } void KisCanvas2::startResizingImage() { KisImageWSP image = this->image(); qint32 w = image->width(); qint32 h = image->height(); emit sigContinueResizeImage(w, h); QRect imageBounds(0, 0, w, h); startUpdateInPatches(imageBounds); } void KisCanvas2::finishResizingImage(qint32 w, qint32 h) { m_d->canvasWidget->finishResizingImage(w, h); } void KisCanvas2::startUpdateCanvasProjection(const QRect & rc) { KisUpdateInfoSP info = m_d->canvasWidget->startUpdateCanvasProjection(rc, m_d->channelFlags); if (m_d->projectionUpdatesCompressor.putUpdateInfo(info)) { emit sigCanvasCacheUpdated(); } } void KisCanvas2::updateCanvasProjection() { auto tryIssueCanvasUpdates = [this](const QRect &vRect) { if (!m_d->isBatchUpdateActive) { // TODO: Implement info->dirtyViewportRect() for KisOpenGLCanvas2 to avoid updating whole canvas if (m_d->currentCanvasIsOpenGL) { m_d->savedUpdateRect = QRect(); // we already had a compression in frameRenderStartCompressor, so force the update directly slotDoCanvasUpdate(); } else if (/* !m_d->currentCanvasIsOpenGL && */ !vRect.isEmpty()) { m_d->savedUpdateRect = m_d->coordinatesConverter->viewportToWidget(vRect).toAlignedRect(); // we already had a compression in frameRenderStartCompressor, so force the update directly slotDoCanvasUpdate(); } } }; auto uploadData = [this, tryIssueCanvasUpdates](const QVector &infoObjects) { QVector viewportRects = m_d->canvasWidget->updateCanvasProjection(infoObjects); const QRect vRect = std::accumulate(viewportRects.constBegin(), viewportRects.constEnd(), QRect(), std::bit_or()); tryIssueCanvasUpdates(vRect); }; bool shouldExplicitlyIssueUpdates = false; QVector infoObjects; KisUpdateInfoList originalInfoObjects; m_d->projectionUpdatesCompressor.takeUpdateInfo(originalInfoObjects); for (auto it = originalInfoObjects.constBegin(); it != originalInfoObjects.constEnd(); ++it) { KisUpdateInfoSP info = *it; const KisMarkerUpdateInfo *batchInfo = dynamic_cast(info.data()); if (batchInfo) { if (!infoObjects.isEmpty()) { uploadData(infoObjects); infoObjects.clear(); } if (batchInfo->type() == KisMarkerUpdateInfo::StartBatch) { m_d->isBatchUpdateActive++; } else if (batchInfo->type() == KisMarkerUpdateInfo::EndBatch) { m_d->isBatchUpdateActive--; KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->isBatchUpdateActive >= 0); if (m_d->isBatchUpdateActive == 0) { shouldExplicitlyIssueUpdates = true; } } else if (batchInfo->type() == KisMarkerUpdateInfo::BlockLodUpdates) { m_d->canvasWidget->setLodResetInProgress(true); } else if (batchInfo->type() == KisMarkerUpdateInfo::UnblockLodUpdates) { m_d->canvasWidget->setLodResetInProgress(false); shouldExplicitlyIssueUpdates = true; } } else { infoObjects << info; } } if (!infoObjects.isEmpty()) { uploadData(infoObjects); } else if (shouldExplicitlyIssueUpdates) { tryIssueCanvasUpdates(m_d->coordinatesConverter->imageRectInImagePixels()); } } void KisCanvas2::slotBeginUpdatesBatch() { KisUpdateInfoSP info = new KisMarkerUpdateInfo(KisMarkerUpdateInfo::StartBatch, m_d->coordinatesConverter->imageRectInImagePixels()); m_d->projectionUpdatesCompressor.putUpdateInfo(info); emit sigCanvasCacheUpdated(); } void KisCanvas2::slotEndUpdatesBatch() { KisUpdateInfoSP info = new KisMarkerUpdateInfo(KisMarkerUpdateInfo::EndBatch, m_d->coordinatesConverter->imageRectInImagePixels()); m_d->projectionUpdatesCompressor.putUpdateInfo(info); emit sigCanvasCacheUpdated(); } void KisCanvas2::slotSetLodUpdatesBlocked(bool value) { KisUpdateInfoSP info = new KisMarkerUpdateInfo(value ? KisMarkerUpdateInfo::BlockLodUpdates : KisMarkerUpdateInfo::UnblockLodUpdates, m_d->coordinatesConverter->imageRectInImagePixels()); m_d->projectionUpdatesCompressor.putUpdateInfo(info); emit sigCanvasCacheUpdated(); } void KisCanvas2::slotDoCanvasUpdate() { /** * WARNING: in isBusy() we access openGL functions without making the painting * context current. We hope that currently active context will be Qt's one, * which is shared with our own. */ if (m_d->canvasWidget->isBusy()) { // just restarting the timer updateCanvasWidgetImpl(m_d->savedUpdateRect); return; } if (m_d->savedUpdateRect.isEmpty()) { m_d->canvasWidget->widget()->update(); emit updateCanvasRequested(m_d->canvasWidget->widget()->rect()); } else { emit updateCanvasRequested(m_d->savedUpdateRect); m_d->canvasWidget->widget()->update(m_d->savedUpdateRect); } m_d->savedUpdateRect = QRect(); } void KisCanvas2::updateCanvasWidgetImpl(const QRect &rc) { if (!m_d->canvasUpdateCompressor.isActive() || !m_d->savedUpdateRect.isEmpty()) { m_d->savedUpdateRect |= rc; } m_d->canvasUpdateCompressor.start(); } void KisCanvas2::updateCanvas() { updateCanvasWidgetImpl(); } void KisCanvas2::updateCanvas(const QRectF& documentRect) { if (m_d->currentCanvasIsOpenGL && m_d->canvasWidget->decorations().size() > 0) { updateCanvasWidgetImpl(); } else { // updateCanvas is called from tools, never from the projection // updates, so no need to prescale! QRect widgetRect = m_d->coordinatesConverter->documentToWidget(documentRect).toAlignedRect(); widgetRect.adjust(-2, -2, 2, 2); if (!widgetRect.isEmpty()) { updateCanvasWidgetImpl(widgetRect); } } } void KisCanvas2::disconnectCanvasObserver(QObject *object) { KoCanvasBase::disconnectCanvasObserver(object); m_d->view->disconnect(object); } void KisCanvas2::notifyZoomChanged() { if (!m_d->currentCanvasIsOpenGL) { Q_ASSERT(m_d->prescaledProjection); m_d->prescaledProjection->notifyZoomChanged(); } notifyLevelOfDetailChange(); updateCanvas(); // update the canvas, because that isn't done when zooming using KoZoomAction m_d->regionOfInterestUpdateCompressor.start(); } QRect KisCanvas2::regionOfInterest() const { return m_d->regionOfInterest; } void KisCanvas2::slotUpdateRegionOfInterest() { const QRect oldRegionOfInterest = m_d->regionOfInterest; const qreal ratio = m_d->regionOfInterestMargin; const QRect proposedRoi = KisAlgebra2D::blowRect(m_d->coordinatesConverter->widgetRectInImagePixels(), ratio).toAlignedRect(); const QRect imageRect = m_d->coordinatesConverter->imageRectInImagePixels(); m_d->regionOfInterest = proposedRoi & imageRect; if (m_d->regionOfInterest != oldRegionOfInterest) { emit sigRegionOfInterestChanged(m_d->regionOfInterest); } } void KisCanvas2::slotReferenceImagesChanged() { canvasController()->resetScrollBars(); } void KisCanvas2::setRenderingLimit(const QRect &rc) { m_d->renderingLimit = rc; } QRect KisCanvas2::renderingLimit() const { return m_d->renderingLimit; } void KisCanvas2::slotTrySwitchShapeManager() { KisNodeSP node = m_d->view->currentNode(); QPointer newManager; newManager = fetchShapeManagerFromNode(node); m_d->setActiveShapeManager(newManager); } void KisCanvas2::notifyLevelOfDetailChange() { if (!m_d->effectiveLodAllowedInImage()) return; const qreal effectiveZoom = m_d->coordinatesConverter->effectiveZoom(); KisConfig cfg(true); const int maxLod = cfg.numMipmapLevels(); const int lod = KisLodTransform::scaleToLod(effectiveZoom, maxLod); if (m_d->effectiveLodAllowedInImage()) { KisImageSP image = this->image(); image->setDesiredLevelOfDetail(lod); } } const KoColorProfile * KisCanvas2::monitorProfile() { return m_d->displayColorConverter.monitorProfile(); } KisViewManager* KisCanvas2::viewManager() const { if (m_d->view) { return m_d->view->viewManager(); } return 0; } QPointerKisCanvas2::imageView() const { return m_d->view; } KisImageWSP KisCanvas2::image() const { return m_d->view->image(); } KisImageWSP KisCanvas2::currentImage() const { return m_d->view->image(); } void KisCanvas2::documentOffsetMoved(const QPoint &documentOffset) { QPointF offsetBefore = m_d->coordinatesConverter->imageRectInViewportPixels().topLeft(); // The given offset is in widget logical pixels. In order to prevent fuzzy // canvas rendering at 100% pixel-perfect zoom level when devicePixelRatio // is not integral, we adjusts the offset to map to whole device pixels. // // FIXME: This is a temporary hack for fixing the canvas under fractional // DPI scaling before a new coordinate system is introduced. QPointF offsetAdjusted = m_d->coordinatesConverter->snapToDevicePixel(documentOffset); m_d->coordinatesConverter->setDocumentOffset(offsetAdjusted); QPointF offsetAfter = m_d->coordinatesConverter->imageRectInViewportPixels().topLeft(); QPointF moveOffset = offsetAfter - offsetBefore; if (!m_d->currentCanvasIsOpenGL) m_d->prescaledProjection->viewportMoved(moveOffset); emit documentOffsetUpdateFinished(); updateCanvas(); m_d->regionOfInterestUpdateCompressor.start(); } void KisCanvas2::slotConfigChanged() { KisConfig cfg(true); m_d->vastScrolling = cfg.vastScrolling(); m_d->regionOfInterestMargin = KisImageConfig(true).animationCacheRegionOfInterestMargin(); resetCanvas(cfg.useOpenGL()); // HACK: Sometimes screenNumber(this->canvasWidget()) is not able to get the // proper screenNumber when moving the window across screens. Using // the coordinates should be able to work around this. // FIXME: We should change to associate the display profiles with the screen // model and serial number instead. See https://bugs.kde.org/show_bug.cgi?id=407498 int canvasScreenNumber = QApplication::desktop()->screenNumber(this->canvasWidget()); if (canvasScreenNumber != -1) { setDisplayProfile(cfg.displayProfile(canvasScreenNumber)); } else { warnUI << "Failed to get screenNumber for updating display profile."; } initializeFpsDecoration(); } void KisCanvas2::refetchDataFromImage() { KisImageSP image = this->image(); KisImageBarrierLocker l(image); startUpdateInPatches(image->bounds()); } void KisCanvas2::setDisplayProfile(const KoColorProfile *monitorProfile) { if (m_d->displayColorConverter.monitorProfile() == monitorProfile) return; m_d->displayColorConverter.setMonitorProfile(monitorProfile); { KisImageSP image = this->image(); KisImageBarrierLocker l(image); m_d->canvasWidget->setDisplayColorConverter(&m_d->displayColorConverter); } refetchDataFromImage(); } void KisCanvas2::addDecoration(KisCanvasDecorationSP deco) { m_d->canvasWidget->addDecoration(deco); } KisCanvasDecorationSP KisCanvas2::decoration(const QString& id) const { return m_d->canvasWidget->decoration(id); } QPoint KisCanvas2::documentOrigin() const { /** * In Krita we don't use document origin anymore. * All the centering when needed (vastScrolling < 0.5) is done * automatically by the KisCoordinatesConverter. */ return QPoint(); } QPoint KisCanvas2::documentOffset() const { return m_d->coordinatesConverter->documentOffset(); } void KisCanvas2::setFavoriteResourceManager(KisFavoriteResourceManager* favoriteResourceManager) { m_d->popupPalette = new KisPopupPalette(viewManager(), m_d->coordinatesConverter, favoriteResourceManager, displayColorConverter()->displayRendererInterface(), m_d->view->resourceProvider(), m_d->canvasWidget->widget()); connect(m_d->popupPalette, SIGNAL(zoomLevelChanged(int)), this, SLOT(slotPopupPaletteRequestedZoomChange(int))); connect(m_d->popupPalette, SIGNAL(sigUpdateCanvas()), this, SLOT(updateCanvas())); connect(m_d->view->mainWindow(), SIGNAL(themeChanged()), m_d->popupPalette, SLOT(slotUpdateIcons())); m_d->popupPalette->showPopupPalette(false); } void KisCanvas2::slotPopupPaletteRequestedZoomChange(int zoom ) { m_d->view->viewManager()->zoomController()->setZoom(KoZoomMode::ZOOM_CONSTANT, (qreal)(zoom/100.0)); // 1.0 is 100% zoom notifyZoomChanged(); } void KisCanvas2::setCursor(const QCursor &cursor) { canvasWidget()->setCursor(cursor); } KisAnimationFrameCacheSP KisCanvas2::frameCache() const { return m_d->frameCache; } KisAnimationPlayer *KisCanvas2::animationPlayer() const { return m_d->animationPlayer; } void KisCanvas2::slotSelectionChanged() { KisShapeLayer* shapeLayer = dynamic_cast(viewManager()->activeLayer().data()); if (!shapeLayer) { return; } m_d->shapeManager.selection()->deselectAll(); Q_FOREACH (KoShape* shape, shapeLayer->shapeManager()->selection()->selectedShapes()) { m_d->shapeManager.selection()->select(shape); } } bool KisCanvas2::isPopupPaletteVisible() const { if (!m_d->popupPalette) { return false; } return m_d->popupPalette->isVisible(); } void KisCanvas2::setWrapAroundViewingMode(bool value) { KisCanvasDecorationSP infinityDecoration = m_d->canvasWidget->decoration(INFINITY_DECORATION_ID); if (infinityDecoration) { infinityDecoration->setVisible(!value); } m_d->canvasWidget->setWrapAroundViewingMode(value); } bool KisCanvas2::wrapAroundViewingMode() const { KisCanvasDecorationSP infinityDecoration = m_d->canvasWidget->decoration(INFINITY_DECORATION_ID); if (infinityDecoration) { return !(infinityDecoration->visible()); } return false; } void KisCanvas2::bootstrapFinished() { if (!m_d->bootstrapLodBlocked) return; m_d->bootstrapLodBlocked = false; setLodAllowedInCanvas(m_d->lodAllowedInImage); } void KisCanvas2::setLodAllowedInCanvas(bool value) { if (!KisOpenGL::supportsLoD()) { qWarning() << "WARNING: Level of Detail functionality is available only with openGL + GLSL 1.3 support"; } m_d->lodAllowedInImage = value && m_d->currentCanvasIsOpenGL && KisOpenGL::supportsLoD() && (m_d->openGLFilterMode == KisOpenGL::TrilinearFilterMode || m_d->openGLFilterMode == KisOpenGL::HighQualityFiltering); KisImageSP image = this->image(); if (m_d->effectiveLodAllowedInImage() != !image->levelOfDetailBlocked()) { image->setLevelOfDetailBlocked(!m_d->effectiveLodAllowedInImage()); } notifyLevelOfDetailChange(); KisConfig cfg(false); cfg.setLevelOfDetailEnabled(m_d->lodAllowedInImage); - - KisUsageLogger::log(QString("Instant Preview Setting: %1").arg(m_d->lodAllowedInImage)); } bool KisCanvas2::lodAllowedInCanvas() const { return m_d->lodAllowedInImage; } void KisCanvas2::slotShowPopupPalette(const QPoint &p) { if (!m_d->popupPalette) { return; } m_d->popupPalette->showPopupPalette(p); } KisPaintingAssistantsDecorationSP KisCanvas2::paintingAssistantsDecoration() const { KisCanvasDecorationSP deco = decoration("paintingAssistantsDecoration"); return qobject_cast(deco.data()); } KisReferenceImagesDecorationSP KisCanvas2::referenceImagesDecoration() const { KisCanvasDecorationSP deco = decoration("referenceImagesDecoration"); return qobject_cast(deco.data()); } diff --git a/libs/ui/canvas/kis_grid_config.h b/libs/ui/canvas/kis_grid_config.h index 9042fba49a..a052593774 100644 --- a/libs/ui/canvas/kis_grid_config.h +++ b/libs/ui/canvas/kis_grid_config.h @@ -1,257 +1,257 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_GRID_CONFIG_H #define __KIS_GRID_CONFIG_H #include #include #include #include #include "kritaui_export.h" class QDomElement; class QDomDocument; class KRITAUI_EXPORT KisGridConfig : boost::equality_comparable { public: enum LineTypeInternal { LINE_SOLID = 0, LINE_DASHED, LINE_DOTTED }; enum GridType { GRID_RECTANGULAR = 0, GRID_ISOMETRIC }; public: KisGridConfig() : m_showGrid(false), m_snapToGrid(false), m_spacing(20,20), m_offsetAspectLocked(true), m_spacingAspectLocked(true), m_angleLeft(45), m_angleRight(45), m_cellSpacing(30), m_gridType(GRID_RECTANGULAR), m_subdivision(2), m_lineTypeMain(LINE_SOLID), m_lineTypeSubdivision(LINE_DOTTED), m_colorMain(200, 200, 200, 200), m_colorSubdivision(200, 200, 200, 150) { loadStaticData(); } bool operator==(const KisGridConfig &rhs) const { return m_showGrid == rhs.m_showGrid && m_snapToGrid == rhs.m_snapToGrid && m_spacing == rhs.m_spacing && m_offset == rhs.m_offset && m_offsetAspectLocked == rhs.m_offsetAspectLocked && m_spacingAspectLocked == rhs.m_spacingAspectLocked && m_angleRight == rhs.m_angleRight && m_angleLeft == rhs.m_angleLeft && m_gridType == rhs.m_gridType && m_cellSpacing == rhs.m_cellSpacing && m_subdivision == rhs.m_subdivision && m_lineTypeMain == rhs.m_lineTypeMain && m_lineTypeSubdivision == rhs.m_lineTypeSubdivision && m_colorMain == rhs.m_colorMain && m_colorSubdivision == rhs.m_colorSubdivision; } bool showGrid() const { return m_showGrid; } void setShowGrid(bool value) { m_showGrid = value; } bool snapToGrid() const { return m_snapToGrid; } void setSnapToGrid(bool value) { m_snapToGrid = value; } QPoint offset() const { return m_offset; } void setOffset(const QPoint &value) { m_offset = value; } QPoint spacing() const { return m_spacing; } void setSpacing(const QPoint &value) { m_spacing = value; } int subdivision() const { return m_subdivision; } void setSubdivision(int value) { m_subdivision = value; } int angleLeft() const { return m_angleLeft; } void setAngleLeft(int angle) { m_angleLeft = angle; } int angleRight() const { return m_angleRight; } void setAngleRight(int angle) { m_angleRight = angle; } int cellSpacing() const { return m_cellSpacing; } void setCellSpacing(int spacing) { m_cellSpacing = spacing; } GridType gridType() const { return m_gridType; } void setGridType(GridType type) { m_gridType = type; } bool offsetAspectLocked() const { return m_offsetAspectLocked; } void setOffsetAspectLocked(bool value) { m_offsetAspectLocked = value; } bool spacingAspectLocked() const { return m_spacingAspectLocked; } void setSpacingAspectLocked(bool value) { m_spacingAspectLocked = value; } LineTypeInternal lineTypeMain() const { return m_lineTypeMain; } void setLineTypeMain(LineTypeInternal value) { m_lineTypeMain = value; } LineTypeInternal lineTypeSubdivision() const { return m_lineTypeSubdivision; } void setLineTypeSubdivision(LineTypeInternal value) { m_lineTypeSubdivision = value; } QColor colorMain() const { return m_colorMain; } void setColorMain(const QColor &value) { m_colorMain = value; } QColor colorSubdivision() const { return m_colorSubdivision; } void setColorSubdivision(const QColor &value) { m_colorSubdivision = value; } QPen penMain() const { return QPen(m_colorMain, 0, toPenStyle(m_lineTypeMain)); } QPen penSubdivision() const { return QPen(m_colorSubdivision, 0, toPenStyle(m_lineTypeSubdivision)); } void loadStaticData(); void saveStaticData() const; QDomElement saveDynamicDataToXml(QDomDocument& doc, const QString &tag) const; bool loadDynamicDataFromXml(const QDomElement &parent); static const KisGridConfig& defaultGrid(); bool isDefault() const { return *this == defaultGrid(); } - /// Transform the grids using the given \p tranform. Please note that \p transform + /// Transform the grids using the given \p transform. Please note that \p transform /// should be in 'image' coordinate system. /// Used with image-wide transformations. void transform(const QTransform &transform); private: static Qt::PenStyle toPenStyle(LineTypeInternal type) { return type == LINE_SOLID ? Qt::SolidLine : type == LINE_DASHED ? Qt::DashLine : type == LINE_DOTTED ? Qt::DotLine : Qt::DashDotDotLine; } private: // Dynamic data. Stored in KisDocument. bool m_showGrid; bool m_snapToGrid; QPoint m_spacing; bool m_offsetAspectLocked; bool m_spacingAspectLocked; int m_angleLeft; int m_angleRight; int m_cellSpacing; GridType m_gridType; int m_subdivision; QPoint m_offset; // Static data. Stored in the Krita config. LineTypeInternal m_lineTypeMain; LineTypeInternal m_lineTypeSubdivision; QColor m_colorMain; QColor m_colorSubdivision; }; Q_DECLARE_METATYPE(KisGridConfig) #endif /* __KIS_GRID_CONFIG_H */ diff --git a/libs/ui/canvas/kis_guides_config.h b/libs/ui/canvas/kis_guides_config.h index 7aff6e7eeb..3bf5cbc36d 100644 --- a/libs/ui/canvas/kis_guides_config.h +++ b/libs/ui/canvas/kis_guides_config.h @@ -1,136 +1,136 @@ /* This file is part of the KDE project Copyright (C) 2006 Laurent Montel Copyright (C) 2008 Jan Hambrecht Copyright (c) 2015 Dmitry Kazakov This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOGUIDESDATA_H #define KOGUIDESDATA_H #include "kritaui_export.h" #include #include #include #include class QDomElement; class QDomDocument; class QColor; class QPen; class KRITAUI_EXPORT KisGuidesConfig : boost::equality_comparable { public: enum LineTypeInternal { LINE_SOLID = 0, LINE_DASHED, LINE_DOTTED }; public: KisGuidesConfig(); ~KisGuidesConfig(); KisGuidesConfig(const KisGuidesConfig &rhs); KisGuidesConfig& operator=(const KisGuidesConfig &rhs); bool operator==(const KisGuidesConfig &rhs) const; bool hasSamePositionAs(const KisGuidesConfig &rhs) const; /** * @brief Set the positions of the horizontal guide lines * * @param lines a list of positions of the horizontal guide lines */ void setHorizontalGuideLines(const QList &lines); /** * @brief Set the positions of the vertical guide lines * * @param lines a list of positions of the vertical guide lines */ void setVerticalGuideLines(const QList &lines); /** * @brief Add a guide line to the canvas. * * @param orientation the orientation of the guide line * @param position the position in document coordinates of the guide line */ void addGuideLine(Qt::Orientation orientation, qreal position); /** * @brief Display or not guide lines */ bool showGuideLines() const; /** * @param show display or not guide line */ void setShowGuideLines(bool show); bool showGuides() const; void setShowGuides(bool value); bool lockGuides() const; void setLockGuides(bool value); bool snapToGuides() const; void setSnapToGuides(bool value); bool rulersMultiple2() const; void setRulersMultiple2(bool value); KoUnit::Type unitType() const; void setUnitType(KoUnit::Type type); LineTypeInternal guidesLineType() const; void setGuidesLineType(LineTypeInternal value); QColor guidesColor() const; void setGuidesColor(const QColor &value); QPen guidesPen() const; /// Returns the list of horizontal guide lines. const QList& horizontalGuideLines() const; /// Returns the list of vertical guide lines. const QList& verticalGuideLines() const; bool hasGuides() const; void loadStaticData(); void saveStaticData() const; QDomElement saveToXml(QDomDocument& doc, const QString &tag) const; bool loadFromXml(const QDomElement &parent); bool isDefault() const; - /// Transform the guides using the given \p tranform. Please note that \p transform + /// Transform the guides using the given \p transform. Please note that \p transform /// should be in 'document' coordinate system. /// Used with image-wide transformations. void transform(const QTransform &transform); private: class Private; const QScopedPointer d; }; #endif diff --git a/libs/ui/canvas/kis_image_patch.h b/libs/ui/canvas/kis_image_patch.h index 5ddcbc6f3a..76b671f257 100644 --- a/libs/ui/canvas/kis_image_patch.h +++ b/libs/ui/canvas/kis_image_patch.h @@ -1,111 +1,111 @@ /* * 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_PATCH_H_ #define KIS_IMAGE_PATCH_H_ #include #include #include #define BORDER_SIZE(scale) (ceil(0.5/scale)) class KisImagePatch { public: /** * A default constructor initializing invalid patch */ KisImagePatch(); /** * Initializes a new patch with given values. * Be careful, because the constructor does not fill * QImage of the patch, as the patch rect is not known yet * * \see setImage */ KisImagePatch(QRect imageRect, qint32 borderWidth, qreal scaleX, qreal scaleY); /** * Sets the image of the patch * Should be called right after the constructor * to finish initializing the object */ void setImage(QImage image); /** * prescale the patch image. Call after setImage(). * This ensures that we use the QImage smoothscale method, not the QPainter scaling, * which is far inferior. */ void preScale(const QRectF &dstRect); /** * Returns the rect of KisImage covered by the image * of the patch (in KisImage pixels) * * \see m_patchRect */ QRect patchRect(); /** * Draws an m_interestRect of the patch onto @p gc * By the way it fits this rect into @p dstRect * @p renderHints are directly transmitted to QPainter */ void drawMe(QPainter &gc, const QRectF &dstRect, QPainter::RenderHints renderHints); /** * Checks whether the patch can be used for drawing the image */ bool isValid(); private: /** * The scale of the image stored in the patch */ qreal m_scaleX; qreal m_scaleY; /** * The rect of KisImage covered by the image * of the patch (in KisImage pixels) */ QRect m_patchRect; /** * The rect that was requested during creation * of the patch. It equals to patchRect withount * borders - * These borders are introdused for more accurate + * These borders are introduced for more accurate * smooth scaling to reduce border effects * (IN m_image PIXELS, relative to m_image's topLeft); */ QRectF m_interestRect; QImage m_image; bool m_isScaled; }; #endif /* KIS_IMAGE_PATCH_H_ */ diff --git a/libs/ui/canvas/kis_prescaled_projection.cpp b/libs/ui/canvas/kis_prescaled_projection.cpp index a7212b7cf3..b276b4c474 100644 --- a/libs/ui/canvas/kis_prescaled_projection.cpp +++ b/libs/ui/canvas/kis_prescaled_projection.cpp @@ -1,400 +1,400 @@ /* * Copyright (c) 2007, Boudewijn Rempt * Copyright (c) 2008, Cyrille Berger * 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_prescaled_projection.h" #include #include #include #include #include #include #include #include #include #include "kis_config.h" #include "kis_image_config.h" #include "kis_config_notifier.h" #include "kis_image.h" #include "krita_utils.h" #include "kis_coordinates_converter.h" #include "kis_projection_backend.h" #include "kis_image_pyramid.h" #include "kis_display_filter.h" #define ceiledSize(sz) QSize(ceil((sz).width()), ceil((sz).height())) inline void copyQImageBuffer(uchar* dst, const uchar* src , qint32 deltaX, qint32 width) { if (deltaX >= 0) { memcpy(dst + 4 * deltaX, src, 4 *(width - deltaX) * sizeof(uchar)); } else { memcpy(dst, src - 4 * deltaX, 4 *(width + deltaX) * sizeof(uchar)); } } void copyQImage(qint32 deltaX, qint32 deltaY, QImage* dstImage, const QImage& srcImage) { qint32 height = dstImage->height(); qint32 width = dstImage->width(); Q_ASSERT(dstImage->width() == srcImage.width() && dstImage->height() == srcImage.height()); if (deltaY >= 0) { for (int y = 0; y < height - deltaY; y ++) { const uchar* src = srcImage.scanLine(y); uchar* dst = dstImage->scanLine(y + deltaY); copyQImageBuffer(dst, src, deltaX, width); } } else { for (int y = 0; y < height + deltaY; y ++) { const uchar* src = srcImage.scanLine(y - deltaY); uchar* dst = dstImage->scanLine(y); copyQImageBuffer(dst, src, deltaX, width); } } } struct KisPrescaledProjection::Private { Private() : viewportSize(0, 0) , projectionBackend(0) { } QImage prescaledQImage; QSize updatePatchSize; QSize canvasSize; QSize viewportSize; KisImageWSP image; KisCoordinatesConverter *coordinatesConverter; KisProjectionBackend* projectionBackend; }; KisPrescaledProjection::KisPrescaledProjection() : QObject(0) , m_d(new Private()) { updateSettings(); // we disable building the pyramid with setting its height to 1 // XXX: setting it higher than 1 is broken because it's not updated until you show/hide the layer m_d->projectionBackend = new KisImagePyramid(1); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(updateSettings())); } KisPrescaledProjection::~KisPrescaledProjection() { delete m_d->projectionBackend; delete m_d; } void KisPrescaledProjection::setImage(KisImageWSP image) { Q_ASSERT(image); m_d->image = image; m_d->projectionBackend->setImage(image); } QImage KisPrescaledProjection::prescaledQImage() const { return m_d->prescaledQImage; } void KisPrescaledProjection::setCoordinatesConverter(KisCoordinatesConverter *coordinatesConverter) { m_d->coordinatesConverter = coordinatesConverter; } void KisPrescaledProjection::updateSettings() { KisImageConfig imageConfig(false); m_d->updatePatchSize.setWidth(imageConfig.updatePatchWidth()); m_d->updatePatchSize.setHeight(imageConfig.updatePatchHeight()); } void KisPrescaledProjection::viewportMoved(const QPointF &offset) { // FIXME: \|/ if (m_d->prescaledQImage.isNull()) return; if (offset.isNull()) return; QPoint alignedOffset = offset.toPoint(); if(offset != alignedOffset) { /** * We can't optimize anything when offset is float :( * Just prescale entire image. */ dbgRender << "prescaling the entire image because the offset is float"; preScale(); return; } QImage newImage = QImage(m_d->viewportSize, QImage::Format_ARGB32); newImage.fill(0); /** * TODO: viewport rects should be cropped by the borders of * the image, because it may be requested to read/write * outside QImage and copyQImage will not catch it */ QRect newViewportRect = QRect(QPoint(0,0), m_d->viewportSize); QRect oldViewportRect = newViewportRect.translated(alignedOffset); QRegion updateRegion = newViewportRect; QRect savedArea = newViewportRect & oldViewportRect; if(!savedArea.isEmpty()) { copyQImage(alignedOffset.x(), alignedOffset.y(), &newImage, m_d->prescaledQImage); updateRegion -= savedArea; } QPainter gc(&newImage); QVector rects = updateRegion.rects(); Q_FOREACH (const QRect &rect, rects) { QRect imageRect = m_d->coordinatesConverter->viewportToImage(rect).toAlignedRect(); QVector patches = KritaUtils::splitRectIntoPatches(imageRect, m_d->updatePatchSize); Q_FOREACH (const QRect& rc, patches) { QRect viewportPatch = m_d->coordinatesConverter->imageToViewport(rc).toAlignedRect(); KisPPUpdateInfoSP info = getInitialUpdateInformation(QRect()); fillInUpdateInformation(viewportPatch, info); drawUsingBackend(gc, info); } } m_d->prescaledQImage = newImage; } void KisPrescaledProjection::slotImageSizeChanged(qint32 w, qint32 h) { m_d->projectionBackend->setImageSize(w, h); // viewport size is cropped by the size of the image // so we need to update it as well updateViewportSize(); } KisUpdateInfoSP KisPrescaledProjection::updateCache(const QRect &dirtyImageRect) { if (!m_d->image) { dbgRender << "Calling updateCache without an image: " << kisBacktrace() << endl; // return invalid info return new KisPPUpdateInfo(); } /** - * We needn't this stuff ouside KisImage's area. We're not displaying + * We needn't this stuff outside KisImage's area. We're not displaying * anything painted outside the image anyway. */ QRect croppedImageRect = dirtyImageRect & m_d->image->bounds(); if (croppedImageRect.isEmpty()) return new KisPPUpdateInfo(); KisPPUpdateInfoSP info = getInitialUpdateInformation(croppedImageRect); m_d->projectionBackend->updateCache(croppedImageRect); return info; } void KisPrescaledProjection::recalculateCache(KisUpdateInfoSP info) { KisPPUpdateInfoSP ppInfo = dynamic_cast(info.data()); if(!ppInfo) return; QRect rawViewRect = m_d->coordinatesConverter-> imageToViewport(ppInfo->dirtyImageRectVar).toAlignedRect(); fillInUpdateInformation(rawViewRect, ppInfo); m_d->projectionBackend->recalculateCache(ppInfo); if(!info->dirtyViewportRect().isEmpty()) updateScaledImage(ppInfo); } void KisPrescaledProjection::preScale() { if (!m_d->image) return; m_d->prescaledQImage.fill(0); QRect viewportRect(QPoint(0, 0), m_d->viewportSize); QRect imageRect = m_d->coordinatesConverter->viewportToImage(viewportRect).toAlignedRect(); QVector patches = KritaUtils::splitRectIntoPatches(imageRect, m_d->updatePatchSize); Q_FOREACH (const QRect& rc, patches) { QRect viewportPatch = m_d->coordinatesConverter->imageToViewport(rc).toAlignedRect(); KisPPUpdateInfoSP info = getInitialUpdateInformation(QRect()); fillInUpdateInformation(viewportPatch, info); QPainter gc(&m_d->prescaledQImage); gc.setCompositionMode(QPainter::CompositionMode_Source); drawUsingBackend(gc, info); } } void KisPrescaledProjection::setMonitorProfile(const KoColorProfile *monitorProfile, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { m_d->projectionBackend->setMonitorProfile(monitorProfile, renderingIntent, conversionFlags); } void KisPrescaledProjection::setChannelFlags(const QBitArray &channelFlags) { m_d->projectionBackend->setChannelFlags(channelFlags); } void KisPrescaledProjection::setDisplayFilter(QSharedPointer displayFilter) { m_d->projectionBackend->setDisplayFilter(displayFilter); } void KisPrescaledProjection::updateViewportSize() { QRectF imageRect = m_d->coordinatesConverter->imageRectInWidgetPixels(); QSizeF minimalSize(qMin(imageRect.width(), (qreal)m_d->canvasSize.width()), qMin(imageRect.height(), (qreal)m_d->canvasSize.height())); QRectF minimalRect(QPointF(0,0), minimalSize); m_d->viewportSize = m_d->coordinatesConverter->widgetToViewport(minimalRect).toAlignedRect().size(); if (m_d->prescaledQImage.isNull() || m_d->prescaledQImage.size() != m_d->viewportSize) { m_d->prescaledQImage = QImage(m_d->viewportSize, QImage::Format_ARGB32); m_d->prescaledQImage.fill(0); } } void KisPrescaledProjection::notifyZoomChanged() { updateViewportSize(); preScale(); } void KisPrescaledProjection::notifyCanvasSizeChanged(const QSize &widgetSize) { m_d->canvasSize = widgetSize; updateViewportSize(); preScale(); } KisPPUpdateInfoSP KisPrescaledProjection::getInitialUpdateInformation(const QRect &dirtyImageRect) { /** * This update information has nothing more than an information * about dirty image rect. All the other information used for * scaling will be fetched in fillUpdateInformation() later, * when we are working in the context of the UI thread */ KisPPUpdateInfoSP info = new KisPPUpdateInfo(); info->dirtyImageRectVar = dirtyImageRect; return info; } void KisPrescaledProjection::fillInUpdateInformation(const QRect &viewportRect, KisPPUpdateInfoSP info) { m_d->coordinatesConverter->imageScale(&info->scaleX, &info->scaleY); // first, crop the part of the view rect that is outside of the canvas QRect croppedViewRect = viewportRect.intersected(QRect(QPoint(0, 0), m_d->viewportSize)); // second, align this rect to the KisImage's pixels and pixels // of projection backend. info->imageRect = m_d->coordinatesConverter->viewportToImage(QRectF(croppedViewRect)).toAlignedRect(); /** * To avoid artifacts while scaling we use mechanism like * changeRect/needRect for layers. Here we grow the rect to update * pixels which depend on the dirty rect (like changeRect), and * later we request a bit more pixels for the patch to make the * scaling safe (like needRect). */ const int borderSize = BORDER_SIZE(qMax(info->scaleX, info->scaleY)); info->imageRect.adjust(-borderSize, -borderSize, borderSize, borderSize); info->imageRect = info->imageRect & m_d->image->bounds(); m_d->projectionBackend->alignSourceRect(info->imageRect, info->scaleX); // finally, compute the dirty rect of the canvas info->viewportRect = m_d->coordinatesConverter->imageToViewport(info->imageRect); info->borderWidth = 0; if (SCALE_MORE_OR_EQUAL_TO(info->scaleX, info->scaleY, 1.0)) { if (SCALE_LESS_THAN(info->scaleX, info->scaleY, 2.0)) { dbgRender << "smoothBetween100And200Percent" << endl; info->renderHints = QPainter::SmoothPixmapTransform; info->borderWidth = borderSize; } info->transfer = KisPPUpdateInfo::DIRECT; } else { // <100% info->renderHints = QPainter::SmoothPixmapTransform; info->borderWidth = borderSize; info->transfer = KisPPUpdateInfo::PATCH; } dbgRender << "#####################################"; dbgRender << ppVar(info->scaleX) << ppVar(info->scaleY); dbgRender << ppVar(info->borderWidth) << ppVar(info->renderHints); dbgRender << ppVar(info->transfer); dbgRender << ppVar(info->dirtyImageRectVar); dbgRender << "Not aligned rect of the canvas (raw):\t" << croppedViewRect; dbgRender << "Update rect in KisImage's pixels:\t" << info->imageRect; dbgRender << "Update rect in canvas' pixels:\t" << info->viewportRect; dbgRender << "#####################################"; } void KisPrescaledProjection::updateScaledImage(KisPPUpdateInfoSP info) { QPainter gc(&m_d->prescaledQImage); gc.setCompositionMode(QPainter::CompositionMode_Source); drawUsingBackend(gc, info); } void KisPrescaledProjection::drawUsingBackend(QPainter &gc, KisPPUpdateInfoSP info) { if (info->imageRect.isEmpty()) return; if (info->transfer == KisPPUpdateInfo::DIRECT) { m_d->projectionBackend->drawFromOriginalImage(gc, info); } else /* if info->transfer == KisPPUpdateInformation::PATCH */ { KisImagePatch patch = m_d->projectionBackend->getNearestPatch(info); // prescale the patch because otherwise we'd scale using QPainter, which gives // a crap result compared to QImage's smoothscale patch.preScale(info->viewportRect); patch.drawMe(gc, info->viewportRect, info->renderHints); } } diff --git a/libs/ui/flake/kis_node_dummies_graph.h b/libs/ui/flake/kis_node_dummies_graph.h index 4c91dcc567..65e2a4f09e 100644 --- a/libs/ui/flake/kis_node_dummies_graph.h +++ b/libs/ui/flake/kis_node_dummies_graph.h @@ -1,146 +1,146 @@ /* * Copyright (c) 2011 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_NODE_DUMMIES_GRAPH_H #define __KIS_NODE_DUMMIES_GRAPH_H #include #include #include "kritaui_export.h" #include "kis_types.h" #include "kis_node.h" class KisNodeShape; /** * KisNodeDummy is a simplified representation of a node * in the node stack. It stores all the hierarchy information * about the node, so you needn't access from the node * directly (actually, you cannot do it usually, because UI * works in a different thread and race conditions are possible). * * The dummy stores a KisNodeShape which can store a pointer to * to node. You can access to the node data through it, but take * care -- not all the information is accessible in * multithreading environment. * * The ownership on the KisNodeShape is taken by the dummy. * The ownership on the children of the dummy is taken as well. */ class KRITAUI_EXPORT KisNodeDummy : public QObject { Q_OBJECT public: /** - * Take care tha KisNodeDummy does not take ownership over + * Take care that KisNodeDummy does not take ownership over * the \p nodeShape since the handling of the removal of the * children of the shape is done by flake. So please handle it * manually. * * The children dummies of the dummy are still owned by the * dummy and are deleted automatically. */ KisNodeDummy(KisNodeShape *nodeShape, KisNodeSP node); ~KisNodeDummy() override; KisNodeDummy* firstChild() const; KisNodeDummy* lastChild() const; KisNodeDummy* nextSibling() const; KisNodeDummy* prevSibling() const; KisNodeDummy* parent() const; KisNodeDummy* at(int index) const; int childCount() const; int indexOf(KisNodeDummy *child) const; KisNodeSP node() const; private: friend class KisNodeShapesGraph; // for ::nodeShape() method friend class KisNodeShapesGraphTest; KisNodeShape* nodeShape() const; friend class KisNodeDummiesGraph; QList m_children; KisNodeShape *m_nodeShape; KisNodeSP m_node; }; /** * KisNodeDummiesGraph manages the hierarchy of dummy objects * representing nodes in the UI environment. */ class KRITAUI_EXPORT KisNodeDummiesGraph { public: KisNodeDummiesGraph(); KisNodeDummy* rootDummy() const; KisNodeDummy* nodeToDummy(KisNodeSP node); bool containsNode(KisNodeSP node) const; int dummiesCount() const; /** * Adds a dummy \p node to the position specified * by \p parent and \p aboveThis. * * It is not expected that you would add a dummy twice. */ void addNode(KisNodeDummy *node, KisNodeDummy *parent, KisNodeDummy *aboveThis); /** * Moves a dummy \p node from its current position to * the position specified by \p parent and \p aboveThis. * * It is expected that the dummy \p node has been added * to the graph with addNode() before calling this function. */ void moveNode(KisNodeDummy *node, KisNodeDummy *parent, KisNodeDummy *aboveThis); /** * Removes the dummy \p node from the graph. * * WARNING: The dummy is only "unlinked" from the graph. Neither * deletion of the node nor deletion of its children happens. * The dummy keeps maintaining its children so after unlinking * it from the graph you can just type to free memory recursively: * \code * graph.removeNode(node); * delete node; * \endcode */ void removeNode(KisNodeDummy *node); private: void unmapDummyRecursively(KisNodeDummy *dummy); private: typedef QMap NodeMap; private: KisNodeDummy *m_rootDummy; NodeMap m_dummiesMap; }; #endif /* __KIS_NODE_DUMMIES_GRAPH_H */ diff --git a/libs/ui/flake/kis_shape_controller.cpp b/libs/ui/flake/kis_shape_controller.cpp index 6e2d31722f..a399741b84 100644 --- a/libs/ui/flake/kis_shape_controller.cpp +++ b/libs/ui/flake/kis_shape_controller.cpp @@ -1,288 +1,283 @@ /* * 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_shape_controller.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_node_manager.h" #include "kis_shape_selection.h" #include "kis_selection.h" #include "kis_selection_component.h" #include "kis_adjustment_layer.h" #include "kis_clone_layer.h" #include "canvas/kis_canvas2.h" #include "KisDocument.h" #include "kis_image.h" #include "kis_group_layer.h" #include "kis_node_shape.h" #include "kis_node_shapes_graph.h" #include "kis_name_server.h" #include "kis_mask.h" #include "kis_shape_layer.h" #include "KisViewManager.h" #include "kis_node.h" #include #include #include #include #include "KoSelectedShapesProxy.h" #include "kis_signal_auto_connection.h" struct KisShapeController::Private { public: KisDocument *doc; KisNameServer *nameServer; KisSignalAutoConnectionsStore imageConnections; KisNodeShapesGraph shapesGraph; }; KisShapeController::KisShapeController(KisDocument *doc, KisNameServer *nameServer) : KisDummiesFacadeBase(doc) , m_d(new Private()) { m_d->doc = doc; m_d->nameServer = nameServer; resourceManager()->setUndoStack(doc->undoStack()); } KisShapeController::~KisShapeController() { KisNodeDummy *node = m_d->shapesGraph.rootDummy(); if (node) { m_d->shapesGraph.removeNode(node->node()); } delete m_d; } void KisShapeController::slotUpdateDocumentResolution() { const qreal pixelsPerInch = m_d->doc->image()->xRes() * 72.0; resourceManager()->setResource(KoDocumentResourceManager::DocumentResolution, pixelsPerInch); } void KisShapeController::slotUpdateDocumentSize() { resourceManager()->setResource(KoDocumentResourceManager::DocumentRectInPixels, m_d->doc->image()->bounds()); } void KisShapeController::addNodeImpl(KisNodeSP node, KisNodeSP parent, KisNodeSP aboveThis) { KisNodeShape *newShape = m_d->shapesGraph.addNode(node, parent, aboveThis); // XXX: what are we going to do with this shape? Q_UNUSED(newShape); KisShapeLayer *shapeLayer = dynamic_cast(node.data()); if (shapeLayer) { /** * Forward signals for global shape manager * \see comment in the constructor of KisCanvas2 */ connect(shapeLayer, SIGNAL(selectionChanged()), SIGNAL(selectionChanged())); connect(shapeLayer->shapeManager(), SIGNAL(selectionContentChanged()), SIGNAL(selectionContentChanged())); connect(shapeLayer, SIGNAL(currentLayerChanged(const KoShapeLayer*)), SIGNAL(currentLayerChanged(const KoShapeLayer*))); } } void KisShapeController::removeNodeImpl(KisNodeSP node) { KisShapeLayer *shapeLayer = dynamic_cast(node.data()); if (shapeLayer) { shapeLayer->disconnect(this); } m_d->shapesGraph.removeNode(node); } bool KisShapeController::hasDummyForNode(KisNodeSP node) const { return m_d->shapesGraph.containsNode(node); } KisNodeDummy* KisShapeController::dummyForNode(KisNodeSP node) const { return m_d->shapesGraph.nodeToDummy(node); } KisNodeDummy* KisShapeController::rootDummy() const { return m_d->shapesGraph.rootDummy(); } int KisShapeController::dummiesCount() const { return m_d->shapesGraph.shapesCount(); } static inline bool belongsToShapeSelection(KoShape* shape) { return dynamic_cast(shape->userData()); } void KisShapeController::addShapes(const QList shapes) { KIS_SAFE_ASSERT_RECOVER_RETURN(!shapes.isEmpty()); KisCanvas2 *canvas = dynamic_cast(KoToolManager::instance()->activeCanvasController()->canvas()); KIS_SAFE_ASSERT_RECOVER_RETURN(canvas); const KoShape *baseShapeParent = shapes.first()->parent(); const bool baseBelongsToSelection = belongsToShapeSelection(shapes.first()); bool allSameParent = true; bool allSameBelongsToShapeSelection = true; bool hasNullParent = false; Q_FOREACH (KoShape *shape, shapes) { hasNullParent |= !shape->parent(); allSameParent &= shape->parent() == baseShapeParent; allSameBelongsToShapeSelection &= belongsToShapeSelection(shape) == baseBelongsToSelection; } KIS_SAFE_ASSERT_RECOVER_RETURN(!baseBelongsToSelection || allSameBelongsToShapeSelection); if (!allSameParent || hasNullParent) { if (baseBelongsToSelection && allSameBelongsToShapeSelection) { KisSelectionSP selection = canvas->viewManager()->selection(); if (selection) { if (!selection->shapeSelection()) { selection->setShapeSelection(new KisShapeSelection(this, image(), selection)); } KisShapeSelection * shapeSelection = static_cast(selection->shapeSelection()); Q_FOREACH(KoShape *shape, shapes) { shapeSelection->addShape(shape); } } } else { KisShapeLayer *shapeLayer = dynamic_cast( canvas->selectedShapesProxy()->selection()->activeLayer()); if (!shapeLayer) { shapeLayer = new KisShapeLayer(this, image(), i18n("Vector Layer %1", m_d->nameServer->number()), OPACITY_OPAQUE_U8); image()->undoAdapter()->addCommand(new KisImageLayerAddCommand(image(), shapeLayer, image()->rootLayer(), image()->rootLayer()->childCount())); } - QRectF updateRect; - Q_FOREACH(KoShape *shape, shapes) { shapeLayer->addShape(shape); - updateRect |= shape->boundingRect(); } - - canvas->shapeManager()->update(updateRect); } } m_d->doc->setModified(true); } void KisShapeController::removeShape(KoShape* shape) { /** * Krita layers have their own destruction path. * It goes through slotRemoveNode() */ Q_ASSERT(shape->shapeId() != KIS_NODE_SHAPE_ID && shape->shapeId() != KIS_SHAPE_LAYER_ID); QRectF updateRect = shape->boundingRect(); shape->setParent(0); KisCanvas2 *canvas = dynamic_cast(KoToolManager::instance()->activeCanvasController()->canvas()); KIS_SAFE_ASSERT_RECOVER_RETURN(canvas); canvas->shapeManager()->update(updateRect); m_d->doc->setModified(true); } QRectF KisShapeController::documentRectInPixels() const { return m_d->doc->image()->bounds(); } qreal KisShapeController::pixelsPerInch() const { return m_d->doc->image()->xRes() * 72.0; } void KisShapeController::setInitialShapeForCanvas(KisCanvas2 *canvas) { if (!image()) return; KisNodeSP rootNode = image()->root(); if (m_d->shapesGraph.containsNode(rootNode)) { Q_ASSERT(canvas); Q_ASSERT(canvas->shapeManager()); KoSelection *selection = canvas->shapeManager()->selection(); if (selection && m_d->shapesGraph.nodeToShape(rootNode)) { selection->select(m_d->shapesGraph.nodeToShape(rootNode)); KoToolManager::instance()->switchToolRequested(KoToolManager::instance()->preferredToolForSelection(selection->selectedShapes())); } } } void KisShapeController::setImage(KisImageWSP image) { m_d->imageConnections.clear(); if (image) { m_d->imageConnections.addConnection(image, SIGNAL(sigResolutionChanged(double, double)), this, SLOT(slotUpdateDocumentResolution())); m_d->imageConnections.addConnection(image, SIGNAL(sigSizeChanged(QPointF, QPointF)), this, SLOT(slotUpdateDocumentSize())); } slotUpdateDocumentResolution(); slotUpdateDocumentSize(); KisDummiesFacadeBase::setImage(image); } KoShapeLayer* KisShapeController::shapeForNode(KisNodeSP node) const { if (node) { return m_d->shapesGraph.nodeToShape(node); } return 0; } diff --git a/libs/ui/flake/kis_shape_layer.cc b/libs/ui/flake/kis_shape_layer.cc index bd3a55bda5..26677e7383 100644 --- a/libs/ui/flake/kis_shape_layer.cc +++ b/libs/ui/flake/kis_shape_layer.cc @@ -1,729 +1,729 @@ /* * Copyright (c) 2006-2008 Boudewijn Rempt * Copyright (c) 2007 Thomas Zander * Copyright (c) 2009 Cyrille Berger * Copyright (c) 2011 Jan Hambrecht * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_shape_layer.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "SvgWriter.h" #include "SvgParser.h" #include #include #include "kis_default_bounds.h" #include #include "kis_shape_layer_canvas.h" #include "kis_image_view_converter.h" #include #include "kis_node_visitor.h" #include "kis_processing_visitor.h" #include "kis_effect_mask.h" #include "commands/KoShapeReorderCommand.h" #include class ShapeLayerContainerModel : public SimpleShapeContainerModel { public: ShapeLayerContainerModel(KisShapeLayer *parent) : q(parent) {} void add(KoShape *child) override { SimpleShapeContainerModel::add(child); /** * The shape is always added with the absolute transformation set appropriately. * Here we should just squeeze it into the layer's transformation. */ KIS_SAFE_ASSERT_RECOVER_NOOP(inheritsTransform(child)); if (inheritsTransform(child)) { QTransform parentTransform = q->absoluteTransformation(0); child->applyAbsoluteTransformation(parentTransform.inverted()); } } void remove(KoShape *child) override { KIS_SAFE_ASSERT_RECOVER_NOOP(inheritsTransform(child)); if (inheritsTransform(child)) { QTransform parentTransform = q->absoluteTransformation(0); child->applyAbsoluteTransformation(parentTransform); } SimpleShapeContainerModel::remove(child); } void shapeHasBeenAddedToHierarchy(KoShape *shape, KoShapeContainer *addedToSubtree) override { q->shapeManager()->addShape(shape); SimpleShapeContainerModel::shapeHasBeenAddedToHierarchy(shape, addedToSubtree); } void shapeToBeRemovedFromHierarchy(KoShape *shape, KoShapeContainer *removedFromSubtree) override { q->shapeManager()->remove(shape); SimpleShapeContainerModel::shapeToBeRemovedFromHierarchy(shape, removedFromSubtree); } private: KisShapeLayer *q; }; struct KisShapeLayer::Private { public: Private() : canvas(0) , controller(0) , x(0) , y(0) {} KisPaintDeviceSP paintDevice; KisShapeLayerCanvasBase * canvas; KoShapeControllerBase* controller; int x; int y; }; KisShapeLayer::KisShapeLayer(KoShapeControllerBase* controller, KisImageWSP image, const QString &name, quint8 opacity) : KisExternalLayer(image, name, opacity), KoShapeLayer(new ShapeLayerContainerModel(this)), m_d(new Private()) { initShapeLayer(controller); } KisShapeLayer::KisShapeLayer(const KisShapeLayer& rhs) : KisShapeLayer(rhs, rhs.m_d->controller) { } KisShapeLayer::KisShapeLayer(const KisShapeLayer& _rhs, KoShapeControllerBase* controller, KisShapeLayerCanvasBase *canvas) : KisExternalLayer(_rhs) , KoShapeLayer(new ShapeLayerContainerModel(this)) //no _rhs here otherwise both layer have the same KoShapeContainerModel , m_d(new Private()) { // copy the projection to avoid extra round of updates! initShapeLayer(controller, _rhs.m_d->paintDevice, canvas); /** * The transformaitons of the added shapes are automatically merged into the transformation * of the layer, so we should apply this extra transform separately */ const QTransform thisInvertedTransform = this->absoluteTransformation(0).inverted(); - m_d->canvas->setUpdatesBlocked(true); + m_d->canvas->shapeManager()->setUpdatesBlocked(true); Q_FOREACH (KoShape *shape, _rhs.shapes()) { KoShape *clonedShape = shape->cloneShape(); KIS_SAFE_ASSERT_RECOVER(clonedShape) { continue; } clonedShape->setTransformation(shape->absoluteTransformation(0) * thisInvertedTransform); addShape(clonedShape); } - m_d->canvas->setUpdatesBlocked(false); + m_d->canvas->shapeManager()->setUpdatesBlocked(false); } KisShapeLayer::KisShapeLayer(const KisShapeLayer& _rhs, const KisShapeLayer &_addShapes) : KisExternalLayer(_rhs) , KoShapeLayer(new ShapeLayerContainerModel(this)) //no _merge here otherwise both layer have the same KoShapeContainerModel , m_d(new Private()) { // Make sure our new layer is visible otherwise the shapes cannot be painted. setVisible(true); initShapeLayer(_rhs.m_d->controller); /** * With current implementation this matrix will always be an identity, because * we do not copy the transformation from any of the source layers. But we should * handle this anyway, to not be caught by this in the future. */ const QTransform thisInvertedTransform = this->absoluteTransformation(0).inverted(); QList shapesAbove; QList shapesBelow; // copy in _rhs's shapes Q_FOREACH (KoShape *shape, _rhs.shapes()) { KoShape *clonedShape = shape->cloneShape(); KIS_SAFE_ASSERT_RECOVER(clonedShape) { continue; } clonedShape->setTransformation(shape->absoluteTransformation(0) * thisInvertedTransform); shapesBelow.append(clonedShape); } // copy in _addShapes's shapes Q_FOREACH (KoShape *shape, _addShapes.shapes()) { KoShape *clonedShape = shape->cloneShape(); KIS_SAFE_ASSERT_RECOVER(clonedShape) { continue; } clonedShape->setTransformation(shape->absoluteTransformation(0) * thisInvertedTransform); shapesAbove.append(clonedShape); } QList shapes = KoShapeReorderCommand::mergeDownShapes(shapesBelow, shapesAbove); KoShapeReorderCommand cmd(shapes); cmd.redo(); Q_FOREACH (KoShape *shape, shapesBelow + shapesAbove) { addShape(shape); } } KisShapeLayer::KisShapeLayer(KoShapeControllerBase* controller, KisImageWSP image, const QString &name, quint8 opacity, KisShapeLayerCanvasBase *canvas) : KisExternalLayer(image, name, opacity) , KoShapeLayer(new ShapeLayerContainerModel(this)) , m_d(new Private()) { initShapeLayer(controller, nullptr, canvas); } KisShapeLayer::~KisShapeLayer() { /** * Small hack alert: we should avoid updates on shape deletion */ m_d->canvas->prepareForDestroying(); Q_FOREACH (KoShape *shape, shapes()) { shape->setParent(0); delete shape; } delete m_d->canvas; delete m_d; } void KisShapeLayer::initShapeLayer(KoShapeControllerBase* controller, KisPaintDeviceSP copyFromProjection, KisShapeLayerCanvasBase *canvas) { setSupportsLodMoves(false); setShapeId(KIS_SHAPE_LAYER_ID); KIS_ASSERT_RECOVER_NOOP(this->image()); if (!copyFromProjection) { m_d->paintDevice = new KisPaintDevice(image()->colorSpace()); m_d->paintDevice->setDefaultBounds(new KisDefaultBounds(this->image())); m_d->paintDevice->setParentNode(this); } else { m_d->paintDevice = new KisPaintDevice(*copyFromProjection); } if (!canvas) { auto *slCanvas = new KisShapeLayerCanvas(this, image()); slCanvas->setProjection(m_d->paintDevice); canvas = slCanvas; } m_d->canvas = canvas; m_d->canvas->moveToThread(this->thread()); m_d->controller = controller; m_d->canvas->shapeManager()->selection()->disconnect(this); connect(m_d->canvas->selectedShapesProxy(), SIGNAL(selectionChanged()), this, SIGNAL(selectionChanged())); connect(m_d->canvas->selectedShapesProxy(), SIGNAL(currentLayerChanged(const KoShapeLayer*)), this, SIGNAL(currentLayerChanged(const KoShapeLayer*))); connect(this, SIGNAL(sigMoveShapes(QPointF)), SLOT(slotMoveShapes(QPointF))); } bool KisShapeLayer::allowAsChild(KisNodeSP node) const { return node->inherits("KisMask"); } void KisShapeLayer::setImage(KisImageWSP _image) { KisLayer::setImage(_image); m_d->canvas->setImage(_image); m_d->paintDevice->convertTo(_image->colorSpace()); m_d->paintDevice->setDefaultBounds(new KisDefaultBounds(_image)); } KisLayerSP KisShapeLayer::createMergedLayerTemplate(KisLayerSP prevLayer) { KisShapeLayer *prevShape = dynamic_cast(prevLayer.data()); if (prevShape) return new KisShapeLayer(*prevShape, *this); else return KisExternalLayer::createMergedLayerTemplate(prevLayer); } void KisShapeLayer::fillMergedLayerTemplate(KisLayerSP dstLayer, KisLayerSP prevLayer) { if (!dynamic_cast(dstLayer.data())) { KisLayer::fillMergedLayerTemplate(dstLayer, prevLayer); } } void KisShapeLayer::setParent(KoShapeContainer *parent) { Q_UNUSED(parent) KIS_ASSERT_RECOVER_RETURN(0) } QIcon KisShapeLayer::icon() const { return KisIconUtils::loadIcon("vectorLayer"); } KisPaintDeviceSP KisShapeLayer::original() const { return m_d->paintDevice; } KisPaintDeviceSP KisShapeLayer::paintDevice() const { return 0; } qint32 KisShapeLayer::x() const { return m_d->x; } qint32 KisShapeLayer::y() const { return m_d->y; } void KisShapeLayer::setX(qint32 x) { qint32 delta = x - this->x(); QPointF diff = QPointF(m_d->canvas->viewConverter()->viewToDocumentX(delta), 0); emit sigMoveShapes(diff); // Save new value to satisfy LSP m_d->x = x; } void KisShapeLayer::setY(qint32 y) { qint32 delta = y - this->y(); QPointF diff = QPointF(0, m_d->canvas->viewConverter()->viewToDocumentY(delta)); emit sigMoveShapes(diff); // Save new value to satisfy LSP m_d->y = y; } namespace { void filterTransformableShapes(QList &shapes) { auto it = shapes.begin(); while (it != shapes.end()) { if (shapes.size() == 1) break; if ((*it)->inheritsTransformFromAny(shapes)) { it = shapes.erase(it); } else { ++it; } } } } QList KisShapeLayer::shapesToBeTransformed() { QList shapes = shapeManager()->shapes(); // We expect that **all** the shapes inherit the transform from its parent // SANITY_CHECK: we expect all the shapes inside the // shape layer to inherit transform! Q_FOREACH (KoShape *shape, shapes) { if (shape->parent()) { KIS_SAFE_ASSERT_RECOVER(shape->parent()->inheritsTransform(shape)) { break; } } } shapes << this; filterTransformableShapes(shapes); return shapes; } void KisShapeLayer::slotMoveShapes(const QPointF &diff) { QList shapes = shapesToBeTransformed(); if (shapes.isEmpty()) return; KoShapeMoveCommand cmd(shapes, diff); cmd.redo(); } bool KisShapeLayer::accept(KisNodeVisitor& visitor) { return visitor.visit(this); } void KisShapeLayer::accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter) { return visitor.visit(this, undoAdapter); } KoShapeManager* KisShapeLayer::shapeManager() const { return m_d->canvas->shapeManager(); } KoViewConverter* KisShapeLayer::converter() const { return m_d->canvas->viewConverter(); } bool KisShapeLayer::visible(bool recursive) const { return KisExternalLayer::visible(recursive); } void KisShapeLayer::setVisible(bool visible, bool isLoading) { const bool oldVisible = this->visible(false); KoShapeLayer::setVisible(visible); KisExternalLayer::setVisible(visible, isLoading); if (visible && !oldVisible && m_d->canvas->hasChangedWhileBeingInvisible()) { m_d->canvas->rerenderAfterBeingInvisible(); } } void KisShapeLayer::setUserLocked(bool value) { KoShapeLayer::setGeometryProtected(value); KisExternalLayer::setUserLocked(value); } bool KisShapeLayer::isShapeEditable(bool recursive) const { return KoShapeLayer::isShapeEditable(recursive) && isEditable(true); } // we do not override KoShape::setGeometryProtected() as we consider // the user not being able to access the layer shape from Krita UI! void KisShapeLayer::forceUpdateTimedNode() { m_d->canvas->forceRepaint(); } bool KisShapeLayer::hasPendingTimedUpdates() const { return m_d->canvas->hasPendingUpdates(); } void KisShapeLayer::forceUpdateHiddenAreaOnOriginal() { m_d->canvas->forceRepaintWithHiddenAreas(); } bool KisShapeLayer::saveShapesToStore(KoStore *store, QList shapes, const QSizeF &sizeInPt) { if (!store->open("content.svg")) { return false; } KoStoreDevice storeDev(store); storeDev.open(QIODevice::WriteOnly); std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); SvgWriter writer(shapes); writer.save(storeDev, sizeInPt); if (!store->close()) { return false; } return true; } QList KisShapeLayer::createShapesFromSvg(QIODevice *device, const QString &baseXmlDir, const QRectF &rectInPixels, qreal resolutionPPI, KoDocumentResourceManager *resourceManager, QSizeF *fragmentSize) { QString errorMsg; int errorLine = 0; int errorColumn; KoXmlDocument doc = SvgParser::createDocumentFromSvg(device, &errorMsg, &errorLine, &errorColumn); if (doc.isNull()) { errKrita << "Parsing error in " << "contents.svg" << "! Aborting!" << endl << " In line: " << errorLine << ", column: " << errorColumn << endl << " Error message: " << errorMsg << endl; errKrita << i18n("Parsing error in the main document at line %1, column %2\nError message: %3" , errorLine , errorColumn , errorMsg); } SvgParser parser(resourceManager); parser.setXmlBaseDir(baseXmlDir); parser.setResolution(rectInPixels /* px */, resolutionPPI /* ppi */); return parser.parseSvg(doc.documentElement(), fragmentSize); } bool KisShapeLayer::saveLayer(KoStore * store) const { // FIXME: we handle xRes() only! const QSizeF sizeInPx = image()->bounds().size(); const QSizeF sizeInPt(sizeInPx.width() / image()->xRes(), sizeInPx.height() / image()->yRes()); return saveShapesToStore(store, this->shapes(), sizeInPt); } bool KisShapeLayer::loadSvg(QIODevice *device, const QString &baseXmlDir) { QSizeF fragmentSize; // unused! KisImageSP image = this->image(); // FIXME: we handle xRes() only! KIS_SAFE_ASSERT_RECOVER_NOOP(qFuzzyCompare(image->xRes(), image->yRes())); const qreal resolutionPPI = 72.0 * image->xRes(); QList shapes = createShapesFromSvg(device, baseXmlDir, image->bounds(), resolutionPPI, m_d->controller->resourceManager(), &fragmentSize); Q_FOREACH (KoShape *shape, shapes) { addShape(shape); } return true; } bool KisShapeLayer::loadLayer(KoStore* store) { if (!store) { warnKrita << i18n("No store backend"); return false; } if (store->open("content.svg")) { KoStoreDevice storeDev(store); storeDev.open(QIODevice::ReadOnly); loadSvg(&storeDev, ""); store->close(); return true; } KoOdfReadStore odfStore(store); QString errorMessage; odfStore.loadAndParse(errorMessage); if (!errorMessage.isEmpty()) { warnKrita << errorMessage; return false; } KoXmlElement contents = odfStore.contentDoc().documentElement(); // dbgKrita <<"Start loading OASIS document..." << contents.text(); // dbgKrita <<"Start loading OASIS contents..." << contents.lastChild().localName(); // dbgKrita <<"Start loading OASIS contents..." << contents.lastChild().namespaceURI(); // dbgKrita <<"Start loading OASIS contents..." << contents.lastChild().isElement(); KoXmlElement body(KoXml::namedItemNS(contents, KoXmlNS::office, "body")); if (body.isNull()) { //setErrorMessage( i18n( "Invalid OASIS document. No office:body tag found." ) ); return false; } body = KoXml::namedItemNS(body, KoXmlNS::office, "drawing"); if (body.isNull()) { //setErrorMessage( i18n( "Invalid OASIS document. No office:drawing tag found." ) ); return false; } KoXmlElement page(KoXml::namedItemNS(body, KoXmlNS::draw, "page")); if (page.isNull()) { //setErrorMessage( i18n( "Invalid OASIS document. No draw:page tag found." ) ); return false; } KoXmlElement * master = 0; if (odfStore.styles().masterPages().contains("Standard")) master = odfStore.styles().masterPages().value("Standard"); else if (odfStore.styles().masterPages().contains("Default")) master = odfStore.styles().masterPages().value("Default"); else if (! odfStore.styles().masterPages().empty()) master = odfStore.styles().masterPages().begin().value(); if (master) { const KoXmlElement *style = odfStore.styles().findStyle( master->attributeNS(KoXmlNS::style, "page-layout-name", QString())); KoPageLayout pageLayout; pageLayout.loadOdf(*style); setSize(QSizeF(pageLayout.width, pageLayout.height)); } // We work fine without a master page KoOdfLoadingContext context(odfStore.styles(), odfStore.store()); context.setManifestFile(QString("tar:/") + odfStore.store()->currentPath() + "META-INF/manifest.xml"); KoShapeLoadingContext shapeContext(context, m_d->controller->resourceManager()); KoXmlElement layerElement; forEachElement(layerElement, context.stylesReader().layerSet()) { // FIXME: investigate what is this // KoShapeLayer * l = new KoShapeLayer(); if (!loadOdf(layerElement, shapeContext)) { dbgKrita << "Could not load vector layer!"; return false; } } KoXmlElement child; forEachElement(child, page) { KoShape * shape = KoShapeRegistry::instance()->createShapeFromOdf(child, shapeContext); if (shape) { addShape(shape); } } return true; } void KisShapeLayer::resetCache() { m_d->canvas->resetCache(); } KUndo2Command* KisShapeLayer::crop(const QRect & rect) { QPoint oldPos(x(), y()); QPoint newPos = oldPos - rect.topLeft(); return new KisNodeMoveCommand2(this, oldPos, newPos); } KUndo2Command* KisShapeLayer::transform(const QTransform &transform) { QList shapes = shapesToBeTransformed(); if (shapes.isEmpty()) return 0; KisImageViewConverter *converter = dynamic_cast(this->converter()); QTransform realTransform = converter->documentToView() * transform * converter->viewToDocument(); QList oldTransformations; QList newTransformations; QList newShadows; const qreal transformBaseScale = KoUnit::approxTransformScale(transform); Q_FOREACH (const KoShape* shape, shapes) { QTransform oldTransform = shape->transformation(); oldTransformations.append(oldTransform); QTransform globalTransform = shape->absoluteTransformation(0); QTransform localTransform = globalTransform * realTransform * globalTransform.inverted(); newTransformations.append(localTransform * oldTransform); KoShapeShadow *shadow = 0; if (shape->shadow()) { shadow = new KoShapeShadow(*shape->shadow()); shadow->setOffset(transformBaseScale * shadow->offset()); shadow->setBlur(transformBaseScale * shadow->blur()); } newShadows.append(shadow); } KUndo2Command *parentCommand = new KUndo2Command(); new KoShapeTransformCommand(shapes, oldTransformations, newTransformations, parentCommand); new KoShapeShadowCommand(shapes, newShadows, parentCommand); return parentCommand; } KoShapeControllerBase *KisShapeLayer::shapeController() const { return m_d->controller; } diff --git a/libs/ui/flake/kis_shape_layer_canvas.cpp b/libs/ui/flake/kis_shape_layer_canvas.cpp index df5e136ed9..d7879c26d5 100644 --- a/libs/ui/flake/kis_shape_layer_canvas.cpp +++ b/libs/ui/flake/kis_shape_layer_canvas.cpp @@ -1,418 +1,419 @@ /* * 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_shape_layer_canvas.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_image_view_converter.h" #include #include #include #include #include "kis_global.h" //#define DEBUG_REPAINT KisShapeLayerCanvasBase::KisShapeLayerCanvasBase(KisShapeLayer *parent, KisImageWSP image) : KoCanvasBase(0) , m_viewConverter(new KisImageViewConverter(image)) , m_shapeManager(new KoShapeManager(this)) , m_selectedShapesProxy(new KoSelectedShapesProxySimple(m_shapeManager.data())) { m_shapeManager->selection()->setActiveLayer(parent); } KoShapeManager *KisShapeLayerCanvasBase::shapeManager() const { return m_shapeManager.data(); } KoSelectedShapesProxy *KisShapeLayerCanvasBase::selectedShapesProxy() const { return m_selectedShapesProxy.data(); } KoViewConverter* KisShapeLayerCanvasBase::viewConverter() const { return m_viewConverter.data(); } void KisShapeLayerCanvasBase::gridSize(QPointF *offset, QSizeF *spacing) const { KIS_SAFE_ASSERT_RECOVER_NOOP(false); // This should never be called as this canvas should have no tools. Q_UNUSED(offset); Q_UNUSED(spacing); } bool KisShapeLayerCanvasBase::snapToGrid() const { KIS_SAFE_ASSERT_RECOVER_NOOP(false); // This should never be called as this canvas should have no tools. return false; } void KisShapeLayerCanvasBase::addCommand(KUndo2Command *) { KIS_SAFE_ASSERT_RECOVER_NOOP(false); // This should never be called as this canvas should have no tools. } KoToolProxy * KisShapeLayerCanvasBase::toolProxy() const { // KIS_SAFE_ASSERT_RECOVER_NOOP(false); // This should never be called as this canvas should have no tools. return 0; } QWidget* KisShapeLayerCanvasBase::canvasWidget() { return 0; } const QWidget* KisShapeLayerCanvasBase::canvasWidget() const { return 0; } KoUnit KisShapeLayerCanvasBase::unit() const { KIS_SAFE_ASSERT_RECOVER_NOOP(false); // This should never be called as this canvas should have no tools. return KoUnit(KoUnit::Point); } -void KisShapeLayerCanvasBase::setUpdatesBlocked(bool value) -{ - m_updatesBlocked = value; -} - -bool KisShapeLayerCanvasBase::updatesBlocked() const -{ - return m_updatesBlocked; -} - void KisShapeLayerCanvasBase::prepareForDestroying() { m_isDestroying = true; } bool KisShapeLayerCanvasBase::hasChangedWhileBeingInvisible() { return m_hasChangedWhileBeingInvisible; } KisShapeLayerCanvas::KisShapeLayerCanvas(KisShapeLayer *parent, KisImageWSP image) : KisShapeLayerCanvasBase(parent, image) , m_projection(0) , m_parentLayer(parent) , m_canvasUpdateCompressor(100, KisSignalCompressor::FIRST_INACTIVE) , m_asyncUpdateSignalCompressor(100, KisSignalCompressor::FIRST_INACTIVE) { /** * The layour should also add itself to its own shape manager, so that the canvas * would track its changes/transformations */ m_shapeManager->addShape(parent, KoShapeManager::AddWithoutRepaint); m_shapeManager->selection()->setActiveLayer(parent); connect(&m_asyncUpdateSignalCompressor, SIGNAL(timeout()), SLOT(slotStartAsyncRepaint())); connect(this, SIGNAL(forwardRepaint()), &m_canvasUpdateCompressor, SLOT(start())); connect(&m_canvasUpdateCompressor, SIGNAL(timeout()), this, SLOT(slotStartDirectSyncRepaint())); setImage(image); } KisShapeLayerCanvas::~KisShapeLayerCanvas() { m_shapeManager->remove(m_parentLayer); } void KisShapeLayerCanvas::setImage(KisImageWSP image) { if (m_image) { disconnect(m_image, 0, this, 0); } m_viewConverter->setImage(image); m_image = image; if (image) { connect(m_image, SIGNAL(sigSizeChanged(QPointF,QPointF)), SLOT(slotImageSizeChanged())); m_cachedImageRect = m_image->bounds(); } updateUpdateCompressorDelay(); } #ifdef DEBUG_REPAINT # include #endif class KisRepaintShapeLayerLayerJob : public KisSpontaneousJob { public: KisRepaintShapeLayerLayerJob(KisShapeLayerSP layer, KisShapeLayerCanvas *canvas) : m_layer(layer), m_canvas(canvas) { } bool overrides(const KisSpontaneousJob *_otherJob) override { const KisRepaintShapeLayerLayerJob *otherJob = dynamic_cast(_otherJob); return otherJob && otherJob->m_canvas == m_canvas; } void run() override { m_canvas->repaint(); } int levelOfDetail() const override { return 0; } private: // we store a pointer to the layer just // to keep the lifetime of the canvas! KisShapeLayerSP m_layer; KisShapeLayerCanvas *m_canvas; }; void KisShapeLayerCanvas::updateCanvas(const QVector ®ion) { - if (!m_parentLayer->image() || m_isDestroying || m_updatesBlocked) { + if (!m_parentLayer->image() || m_isDestroying) { return; } { QMutexLocker locker(&m_dirtyRegionMutex); Q_FOREACH (const QRectF &rc, region) { // grow for antialiasing const QRect imageRect = kisGrowRect(m_viewConverter->documentToView(rc).toAlignedRect(), 2); m_dirtyRegion += imageRect; } } /** * HACK ALERT! * * The shapes may be accessed from both, GUI and worker threads! And we have no real * guard against this until the vector tools will be ported to the strokes framework. * * Here we just avoid the most obvious conflict of threads: * * 1) If the layer is modified by a non-gui (worker) thread, use a spontaneous jobs * to rerender the canvas. The job will be executed (almost) exclusively and it is * the responsibility of the worker thread to add a barrier to wait until this job is * completed, and not try to access the shapes concurrently. * * 2) If the layer is modified by a gui thread, it means that we are being accessed by * a legacy vector tool. It this case just emit a queued signal to make sure the updates * are compressed a little bit (TODO: add a compressor?) */ if (qApp->thread() == QThread::currentThread()) { emit forwardRepaint(); m_hasDirectSyncRepaintInitiated = true; } else { m_asyncUpdateSignalCompressor.start(); m_hasUpdateInCompressor = true; } } void KisShapeLayerCanvas::updateCanvas(const QRectF& rc) { updateCanvas(QVector({rc})); } void KisShapeLayerCanvas::slotStartAsyncRepaint() { m_hasUpdateInCompressor = false; m_image->addSpontaneousJob(new KisRepaintShapeLayerLayerJob(m_parentLayer, this)); } void KisShapeLayerCanvas::slotStartDirectSyncRepaint() { m_hasDirectSyncRepaintInitiated = false; repaint(); } void KisShapeLayerCanvas::slotImageSizeChanged() { QRegion dirtyCacheRegion; dirtyCacheRegion += m_image->bounds(); dirtyCacheRegion += m_cachedImageRect; dirtyCacheRegion -= m_image->bounds() & m_cachedImageRect; QVector dirtyRects; Q_FOREACH (const QRect &rc, dirtyCacheRegion.rects()) { dirtyRects.append(m_viewConverter->viewToDocument(rc)); } updateCanvas(dirtyRects); m_cachedImageRect = m_image->bounds(); updateUpdateCompressorDelay(); } void KisShapeLayerCanvas::repaint() { QRect repaintRect; bool forceUpdateHiddenAreasOnly = false; { QMutexLocker locker(&m_dirtyRegionMutex); repaintRect = m_dirtyRegion.boundingRect(); forceUpdateHiddenAreasOnly = m_forceUpdateHiddenAreasOnly; m_dirtyRegion = QRegion(); m_forceUpdateHiddenAreasOnly = false; } if (!forceUpdateHiddenAreasOnly) { if (repaintRect.isEmpty()) { return; } // Crop the update rect by the image bounds. We keep the cache consistent // by tracking the size of the image in slotImageSizeChanged() repaintRect = repaintRect.intersected(m_parentLayer->image()->bounds()); } else { const QRectF shapesBounds = KoShape::boundingRect(m_shapeManager->shapes()); repaintRect = kisGrowRect(m_viewConverter->documentToView(shapesBounds).toAlignedRect(), 2); } - QImage image(repaintRect.width(), repaintRect.height(), QImage::Format_ARGB32); - image.fill(0); + const QRect r = repaintRect; + const qint32 MASK_IMAGE_WIDTH = 256; + const qint32 MASK_IMAGE_HEIGHT = 256; + + QImage image(MASK_IMAGE_WIDTH, MASK_IMAGE_HEIGHT, QImage::Format_ARGB32); QPainter tempPainter(&image); tempPainter.setRenderHint(QPainter::Antialiasing); tempPainter.setRenderHint(QPainter::TextAntialiasing); - tempPainter.translate(-repaintRect.x(), -repaintRect.y()); - tempPainter.setClipRect(repaintRect); -#ifdef DEBUG_REPAINT - QColor color = QColor(random() % 255, random() % 255, random() % 255); - tempPainter.fillRect(r, color); -#endif - m_shapeManager->paint(tempPainter, *m_viewConverter, false); - tempPainter.end(); + quint8 * dstData = new quint8[MASK_IMAGE_WIDTH * MASK_IMAGE_HEIGHT * m_projection->pixelSize()]; - KisPaintDeviceSP dev = new KisPaintDevice(m_projection->colorSpace()); - dev->convertFromQImage(image, 0); + for (qint32 x = r.x(); x < r.x() + r.width(); x += MASK_IMAGE_WIDTH) { + for (qint32 y = r.y(); y < r.y() + r.height(); y += MASK_IMAGE_HEIGHT) { - if (forceUpdateHiddenAreasOnly) { - m_projection->clear(); - } + image.fill(0); + tempPainter.translate(-x, -y); + tempPainter.setClipRect(QRect(x,y,MASK_IMAGE_WIDTH,MASK_IMAGE_HEIGHT)); - KisPainter::copyAreaOptimized(repaintRect.topLeft(), dev, m_projection, QRect(QPoint(), repaintRect.size())); + #ifdef DEBUG_REPAINT + QColor color = QColor(random() % 255, random() % 255, random() % 255); + maskPainter.fillRect(srcRect, color); + #endif - m_projection->purgeDefaultPixels(); + m_shapeManager->paint(tempPainter, *m_viewConverter, false); + + tempPainter.translate(x, y); + KoColorSpaceRegistry::instance()->rgb8() + ->convertPixelsTo(image.constBits(), dstData, m_projection->colorSpace(), + MASK_IMAGE_WIDTH * MASK_IMAGE_HEIGHT, + KoColorConversionTransformation::internalRenderingIntent(), + KoColorConversionTransformation::internalConversionFlags()); + + m_projection->writeBytes(dstData, x, y, MASK_IMAGE_WIDTH, MASK_IMAGE_HEIGHT); + } + } + delete[] dstData; + m_projection->purgeDefaultPixels(); m_parentLayer->setDirty(repaintRect); m_hasChangedWhileBeingInvisible |= !m_parentLayer->visible(true); } void KisShapeLayerCanvas::forceRepaint() { /** * WARNING! Although forceRepaint() may be called from different threads, it is * not entirely safe. If the user plays with shapes at the same time (vector tools are * not ported to strokes yet), the shapes my be accessed from two different places at * the same time, which will cause a crash. * * The only real solution to this is to port vector tools to strokes framework. */ if (hasPendingUpdates()) { m_asyncUpdateSignalCompressor.stop(); slotStartAsyncRepaint(); } } bool KisShapeLayerCanvas::hasPendingUpdates() const { return m_hasUpdateInCompressor || m_hasDirectSyncRepaintInitiated; } void KisShapeLayerCanvas::forceRepaintWithHiddenAreas() { KIS_SAFE_ASSERT_RECOVER_RETURN(m_parentLayer->image()); KIS_SAFE_ASSERT_RECOVER_RETURN(!m_isDestroying); - KIS_SAFE_ASSERT_RECOVER_RETURN(!m_updatesBlocked); { QMutexLocker locker(&m_dirtyRegionMutex); m_forceUpdateHiddenAreasOnly = true; } m_asyncUpdateSignalCompressor.stop(); slotStartAsyncRepaint(); } void KisShapeLayerCanvas::resetCache() { m_projection->clear(); QList shapes = m_shapeManager->shapes(); Q_FOREACH (const KoShape* shape, shapes) { shape->update(); } } void KisShapeLayerCanvas::rerenderAfterBeingInvisible() { KIS_SAFE_ASSERT_RECOVER_RETURN(m_parentLayer->visible(true)) m_hasChangedWhileBeingInvisible = false; resetCache(); } void KisShapeLayerCanvas::updateUpdateCompressorDelay() { if (m_cachedImageRect.width() * m_cachedImageRect.height() < 2480 * 3508) { // A4 300 DPI m_canvasUpdateCompressor.setDelay(25); } else if (m_cachedImageRect.width() * m_cachedImageRect.height() < 4961 * 7061) { // A4 600 DPI m_canvasUpdateCompressor.setDelay(100); } else { // Really big m_canvasUpdateCompressor.setDelay(500); } } diff --git a/libs/ui/flake/kis_shape_layer_canvas.h b/libs/ui/flake/kis_shape_layer_canvas.h index 0455eb8900..9799b81b61 100644 --- a/libs/ui/flake/kis_shape_layer_canvas.h +++ b/libs/ui/flake/kis_shape_layer_canvas.h @@ -1,145 +1,141 @@ /* * 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_SHAPE_LAYER_CANVAS_H #define KIS_SHAPE_LAYER_CANVAS_H #include #include #include #include #include "kis_thread_safe_signal_compressor.h" #include #include #include class KoShapeManager; class KoToolProxy; class KoViewConverter; class KUndo2Command; class QWidget; class KoUnit; class KisImageViewConverter; class KisShapeLayerCanvasBase : public KoCanvasBase { public: KisShapeLayerCanvasBase(KisShapeLayer *parent, KisImageWSP image); virtual void setImage(KisImageWSP image) = 0; void prepareForDestroying(); virtual void forceRepaint() = 0; virtual bool hasPendingUpdates() const = 0; virtual void forceRepaintWithHiddenAreas() { forceRepaint(); } bool hasChangedWhileBeingInvisible(); virtual void rerenderAfterBeingInvisible() = 0; virtual void resetCache() = 0; KoShapeManager *shapeManager() const override; KoViewConverter *viewConverter() const override; void gridSize(QPointF *offset, QSizeF *spacing) const override; bool snapToGrid() const override; void addCommand(KUndo2Command *command) override; KoSelectedShapesProxy *selectedShapesProxy() const override; KoToolProxy * toolProxy() const override; QWidget* canvasWidget() override; const QWidget* canvasWidget() const override; KoUnit unit() const override; void updateInputMethodInfo() override {} void setCursor(const QCursor &) override {} - void setUpdatesBlocked(bool value); - bool updatesBlocked() const; - protected: QScopedPointer m_viewConverter; QScopedPointer m_shapeManager; QScopedPointer m_selectedShapesProxy; bool m_hasChangedWhileBeingInvisible {false}; bool m_isDestroying {false}; - bool m_updatesBlocked {false}; }; /** * KisShapeLayerCanvas is a special canvas implementation that Krita * uses for non-krita shapes to request updates on. * * Do NOT give this canvas to tools or to the KoCanvasController, it's * not made for that. */ class KisShapeLayerCanvas : public KisShapeLayerCanvasBase { Q_OBJECT public: KisShapeLayerCanvas(KisShapeLayer *parent, KisImageWSP image); ~KisShapeLayerCanvas() override; /// This canvas won't render onto a widget, but a projection void setProjection(KisPaintDeviceSP projection) { m_projection = projection; } void setImage(KisImageWSP image) override; void updateCanvas(const QRectF& rc) override; void updateCanvas(const QVector ®ion); void forceRepaint() override; bool hasPendingUpdates() const override; void forceRepaintWithHiddenAreas() override; void resetCache() override; void rerenderAfterBeingInvisible() override; private Q_SLOTS: friend class KisRepaintShapeLayerLayerJob; void repaint(); void slotStartAsyncRepaint(); void slotStartDirectSyncRepaint(); void slotImageSizeChanged(); Q_SIGNALS: void forwardRepaint(); private: void updateUpdateCompressorDelay(); private: KisPaintDeviceSP m_projection; KisShapeLayer *m_parentLayer {0}; KisThreadSafeSignalCompressor m_canvasUpdateCompressor; KisThreadSafeSignalCompressor m_asyncUpdateSignalCompressor; volatile bool m_hasUpdateInCompressor = false; volatile bool m_hasDirectSyncRepaintInitiated = false; bool m_forceUpdateHiddenAreasOnly = false; QRegion m_dirtyRegion; QMutex m_dirtyRegionMutex; QRect m_cachedImageRect; KisImageWSP m_image; }; #endif diff --git a/libs/ui/forms/wdgdisplaysettings.ui b/libs/ui/forms/wdgdisplaysettings.ui index 92790a125b..40064fb1e8 100644 --- a/libs/ui/forms/wdgdisplaysettings.ui +++ b/libs/ui/forms/wdgdisplaysettings.ui @@ -1,639 +1,639 @@ WdgDisplaySettings 0 0 651 422 0 0 Display 15 0 Canvas Acceleration 0 0 Canvas &Graphics Acceleration true 0 0 0 Nearest Neighbour Bilinear Filtering Trilinear Filtering High Quality Filtering Current Renderer: 0 0 <html><head/><body><p>Try to disable vsync for Krita. This makes painting more responsive. Uncheck only when experiencing crashes with some GPU/driver combinations.</p></body></html> Disable vsync (needs restart) true 0 0 <html><head/><body><p>Use Texture Buffering. This can be faster on some GPU/Driver combinations (like Intel) or broken on some others (like AMD/Radeon).</p></body></html> Use texture buffer 0 0 OpenGL Warnings Qt::RichText true 0 0 Preferred Renderer (needs restart): Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Unknown 0 0 Scaling Mode: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Qt::Vertical 20 40 HDR Settings false Current Output Format: Current Surface Value Current Display Format Preferred Output Format: HDR Warning.................................. Display Format: Qt::Vertical 20 40 Grid Settings 15 7 Si&ze: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter intCheckSize px 256 32 Qt::Horizontal 40 20 Start showing at: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 % 9999.000000000000000 Qt::Horizontal 40 20 Opacity: 0 0 50 20 Pixel Grid: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 Selection Overlay: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 Transparency Checkerboard: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Canvas Border Color: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 Qt::Vertical 20 40 Miscellaneous 20 0 10 - Hide Window Scrollbars + Hide Canvas Scrollbars false Hide layer thumbnail popup Enable curve anti-aliasing Color channels in color false Enable selection outline anti-aliasing If checked, the checkers will move when scrolling the canvas. Determines whether the checks will stay put or whether they will scroll together with the canvas &Move checkers when scrolling true Qt::Vertical 20 341 Qt::Vertical 20 40 + + KisDoubleSliderSpinBox + QWidget +
kis_slider_spin_box.h
+ 1 +
KisIntParseSpinBox QSpinBox
kis_int_parse_spin_box.h
KisColorButton QPushButton
kis_color_button.h
- - KisDoubleSliderSpinBox - QWidget -
kis_slider_spin_box.h
- 1 -
diff --git a/libs/ui/forms/wdggeneralsettings.ui b/libs/ui/forms/wdggeneralsettings.ui index 713403e71d..79716b52e3 100644 --- a/libs/ui/forms/wdggeneralsettings.ui +++ b/libs/ui/forms/wdggeneralsettings.ui @@ -1,935 +1,935 @@ WdgGeneralSettings 0 0 552 468 0 0 552 295 0 Cursor 10 10 10 10 10 10 0 0 Cursor Shape: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 Outline Shape: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter While painting... 3 9 3 3 0 0 200 0 Show outline Use effective outline size Cursor Color: 48 25 Qt::Vertical 20 40 Window 0 0 Multiple Document Mode: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 1 Subwindows Tabs Background Image (overrides color): 200 0 QFrame::StyledPanel QFrame::Sunken ... 0 0 Clear Window Background: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 Qt::Vertical 0 18 General: 0 0 Don't show contents when moving sub-windows Show on-canvas popup messages Enable Hi-DPI support (Hi-DPI) Enable fractional scale factor Allow only one instance of Krita Qt::Vertical 20 40 Tools Tool Options Location (needs restart) In Doc&ker I&n Toolbar true Brush Flow Mode (needs restart): Creamy (Krita 4.2+) Hard (Krita 4.1 and earlier versions) Switch Control/Alt Selection Modifiers Enable Touch Painting Activate transform tool after pasting Enable Touch Rotation Kinetic Scrolling (needs restart) true true Sensitivity: - Hide Scrollbars + Hide docker scrollbars if kinetic scrolling is enabled (needs restart) false Qt::Vertical 250 71 File Handling Enable Autosaving true Autosave Interval: 0 0 75 0 min Every 1 1440 5 15 Unnamed autosave files are hidden by default true Create a Backup File on Saving true Backup File Location Same Folder as Original File User Folder Temporary File Location Backup File Suffix: ~ 10 Number of Backup Files Kept: 1 1 Kra File Compression Compress .kra files more (slows loading/saving) <html><head/><body><p>Only use this option for <span style=" font-weight:600;">very</span> large files: larger than 4 GiB on disk.</p></body></html> Use Zip64 (for very large files: cannot be opened in versions of Krita older than 4.2.0) Qt::Vertical 20 40 Miscellaneous 0 0 When Krita starts: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 Save session when Krita closes On importing images as layers, convert to the image colorspace 0 0 Only applies to new or newly opened images. Undo stack size: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 75 0 Only applies to new or newly opened images. 0 1000 5 30 0 0 Number of Palette Presets: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 75 0 10 30 Show root layer Enable Logging for bug reports true Warning: if you enable this setting and the file dialogs do weird stuff, do not report a bug. Enable native file dialogs (warning: may not work correctly on some systems) Maximum brush size: 0 0 The maximum diameter of a brush in pixels. px 100 10000 1000 (Needs restart) Qt::Vertical 504 13 KisIntParseSpinBox QSpinBox
kis_int_parse_spin_box.h
KisColorButton QPushButton
kis_color_button.h
KisSliderSpinBox QWidget
kis_slider_spin_box.h
1
diff --git a/libs/ui/input/kis_tool_invocation_action.cpp b/libs/ui/input/kis_tool_invocation_action.cpp index 1fbce7c7ea..be60fbc5f7 100644 --- a/libs/ui/input/kis_tool_invocation_action.cpp +++ b/libs/ui/input/kis_tool_invocation_action.cpp @@ -1,205 +1,205 @@ /* This file is part of the KDE project * Copyright (C) 2012 Arjen Hiemstra * * 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_tool_invocation_action.h" #include #include #include #include #include #include #include "kis_tool.h" #include "kis_input_manager.h" #include "kis_image.h" class KisToolInvocationAction::Private { public: Private() : active(false), lineToolActivated(false) { } bool active; bool lineToolActivated; QPointer activatedToolProxy; QPointer runningToolProxy; }; KisToolInvocationAction::KisToolInvocationAction() : KisAbstractInputAction("Tool Invocation") , d(new Private) { setName(i18n("Tool Invocation")); setDescription(i18n("The Tool Invocation action invokes the current tool, for example, using the brush tool, it will start painting.")); QHash indexes; indexes.insert(i18n("Activate"), ActivateShortcut); indexes.insert(i18n("Confirm"), ConfirmShortcut); indexes.insert(i18n("Cancel"), CancelShortcut); indexes.insert(i18n("Activate Line Tool"), LineToolShortcut); setShortcutIndexes(indexes); } KisToolInvocationAction::~KisToolInvocationAction() { delete d; } void KisToolInvocationAction::activate(int shortcut) { Q_UNUSED(shortcut); if (!inputManager()) return; if (shortcut == LineToolShortcut) { KoToolManager::instance()->switchToolTemporaryRequested("KritaShape/KisToolLine"); d->lineToolActivated = true; } d->activatedToolProxy = inputManager()->toolProxy(); d->activatedToolProxy->activateToolAction(KisTool::Primary); } void KisToolInvocationAction::deactivate(int shortcut) { Q_UNUSED(shortcut); if (!inputManager()) return; /** - * Activate call might ave come before actual input manager or tool proxy - * was attached. So we may end up wil null activatedToolProxy. + * Activate call might have come before actual input manager or tool proxy + * was attached. So we may end up with null activatedToolProxy. */ if (d->activatedToolProxy) { d->activatedToolProxy->deactivateToolAction(KisTool::Primary); d->activatedToolProxy.clear(); } if (shortcut == LineToolShortcut && d->lineToolActivated) { d->lineToolActivated = false; KoToolManager::instance()->switchBackRequested(); } } int KisToolInvocationAction::priority() const { return 0; } bool KisToolInvocationAction::canIgnoreModifiers() const { return true; } void KisToolInvocationAction::begin(int shortcut, QEvent *event) { if (shortcut == ActivateShortcut || shortcut == LineToolShortcut) { d->runningToolProxy = inputManager()->toolProxy(); d->active = d->runningToolProxy->forwardEvent( KisToolProxy::BEGIN, KisTool::Primary, event, event); } else if (shortcut == ConfirmShortcut) { QKeyEvent pressEvent(QEvent::KeyPress, Qt::Key_Return, 0); inputManager()->toolProxy()->keyPressEvent(&pressEvent); QKeyEvent releaseEvent(QEvent::KeyRelease, Qt::Key_Return, 0); inputManager()->toolProxy()->keyReleaseEvent(&releaseEvent); /** * All the tools now have a KisTool::requestStrokeEnd() method, * so they should use this instead of direct filtering Enter key * press. Until all the tools support it, we just duplicate the * key event and the method call */ inputManager()->canvas()->image()->requestStrokeEnd(); /** * Some tools would like to distinguish automated requestStrokeEnd() * calls from explicit user actions. Just let them do it! * * Please note that this call should happen **after** * requestStrokeEnd(). Some of the tools will switch to another * tool on this request, and this (next) tool does not expect to * get requestStrokeEnd() right after switching in. */ inputManager()->toolProxy()->explicitUserStrokeEndRequest(); } else if (shortcut == CancelShortcut) { /** * The tools now have a KisTool::requestStrokeCancellation() method, * so just request it. */ inputManager()->canvas()->image()->requestStrokeCancellation(); } } void KisToolInvocationAction::end(QEvent *event) { if (d->active) { // It might happen that the action is still running, while the // canvas has been removed, which kills the toolProxy. KIS_SAFE_ASSERT_RECOVER_NOOP(d->runningToolProxy); if (d->runningToolProxy) { d->runningToolProxy-> forwardEvent(KisToolProxy::END, KisTool::Primary, event, event); d->runningToolProxy.clear(); } d->active = false; } KisAbstractInputAction::end(event); } void KisToolInvocationAction::inputEvent(QEvent* event) { if (!d->active) return; if (!d->runningToolProxy) return; d->runningToolProxy-> forwardEvent(KisToolProxy::CONTINUE, KisTool::Primary, event, event); } void KisToolInvocationAction::processUnhandledEvent(QEvent* event) { bool savedState = d->active; KisToolProxy *savedToolProxy = d->runningToolProxy; if (!d->runningToolProxy) { d->runningToolProxy = inputManager()->toolProxy(); } d->active = true; inputEvent(event); d->active = savedState; d->runningToolProxy = savedToolProxy; } bool KisToolInvocationAction::supportsHiResInputEvents() const { return inputManager()->toolProxy()->primaryActionSupportsHiResEvents(); } bool KisToolInvocationAction::isShortcutRequired(int shortcut) const { //These really all are pretty important for basic user interaction. Q_UNUSED(shortcut) return true; } diff --git a/libs/ui/kis_composite_ops_model.cc b/libs/ui/kis_composite_ops_model.cc index bccf5534af..49798d091d 100644 --- a/libs/ui/kis_composite_ops_model.cc +++ b/libs/ui/kis_composite_ops_model.cc @@ -1,153 +1,168 @@ /* * Copyright (c) 2009 Cyrille Berger * Copyright (c) 2011 Silvio Heinrich * * 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_composite_ops_model.h" #include #include #include #include "kis_debug.h" #include "kis_config.h" KoID KisCompositeOpListModel::favoriteCategory() { static KoID category("favorites", ki18n("Favorites")); return category; } void KisCompositeOpListModel::initialize() { QMap ops = KoCompositeOpRegistry::instance().getCompositeOps(); QMapIterator it(ops); while (it.hasNext()) { KoID op = *it.next(); KoID category = it.key(); BaseKoIDCategorizedListModel::DataItem *item = categoriesMapper()->addEntry(category.name(), op); item->setCheckable(true); } BaseKoIDCategorizedListModel::DataItem *item = categoriesMapper()->addCategory(favoriteCategory().name()); item->setExpanded(true); readFavoriteCompositeOpsFromConfig(); } +void KisCompositeOpListModel::initializeForLayerStyles() +{ + QMap ops = KoCompositeOpRegistry::instance().getLayerStylesCompositeOps(); + QMapIterator it(ops); + + while (it.hasNext()) { + KoID op = *it.next(); + KoID category = it.key(); + BaseKoIDCategorizedListModel::DataItem *item = categoriesMapper()->addEntry(category.name(), op); + item->setCheckable(false); + } + + categoriesMapper()->expandAllCategories(); +} + KisCompositeOpListModel* KisCompositeOpListModel::sharedInstance() { static KisCompositeOpListModel *model = 0; if (!model) { model = new KisCompositeOpListModel(); model->initialize(); } return model; } void KisCompositeOpListModel::validate(const KoColorSpace *cs) { for (int i = 0, size = categoriesMapper()->rowCount(); i < size; i++) { DataItem *item = categoriesMapper()->itemFromRow(i); if (!item->isCategory()) { bool value = KoCompositeOpRegistry::instance().colorSpaceHasCompositeOp(cs, *item->data()); item->setEnabled(value); } } } bool KisCompositeOpListModel::setData(const QModelIndex& idx, const QVariant& value, int role) { if (!idx.isValid()) return false; bool result = BaseKoIDCategorizedListModel::setData(idx, value, role); DataItem *item = categoriesMapper()->itemFromRow(idx.row()); Q_ASSERT(item); if(role == Qt::CheckStateRole) { if (item->isChecked()) { addFavoriteEntry(*item->data()); } else { removeFavoriteEntry(*item->data()); } writeFavoriteCompositeOpsToConfig(); } return result; } QVariant KisCompositeOpListModel::data(const QModelIndex& idx, int role) const { if (!idx.isValid()) return QVariant(); if(role == Qt::DecorationRole) { DataItem *item = categoriesMapper()->itemFromRow(idx.row()); Q_ASSERT(item); if (!item->isCategory() && !item->isEnabled()) { return KisIconUtils::loadIcon("dialog-warning"); } } return BaseKoIDCategorizedListModel::data(idx, role); } void KisCompositeOpListModel::addFavoriteEntry(const KoID &entry) { DataItem *item = categoriesMapper()->addEntry(favoriteCategory().name(), entry); item->setCheckable(false); } void KisCompositeOpListModel::removeFavoriteEntry(const KoID &entry) { categoriesMapper()->removeEntry(favoriteCategory().name(), entry); } void KisCompositeOpListModel::readFavoriteCompositeOpsFromConfig() { KisConfig config(true); Q_FOREACH (const QString &op, config.favoriteCompositeOps()) { KoID entry = KoCompositeOpRegistry::instance().getKoID(op); DataItem *item = categoriesMapper()->fetchOneEntry(entry); if (item) { item->setChecked(true); } addFavoriteEntry(entry); } } void KisCompositeOpListModel::writeFavoriteCompositeOpsToConfig() const { QStringList list; QVector filteredItems = categoriesMapper()->itemsForCategory(favoriteCategory().name()); Q_FOREACH (DataItem *item, filteredItems) { list.append(item->data()->id()); } KisConfig config(false); config.setFavoriteCompositeOps(list); } diff --git a/libs/ui/kis_composite_ops_model.h b/libs/ui/kis_composite_ops_model.h index 09a8106d26..d1df4cdb8e 100644 --- a/libs/ui/kis_composite_ops_model.h +++ b/libs/ui/kis_composite_ops_model.h @@ -1,87 +1,93 @@ /* * Copyright (c) 2009 Cyrille Berger * Copyright (c) 2011 Silvio Heinrich * * 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_COMPOSITE_OPS_MODEL_H_ #define _KIS_COMPOSITE_OPS_MODEL_H_ #include #include "kis_categorized_list_model.h" class KoColorSpace; struct KoIDToQStringConverter { QString operator() (const KoID &id) { return id.name(); } }; typedef KisCategorizedListModel BaseKoIDCategorizedListModel; class KRITAUI_EXPORT KisCompositeOpListModel: public BaseKoIDCategorizedListModel { public: static KisCompositeOpListModel* sharedInstance(); virtual QString categoryToString(const KoID& val) const { return val.name(); } virtual QString entryToString (const KoID& val) const { return val.name(); } bool setData (const QModelIndex& idx, const QVariant& value, int role=Qt::EditRole) override; QVariant data (const QModelIndex& idx, int role=Qt::DisplayRole) const override; void validate(const KoColorSpace *cs); void readFavoriteCompositeOpsFromConfig(); void writeFavoriteCompositeOpsToConfig() const; static KoID favoriteCategory(); void initialize(); + void initializeForLayerStyles(); private: void addFavoriteEntry(const KoID &entry); void removeFavoriteEntry(const KoID &entry); }; /** * @brief The KisSortedCompositeOpListModel class provides a model for the composite op combobox. * * It intentionally does NOT use the shared instance of KisCompositeOpListModel because it is * perfect valid for two composite comboboboxes to show a different set of valid composite ops. */ class KRITAUI_EXPORT KisSortedCompositeOpListModel : public KisSortedCategorizedListModel { public: - KisSortedCompositeOpListModel(QObject *parent) + KisSortedCompositeOpListModel(bool limitToLayerStyles, QObject *parent) : KisSortedCategorizedListModel(parent) { - m_model.initialize(); + if (limitToLayerStyles) { + m_model.initializeForLayerStyles(); + } else { + m_model.initialize(); + } + initializeModel(&m_model); } void validate(const KoColorSpace *cs) { m_model.validate(cs); } protected: bool lessThan(const QModelIndex &left, const QModelIndex &right) const override { return lessThanPriority(left, right, KisCompositeOpListModel::favoriteCategory().name()); } private: KisCompositeOpListModel m_model; }; #endif diff --git a/libs/ui/kis_config.cc b/libs/ui/kis_config.cc index 9ec999aac5..b001c76be1 100644 --- a/libs/ui/kis_config.cc +++ b/libs/ui/kis_config.cc @@ -1,2201 +1,2199 @@ /* * 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. */ #include "kis_config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_canvas_resource_provider.h" #include "kis_config_notifier.h" #include "kis_snap_config.h" #include #include #include #include #include #ifdef Q_OS_WIN #include "config_use_qt_tablet_windows.h" #endif KisConfig::KisConfig(bool readOnly) : m_cfg( KSharedConfig::openConfig()->group("")) , m_readOnly(readOnly) { if (!readOnly) { KIS_SAFE_ASSERT_RECOVER_RETURN(qApp && qApp->thread() == QThread::currentThread()); } } KisConfig::~KisConfig() { if (m_readOnly) return; if (qApp && qApp->thread() != QThread::currentThread()) { dbgKrita << "WARNING: KisConfig: requested config synchronization from nonGUI thread! Called from:" << kisBacktrace(); return; } m_cfg.sync(); } void KisConfig::logImportantSettings() const { - - KisUsageLogger::writeSectionHeader(); - KisUsageLogger::log("Current Settings\n"); - KisUsageLogger::write(QString("Current Swap Location: %1").arg(KisImageConfig(true).swapDir())); - KisUsageLogger::write(QString("Undo Enabled: %1").arg(undoEnabled())); - KisUsageLogger::write(QString("Undo Stack Limit: %1").arg(undoStackLimit())); - KisUsageLogger::write(QString("Use OpenGL: %1").arg(useOpenGL())); - KisUsageLogger::write(QString("Use OpenGL Texture Buffer: %1").arg(useOpenGLTextureBuffer())); - KisUsageLogger::write(QString("Use AMD Vectorization Workaround: %1").arg(enableAmdVectorizationWorkaround())); - KisUsageLogger::write(QString("Canvas State: %1").arg(canvasState())); - KisUsageLogger::write(QString("Autosave Interval: %1").arg(autoSaveInterval())); - KisUsageLogger::write(QString("Use Backup Files: %1").arg(backupFile())); - KisUsageLogger::write(QString("Number of Backups Kept: %1").arg(m_cfg.readEntry("numberofbackupfiles", 1))); - KisUsageLogger::write(QString("Backup File Suffix: %1").arg(m_cfg.readEntry("backupfilesuffix", "~"))); + KisUsageLogger::write("Current Settings\n"); + KisUsageLogger::write(QString("\tCurrent Swap Location: %1").arg(KisImageConfig(true).swapDir())); + KisUsageLogger::write(QString("\tUndo Enabled: %1").arg(undoEnabled())); + KisUsageLogger::write(QString("\tUndo Stack Limit: %1").arg(undoStackLimit())); + KisUsageLogger::write(QString("\tUse OpenGL: %1").arg(useOpenGL())); + KisUsageLogger::write(QString("\tUse OpenGL Texture Buffer: %1").arg(useOpenGLTextureBuffer())); + KisUsageLogger::write(QString("\tUse AMD Vectorization Workaround: %1").arg(enableAmdVectorizationWorkaround())); + KisUsageLogger::write(QString("\tCanvas State: %1").arg(canvasState())); + KisUsageLogger::write(QString("\tAutosave Interval: %1").arg(autoSaveInterval())); + KisUsageLogger::write(QString("\tUse Backup Files: %1").arg(backupFile())); + KisUsageLogger::write(QString("\tNumber of Backups Kept: %1").arg(m_cfg.readEntry("numberofbackupfiles", 1))); + KisUsageLogger::write(QString("\tBackup File Suffix: %1").arg(m_cfg.readEntry("backupfilesuffix", "~"))); QString backupDir; switch(m_cfg.readEntry("backupfilelocation", 0)) { case 1: backupDir = QStandardPaths::writableLocation(QStandardPaths::HomeLocation); break; case 2: backupDir = QStandardPaths::writableLocation(QStandardPaths::TempLocation); break; default: // Do nothing: the empty string is user file location backupDir = "Same Folder as the File"; } - KisUsageLogger::write(QString("Backup Location: %1").arg(backupDir)); + KisUsageLogger::write(QString("\tBackup Location: %1").arg(backupDir)); - KisUsageLogger::write(QString("Use Win8 Pointer Input: %1").arg(useWin8PointerInput())); - KisUsageLogger::write(QString("Use RightMiddleTabletButton Workaround: %1").arg(useRightMiddleTabletButtonWorkaround())); - KisUsageLogger::write(QString("Levels of Detail Enabled: %1").arg(levelOfDetailEnabled())); - KisUsageLogger::write(QString("Use Zip64: %1").arg(useZip64())); + KisUsageLogger::write(QString("\tUse Win8 Pointer Input: %1").arg(useWin8PointerInput())); + KisUsageLogger::write(QString("\tUse RightMiddleTabletButton Workaround: %1").arg(useRightMiddleTabletButtonWorkaround())); + KisUsageLogger::write(QString("\tLevels of Detail Enabled: %1").arg(levelOfDetailEnabled())); + KisUsageLogger::write(QString("\tUse Zip64: %1").arg(useZip64())); KisUsageLogger::write("\n"); } bool KisConfig::disableTouchOnCanvas(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("disableTouchOnCanvas", false)); } void KisConfig::setDisableTouchOnCanvas(bool value) const { m_cfg.writeEntry("disableTouchOnCanvas", value); } bool KisConfig::disableTouchRotation(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("disableTouchRotation", false)); } void KisConfig::setDisableTouchRotation(bool value) const { m_cfg.writeEntry("disableTouchRotation", value); } bool KisConfig::useProjections(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("useProjections", true)); } void KisConfig::setUseProjections(bool useProj) const { m_cfg.writeEntry("useProjections", useProj); } bool KisConfig::undoEnabled(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("undoEnabled", true)); } void KisConfig::setUndoEnabled(bool undo) const { m_cfg.writeEntry("undoEnabled", undo); } int KisConfig::undoStackLimit(bool defaultValue) const { return (defaultValue ? 30 : m_cfg.readEntry("undoStackLimit", 30)); } void KisConfig::setUndoStackLimit(int limit) const { m_cfg.writeEntry("undoStackLimit", limit); } bool KisConfig::useCumulativeUndoRedo(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("useCumulativeUndoRedo",false)); } void KisConfig::setCumulativeUndoRedo(bool value) { m_cfg.writeEntry("useCumulativeUndoRedo", value); } qreal KisConfig::stackT1(bool defaultValue) const { return (defaultValue ? 5 : m_cfg.readEntry("stackT1",5)); } void KisConfig::setStackT1(int T1) { m_cfg.writeEntry("stackT1", T1); } qreal KisConfig::stackT2(bool defaultValue) const { return (defaultValue ? 1 : m_cfg.readEntry("stackT2",1)); } void KisConfig::setStackT2(int T2) { m_cfg.writeEntry("stackT2", T2); } int KisConfig::stackN(bool defaultValue) const { return (defaultValue ? 5 : m_cfg.readEntry("stackN",5)); } void KisConfig::setStackN(int N) { m_cfg.writeEntry("stackN", N); } qint32 KisConfig::defImageWidth(bool defaultValue) const { return (defaultValue ? 1600 : m_cfg.readEntry("imageWidthDef", 1600)); } qint32 KisConfig::defImageHeight(bool defaultValue) const { return (defaultValue ? 1200 : m_cfg.readEntry("imageHeightDef", 1200)); } qreal KisConfig::defImageResolution(bool defaultValue) const { return (defaultValue ? 100.0 : m_cfg.readEntry("imageResolutionDef", 100.0)) / 72.0; } QString KisConfig::defColorModel(bool defaultValue) const { return (defaultValue ? KoColorSpaceRegistry::instance()->rgb8()->colorModelId().id() : m_cfg.readEntry("colorModelDef", KoColorSpaceRegistry::instance()->rgb8()->colorModelId().id())); } void KisConfig::defColorModel(const QString & model) const { m_cfg.writeEntry("colorModelDef", model); } QString KisConfig::defaultColorDepth(bool defaultValue) const { return (defaultValue ? KoColorSpaceRegistry::instance()->rgb8()->colorDepthId().id() : m_cfg.readEntry("colorDepthDef", KoColorSpaceRegistry::instance()->rgb8()->colorDepthId().id())); } void KisConfig::setDefaultColorDepth(const QString & depth) const { m_cfg.writeEntry("colorDepthDef", depth); } QString KisConfig::defColorProfile(bool defaultValue) const { return (defaultValue ? KoColorSpaceRegistry::instance()->rgb8()->profile()->name() : m_cfg.readEntry("colorProfileDef", KoColorSpaceRegistry::instance()->rgb8()->profile()->name())); } void KisConfig::defColorProfile(const QString & profile) const { m_cfg.writeEntry("colorProfileDef", profile); } void KisConfig::defImageWidth(qint32 width) const { m_cfg.writeEntry("imageWidthDef", width); } void KisConfig::defImageHeight(qint32 height) const { m_cfg.writeEntry("imageHeightDef", height); } void KisConfig::defImageResolution(qreal res) const { m_cfg.writeEntry("imageResolutionDef", res*72.0); } int KisConfig::preferredVectorImportResolutionPPI(bool defaultValue) const { return defaultValue ? 100.0 : m_cfg.readEntry("preferredVectorImportResolution", 100.0); } void KisConfig::setPreferredVectorImportResolutionPPI(int value) const { m_cfg.writeEntry("preferredVectorImportResolution", value); } void cleanOldCursorStyleKeys(KConfigGroup &cfg) { if (cfg.hasKey("newCursorStyle") && cfg.hasKey("newOutlineStyle")) { cfg.deleteEntry("cursorStyleDef"); } } CursorStyle KisConfig::newCursorStyle(bool defaultValue) const { if (defaultValue) { return CURSOR_STYLE_NO_CURSOR; } int style = m_cfg.readEntry("newCursorStyle", int(-1)); if (style < 0) { // old style format style = m_cfg.readEntry("cursorStyleDef", int(OLD_CURSOR_STYLE_OUTLINE)); switch (style) { case OLD_CURSOR_STYLE_TOOLICON: style = CURSOR_STYLE_TOOLICON; break; case OLD_CURSOR_STYLE_CROSSHAIR: case OLD_CURSOR_STYLE_OUTLINE_CENTER_CROSS: style = CURSOR_STYLE_CROSSHAIR; break; case OLD_CURSOR_STYLE_POINTER: style = CURSOR_STYLE_POINTER; break; case OLD_CURSOR_STYLE_OUTLINE: case OLD_CURSOR_STYLE_NO_CURSOR: style = CURSOR_STYLE_NO_CURSOR; break; case OLD_CURSOR_STYLE_SMALL_ROUND: case OLD_CURSOR_STYLE_OUTLINE_CENTER_DOT: style = CURSOR_STYLE_SMALL_ROUND; break; case OLD_CURSOR_STYLE_TRIANGLE_RIGHTHANDED: case OLD_CURSOR_STYLE_OUTLINE_TRIANGLE_RIGHTHANDED: style = CURSOR_STYLE_TRIANGLE_RIGHTHANDED; break; case OLD_CURSOR_STYLE_TRIANGLE_LEFTHANDED: case OLD_CURSOR_STYLE_OUTLINE_TRIANGLE_LEFTHANDED: style = CURSOR_STYLE_TRIANGLE_LEFTHANDED; break; default: style = -1; } } cleanOldCursorStyleKeys(m_cfg); // compatibility with future versions if (style < 0 || style >= N_CURSOR_STYLE_SIZE) { style = CURSOR_STYLE_NO_CURSOR; } return (CursorStyle) style; } void KisConfig::setNewCursorStyle(CursorStyle style) { m_cfg.writeEntry("newCursorStyle", (int)style); } QColor KisConfig::getCursorMainColor(bool defaultValue) const { QColor col; col.setRgbF(0.501961, 1.0, 0.501961); return (defaultValue ? col : m_cfg.readEntry("cursorMaincColor", col)); } void KisConfig::setCursorMainColor(const QColor &v) const { m_cfg.writeEntry("cursorMaincColor", v); } OutlineStyle KisConfig::newOutlineStyle(bool defaultValue) const { if (defaultValue) { return OUTLINE_FULL; } int style = m_cfg.readEntry("newOutlineStyle", int(-1)); if (style < 0) { // old style format style = m_cfg.readEntry("cursorStyleDef", int(OLD_CURSOR_STYLE_OUTLINE)); switch (style) { case OLD_CURSOR_STYLE_TOOLICON: case OLD_CURSOR_STYLE_CROSSHAIR: case OLD_CURSOR_STYLE_POINTER: case OLD_CURSOR_STYLE_NO_CURSOR: case OLD_CURSOR_STYLE_SMALL_ROUND: case OLD_CURSOR_STYLE_TRIANGLE_RIGHTHANDED: case OLD_CURSOR_STYLE_TRIANGLE_LEFTHANDED: style = OUTLINE_NONE; break; case OLD_CURSOR_STYLE_OUTLINE: case OLD_CURSOR_STYLE_OUTLINE_CENTER_DOT: case OLD_CURSOR_STYLE_OUTLINE_CENTER_CROSS: case OLD_CURSOR_STYLE_OUTLINE_TRIANGLE_RIGHTHANDED: case OLD_CURSOR_STYLE_OUTLINE_TRIANGLE_LEFTHANDED: style = OUTLINE_FULL; break; default: style = -1; } } cleanOldCursorStyleKeys(m_cfg); // compatibility with future versions if (style < 0 || style >= N_OUTLINE_STYLE_SIZE) { style = OUTLINE_FULL; } return (OutlineStyle) style; } void KisConfig::setNewOutlineStyle(OutlineStyle style) { m_cfg.writeEntry("newOutlineStyle", (int)style); } QRect KisConfig::colorPreviewRect() const { return m_cfg.readEntry("colorPreviewRect", QVariant(QRect(32, 32, 48, 48))).toRect(); } void KisConfig::setColorPreviewRect(const QRect &rect) { m_cfg.writeEntry("colorPreviewRect", QVariant(rect)); } bool KisConfig::useDirtyPresets(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("useDirtyPresets", true)); } void KisConfig::setUseDirtyPresets(bool value) { m_cfg.writeEntry("useDirtyPresets",value); KisConfigNotifier::instance()->notifyConfigChanged(); } bool KisConfig::useEraserBrushSize(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("useEraserBrushSize", false)); } void KisConfig::setUseEraserBrushSize(bool value) { m_cfg.writeEntry("useEraserBrushSize",value); KisConfigNotifier::instance()->notifyConfigChanged(); } bool KisConfig::useEraserBrushOpacity(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("useEraserBrushOpacity",false)); } void KisConfig::setUseEraserBrushOpacity(bool value) { m_cfg.writeEntry("useEraserBrushOpacity",value); KisConfigNotifier::instance()->notifyConfigChanged(); } QString KisConfig::getMDIBackgroundColor(bool defaultValue) const { QColor col(77, 77, 77); KoColor kol(KoColorSpaceRegistry::instance()->rgb8()); kol.fromQColor(col); QString xml = kol.toXML(); return (defaultValue ? xml : m_cfg.readEntry("mdiBackgroundColorXML", xml)); } void KisConfig::setMDIBackgroundColor(const QString &v) const { m_cfg.writeEntry("mdiBackgroundColorXML", v); } QString KisConfig::getMDIBackgroundImage(bool defaultValue) const { return (defaultValue ? "" : m_cfg.readEntry("mdiBackgroundImage", "")); } void KisConfig::setMDIBackgroundImage(const QString &filename) const { m_cfg.writeEntry("mdiBackgroundImage", filename); } QString KisConfig::monitorProfile(int screen) const { // Note: keep this in sync with the default profile for the RGB colorspaces! QString profile = m_cfg.readEntry("monitorProfile" + QString(screen == 0 ? "": QString("_%1").arg(screen)), "sRGB-elle-V2-srgbtrc.icc"); //dbgKrita << "KisConfig::monitorProfile()" << profile; return profile; } QString KisConfig::monitorForScreen(int screen, const QString &defaultMonitor, bool defaultValue) const { return (defaultValue ? defaultMonitor : m_cfg.readEntry(QString("monitor_for_screen_%1").arg(screen), defaultMonitor)); } void KisConfig::setMonitorForScreen(int screen, const QString& monitor) { m_cfg.writeEntry(QString("monitor_for_screen_%1").arg(screen), monitor); } void KisConfig::setMonitorProfile(int screen, const QString & monitorProfile, bool override) const { m_cfg.writeEntry("monitorProfile/OverrideX11", override); m_cfg.writeEntry("monitorProfile" + QString(screen == 0 ? "": QString("_%1").arg(screen)), monitorProfile); } const KoColorProfile *KisConfig::getScreenProfile(int screen) { if (screen < 0) return 0; KisConfig cfg(true); QString monitorId; if (KisColorManager::instance()->devices().size() > screen) { monitorId = cfg.monitorForScreen(screen, KisColorManager::instance()->devices()[screen]); } //dbgKrita << "getScreenProfile(). Screen" << screen << "monitor id" << monitorId; if (monitorId.isEmpty()) { return 0; } QByteArray bytes = KisColorManager::instance()->displayProfile(monitorId); //dbgKrita << "\tgetScreenProfile()" << bytes.size(); const KoColorProfile * profile = 0; if (bytes.length() > 0) { profile = KoColorSpaceRegistry::instance()->createColorProfile(RGBAColorModelID.id(), Integer8BitsColorDepthID.id(), bytes); //dbgKrita << "\tKisConfig::getScreenProfile for screen" << screen << profile->name(); } return profile; } const KoColorProfile *KisConfig::displayProfile(int screen) const { if (screen < 0) return 0; // if the user plays with the settings, they can override the display profile, in which case // we don't want the system setting. bool override = useSystemMonitorProfile(); //dbgKrita << "KisConfig::displayProfile(). Override X11:" << override; const KoColorProfile *profile = 0; if (override) { //dbgKrita << "\tGoing to get the screen profile"; profile = KisConfig::getScreenProfile(screen); } // if it fails. check the configuration if (!profile || !profile->isSuitableForDisplay()) { //dbgKrita << "\tGoing to get the monitor profile"; QString monitorProfileName = monitorProfile(screen); //dbgKrita << "\t\tmonitorProfileName:" << monitorProfileName; if (!monitorProfileName.isEmpty()) { profile = KoColorSpaceRegistry::instance()->profileByName(monitorProfileName); } if (profile) { //dbgKrita << "\t\tsuitable for display" << profile->isSuitableForDisplay(); } else { //dbgKrita << "\t\tstill no profile"; } } // if we still don't have a profile, or the profile isn't suitable for display, // we need to get a last-resort profile. the built-in sRGB is a good choice then. if (!profile || !profile->isSuitableForDisplay()) { //dbgKrita << "\tnothing worked, going to get sRGB built-in"; profile = KoColorSpaceRegistry::instance()->profileByName("sRGB Built-in"); } if (profile) { //dbgKrita << "\tKisConfig::displayProfile for screen" << screen << "is" << profile->name(); } else { //dbgKrita << "\tCouldn't get a display profile at all"; } return profile; } QString KisConfig::workingColorSpace(bool defaultValue) const { return (defaultValue ? "RGBA" : m_cfg.readEntry("workingColorSpace", "RGBA")); } void KisConfig::setWorkingColorSpace(const QString & workingColorSpace) const { m_cfg.writeEntry("workingColorSpace", workingColorSpace); } QString KisConfig::printerColorSpace(bool /*defaultValue*/) const { //TODO currently only rgb8 is supported //return (defaultValue ? "RGBA" : m_cfg.readEntry("printerColorSpace", "RGBA")); return QString("RGBA"); } void KisConfig::setPrinterColorSpace(const QString & printerColorSpace) const { m_cfg.writeEntry("printerColorSpace", printerColorSpace); } QString KisConfig::printerProfile(bool defaultValue) const { return (defaultValue ? "" : m_cfg.readEntry("printerProfile", "")); } void KisConfig::setPrinterProfile(const QString & printerProfile) const { m_cfg.writeEntry("printerProfile", printerProfile); } bool KisConfig::useBlackPointCompensation(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("useBlackPointCompensation", true)); } void KisConfig::setUseBlackPointCompensation(bool useBlackPointCompensation) const { m_cfg.writeEntry("useBlackPointCompensation", useBlackPointCompensation); } bool KisConfig::allowLCMSOptimization(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("allowLCMSOptimization", true)); } void KisConfig::setAllowLCMSOptimization(bool allowLCMSOptimization) { m_cfg.writeEntry("allowLCMSOptimization", allowLCMSOptimization); } bool KisConfig::forcePaletteColors(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("colorsettings/forcepalettecolors", false)); } void KisConfig::setForcePaletteColors(bool forcePaletteColors) { m_cfg.writeEntry("colorsettings/forcepalettecolors", forcePaletteColors); } bool KisConfig::showRulers(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("showrulers", false)); } void KisConfig::setShowRulers(bool rulers) const { m_cfg.writeEntry("showrulers", rulers); } bool KisConfig::forceShowSaveMessages(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("forceShowSaveMessages", false)); } void KisConfig::setForceShowSaveMessages(bool value) const { m_cfg.writeEntry("forceShowSaveMessages", value); } bool KisConfig::forceShowAutosaveMessages(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("forceShowAutosaveMessages", false)); } void KisConfig::setForceShowAutosaveMessages(bool value) const { m_cfg.writeEntry("forceShowAutosaveMessages", value); } bool KisConfig::rulersTrackMouse(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("rulersTrackMouse", true)); } void KisConfig::setRulersTrackMouse(bool value) const { m_cfg.writeEntry("rulersTrackMouse", value); } qint32 KisConfig::pasteBehaviour(bool defaultValue) const { return (defaultValue ? 2 : m_cfg.readEntry("pasteBehaviour", 2)); } void KisConfig::setPasteBehaviour(qint32 renderIntent) const { m_cfg.writeEntry("pasteBehaviour", renderIntent); } qint32 KisConfig::monitorRenderIntent(bool defaultValue) const { qint32 intent = m_cfg.readEntry("renderIntent", INTENT_PERCEPTUAL); if (intent > 3) intent = 3; if (intent < 0) intent = 0; return (defaultValue ? INTENT_PERCEPTUAL : intent); } void KisConfig::setRenderIntent(qint32 renderIntent) const { if (renderIntent > 3) renderIntent = 3; if (renderIntent < 0) renderIntent = 0; m_cfg.writeEntry("renderIntent", renderIntent); } bool KisConfig::useOpenGL(bool defaultValue) const { if (defaultValue) { return true; } const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); return kritarc.value("OpenGLRenderer", "auto").toString() != "none"; } void KisConfig::disableOpenGL() const { const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); kritarc.setValue("OpenGLRenderer", "none"); } int KisConfig::openGLFilteringMode(bool defaultValue) const { return (defaultValue ? 3 : m_cfg.readEntry("OpenGLFilterMode", 3)); } void KisConfig::setOpenGLFilteringMode(int filteringMode) { m_cfg.writeEntry("OpenGLFilterMode", filteringMode); } bool KisConfig::useOpenGLTextureBuffer(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("useOpenGLTextureBuffer", true)); } void KisConfig::setUseOpenGLTextureBuffer(bool useBuffer) { m_cfg.writeEntry("useOpenGLTextureBuffer", useBuffer); } int KisConfig::openGLTextureSize(bool defaultValue) const { return (defaultValue ? 256 : m_cfg.readEntry("textureSize", 256)); } bool KisConfig::disableVSync(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("disableVSync", true)); } void KisConfig::setDisableVSync(bool disableVSync) { m_cfg.writeEntry("disableVSync", disableVSync); } bool KisConfig::showAdvancedOpenGLSettings(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("showAdvancedOpenGLSettings", false)); } bool KisConfig::forceOpenGLFenceWorkaround(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("forceOpenGLFenceWorkaround", false)); } int KisConfig::numMipmapLevels(bool defaultValue) const { return (defaultValue ? 4 : m_cfg.readEntry("numMipmapLevels", 4)); } int KisConfig::textureOverlapBorder() const { return 1 << qMax(0, numMipmapLevels()); } quint32 KisConfig::getGridMainStyle(bool defaultValue) const { int v = m_cfg.readEntry("gridmainstyle", 0); v = qBound(0, v, 2); return (defaultValue ? 0 : v); } void KisConfig::setGridMainStyle(quint32 v) const { m_cfg.writeEntry("gridmainstyle", v); } quint32 KisConfig::getGridSubdivisionStyle(bool defaultValue) const { quint32 v = m_cfg.readEntry("gridsubdivisionstyle", 1); if (v > 2) v = 2; return (defaultValue ? 1 : v); } void KisConfig::setGridSubdivisionStyle(quint32 v) const { m_cfg.writeEntry("gridsubdivisionstyle", v); } QColor KisConfig::getGridMainColor(bool defaultValue) const { QColor col(99, 99, 99); return (defaultValue ? col : m_cfg.readEntry("gridmaincolor", col)); } void KisConfig::setGridMainColor(const QColor & v) const { m_cfg.writeEntry("gridmaincolor", v); } QColor KisConfig::getGridSubdivisionColor(bool defaultValue) const { QColor col(150, 150, 150); return (defaultValue ? col : m_cfg.readEntry("gridsubdivisioncolor", col)); } void KisConfig::setGridSubdivisionColor(const QColor & v) const { m_cfg.writeEntry("gridsubdivisioncolor", v); } QColor KisConfig::getPixelGridColor(bool defaultValue) const { QColor col(255, 255, 255); return (defaultValue ? col : m_cfg.readEntry("pixelGridColor", col)); } void KisConfig::setPixelGridColor(const QColor & v) const { m_cfg.writeEntry("pixelGridColor", v); } qreal KisConfig::getPixelGridDrawingThreshold(bool defaultValue) const { qreal border = 24.0f; return (defaultValue ? border : m_cfg.readEntry("pixelGridDrawingThreshold", border)); } void KisConfig::setPixelGridDrawingThreshold(qreal v) const { m_cfg.writeEntry("pixelGridDrawingThreshold", v); } bool KisConfig::pixelGridEnabled(bool defaultValue) const { bool enabled = true; return (defaultValue ? enabled : m_cfg.readEntry("pixelGridEnabled", enabled)); } void KisConfig::enablePixelGrid(bool v) const { m_cfg.writeEntry("pixelGridEnabled", v); } quint32 KisConfig::guidesLineStyle(bool defaultValue) const { int v = m_cfg.readEntry("guidesLineStyle", 0); v = qBound(0, v, 2); return (defaultValue ? 0 : v); } void KisConfig::setGuidesLineStyle(quint32 v) const { m_cfg.writeEntry("guidesLineStyle", v); } QColor KisConfig::guidesColor(bool defaultValue) const { QColor col(99, 99, 99); return (defaultValue ? col : m_cfg.readEntry("guidesColor", col)); } void KisConfig::setGuidesColor(const QColor & v) const { m_cfg.writeEntry("guidesColor", v); } void KisConfig::loadSnapConfig(KisSnapConfig *config, bool defaultValue) const { KisSnapConfig defaultConfig(false); if (defaultValue) { *config = defaultConfig; return; } config->setOrthogonal(m_cfg.readEntry("globalSnapOrthogonal", defaultConfig.orthogonal())); config->setNode(m_cfg.readEntry("globalSnapNode", defaultConfig.node())); config->setExtension(m_cfg.readEntry("globalSnapExtension", defaultConfig.extension())); config->setIntersection(m_cfg.readEntry("globalSnapIntersection", defaultConfig.intersection())); config->setBoundingBox(m_cfg.readEntry("globalSnapBoundingBox", defaultConfig.boundingBox())); config->setImageBounds(m_cfg.readEntry("globalSnapImageBounds", defaultConfig.imageBounds())); config->setImageCenter(m_cfg.readEntry("globalSnapImageCenter", defaultConfig.imageCenter())); config->setToPixel(m_cfg.readEntry("globalSnapToPixel", defaultConfig.toPixel())); } void KisConfig::saveSnapConfig(const KisSnapConfig &config) { m_cfg.writeEntry("globalSnapOrthogonal", config.orthogonal()); m_cfg.writeEntry("globalSnapNode", config.node()); m_cfg.writeEntry("globalSnapExtension", config.extension()); m_cfg.writeEntry("globalSnapIntersection", config.intersection()); m_cfg.writeEntry("globalSnapBoundingBox", config.boundingBox()); m_cfg.writeEntry("globalSnapImageBounds", config.imageBounds()); m_cfg.writeEntry("globalSnapImageCenter", config.imageCenter()); m_cfg.writeEntry("globalSnapToPixel", config.toPixel()); } qint32 KisConfig::checkSize(bool defaultValue) const { qint32 size = (defaultValue ? 32 : m_cfg.readEntry("checksize", 32)); if (size == 0) size = 32; return size; } void KisConfig::setCheckSize(qint32 checksize) const { if (checksize == 0) { checksize = 32; } m_cfg.writeEntry("checksize", checksize); } bool KisConfig::scrollCheckers(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("scrollingcheckers", false)); } void KisConfig::setScrollingCheckers(bool sc) const { m_cfg.writeEntry("scrollingcheckers", sc); } QColor KisConfig::canvasBorderColor(bool defaultValue) const { QColor color(QColor(128,128,128)); return (defaultValue ? color : m_cfg.readEntry("canvasBorderColor", color)); } void KisConfig::setCanvasBorderColor(const QColor& color) const { m_cfg.writeEntry("canvasBorderColor", color); } bool KisConfig::hideScrollbars(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("hideScrollbars", false)); } void KisConfig::setHideScrollbars(bool value) const { m_cfg.writeEntry("hideScrollbars", value); } QColor KisConfig::checkersColor1(bool defaultValue) const { QColor col(220, 220, 220); return (defaultValue ? col : m_cfg.readEntry("checkerscolor", col)); } void KisConfig::setCheckersColor1(const QColor & v) const { m_cfg.writeEntry("checkerscolor", v); } QColor KisConfig::checkersColor2(bool defaultValue) const { return (defaultValue ? QColor(Qt::white) : m_cfg.readEntry("checkerscolor2", QColor(Qt::white))); } void KisConfig::setCheckersColor2(const QColor & v) const { m_cfg.writeEntry("checkerscolor2", v); } bool KisConfig::antialiasCurves(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("antialiascurves", true)); } void KisConfig::setAntialiasCurves(bool v) const { m_cfg.writeEntry("antialiascurves", v); } bool KisConfig::antialiasSelectionOutline(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("AntialiasSelectionOutline", false)); } void KisConfig::setAntialiasSelectionOutline(bool v) const { m_cfg.writeEntry("AntialiasSelectionOutline", v); } bool KisConfig::showRootLayer(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("ShowRootLayer", false)); } void KisConfig::setShowRootLayer(bool showRootLayer) const { m_cfg.writeEntry("ShowRootLayer", showRootLayer); } bool KisConfig::showGlobalSelection(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("ShowGlobalSelection", false)); } void KisConfig::setShowGlobalSelection(bool showGlobalSelection) const { m_cfg.writeEntry("ShowGlobalSelection", showGlobalSelection); } bool KisConfig::showOutlineWhilePainting(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("ShowOutlineWhilePainting", true)); } void KisConfig::setShowOutlineWhilePainting(bool showOutlineWhilePainting) const { m_cfg.writeEntry("ShowOutlineWhilePainting", showOutlineWhilePainting); } bool KisConfig::forceAlwaysFullSizedOutline(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("forceAlwaysFullSizedOutline", false)); } void KisConfig::setForceAlwaysFullSizedOutline(bool value) const { m_cfg.writeEntry("forceAlwaysFullSizedOutline", value); } KisConfig::SessionOnStartup KisConfig::sessionOnStartup(bool defaultValue) const { int value = defaultValue ? SOS_BlankSession : m_cfg.readEntry("sessionOnStartup", (int)SOS_BlankSession); return (KisConfig::SessionOnStartup)value; } void KisConfig::setSessionOnStartup(SessionOnStartup value) { m_cfg.writeEntry("sessionOnStartup", (int)value); } bool KisConfig::saveSessionOnQuit(bool defaultValue) const { return defaultValue ? false : m_cfg.readEntry("saveSessionOnQuit", false); } void KisConfig::setSaveSessionOnQuit(bool value) { m_cfg.writeEntry("saveSessionOnQuit", value); } qreal KisConfig::outlineSizeMinimum(bool defaultValue) const { return (defaultValue ? 1.0 : m_cfg.readEntry("OutlineSizeMinimum", 1.0)); } void KisConfig::setOutlineSizeMinimum(qreal outlineSizeMinimum) const { m_cfg.writeEntry("OutlineSizeMinimum", outlineSizeMinimum); } qreal KisConfig::selectionViewSizeMinimum(bool defaultValue) const { return (defaultValue ? 5.0 : m_cfg.readEntry("SelectionViewSizeMinimum", 5.0)); } void KisConfig::setSelectionViewSizeMinimum(qreal outlineSizeMinimum) const { m_cfg.writeEntry("SelectionViewSizeMinimum", outlineSizeMinimum); } int KisConfig::autoSaveInterval(bool defaultValue) const { return (defaultValue ? 15 * 60 : m_cfg.readEntry("AutoSaveInterval", 15 * 60)); } void KisConfig::setAutoSaveInterval(int seconds) const { return m_cfg.writeEntry("AutoSaveInterval", seconds); } bool KisConfig::backupFile(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("CreateBackupFile", true)); } void KisConfig::setBackupFile(bool backupFile) const { m_cfg.writeEntry("CreateBackupFile", backupFile); } bool KisConfig::showFilterGallery(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("showFilterGallery", false)); } void KisConfig::setShowFilterGallery(bool showFilterGallery) const { m_cfg.writeEntry("showFilterGallery", showFilterGallery); } bool KisConfig::showFilterGalleryLayerMaskDialog(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("showFilterGalleryLayerMaskDialog", true)); } void KisConfig::setShowFilterGalleryLayerMaskDialog(bool showFilterGallery) const { m_cfg.writeEntry("setShowFilterGalleryLayerMaskDialog", showFilterGallery); } QString KisConfig::canvasState(bool defaultValue) const { const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); return (defaultValue ? "OPENGL_NOT_TRIED" : kritarc.value("canvasState", "OPENGL_NOT_TRIED").toString()); } void KisConfig::setCanvasState(const QString& state) const { static QStringList acceptableStates; if (acceptableStates.isEmpty()) { acceptableStates << "OPENGL_SUCCESS" << "TRY_OPENGL" << "OPENGL_NOT_TRIED" << "OPENGL_FAILED"; } if (acceptableStates.contains(state)) { const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); kritarc.setValue("canvasState", state); } } bool KisConfig::toolOptionsPopupDetached(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("ToolOptionsPopupDetached", false)); } void KisConfig::setToolOptionsPopupDetached(bool detached) const { m_cfg.writeEntry("ToolOptionsPopupDetached", detached); } bool KisConfig::paintopPopupDetached(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("PaintopPopupDetached", false)); } void KisConfig::setPaintopPopupDetached(bool detached) const { m_cfg.writeEntry("PaintopPopupDetached", detached); } QString KisConfig::pressureTabletCurve(bool defaultValue) const { return (defaultValue ? "0,0;1,1" : m_cfg.readEntry("tabletPressureCurve","0,0;1,1;")); } void KisConfig::setPressureTabletCurve(const QString& curveString) const { m_cfg.writeEntry("tabletPressureCurve", curveString); } bool KisConfig::useWin8PointerInput(bool defaultValue) const { #ifdef Q_OS_WIN #ifdef USE_QT_TABLET_WINDOWS const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); return useWin8PointerInputNoApp(&kritarc, defaultValue); #else return (defaultValue ? false : m_cfg.readEntry("useWin8PointerInput", false)); #endif #else Q_UNUSED(defaultValue); return false; #endif } void KisConfig::setUseWin8PointerInput(bool value) { #ifdef Q_OS_WIN // Special handling: Only set value if changed // I don't want it to be set if the user hasn't touched it if (useWin8PointerInput() != value) { #ifdef USE_QT_TABLET_WINDOWS const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); setUseWin8PointerInputNoApp(&kritarc, value); #else m_cfg.writeEntry("useWin8PointerInput", value); #endif } #else Q_UNUSED(value) #endif } bool KisConfig::useWin8PointerInputNoApp(QSettings *settings, bool defaultValue) { return defaultValue ? false : settings->value("useWin8PointerInput", false).toBool(); } void KisConfig::setUseWin8PointerInputNoApp(QSettings *settings, bool value) { settings->setValue("useWin8PointerInput", value); } bool KisConfig::useRightMiddleTabletButtonWorkaround(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("useRightMiddleTabletButtonWorkaround", false)); } void KisConfig::setUseRightMiddleTabletButtonWorkaround(bool value) { m_cfg.writeEntry("useRightMiddleTabletButtonWorkaround", value); } qreal KisConfig::vastScrolling(bool defaultValue) const { return (defaultValue ? 0.9 : m_cfg.readEntry("vastScrolling", 0.9)); } void KisConfig::setVastScrolling(const qreal factor) const { m_cfg.writeEntry("vastScrolling", factor); } int KisConfig::presetChooserViewMode(bool defaultValue) const { return (defaultValue ? 0 : m_cfg.readEntry("presetChooserViewMode", 0)); } void KisConfig::setPresetChooserViewMode(const int mode) const { m_cfg.writeEntry("presetChooserViewMode", mode); } int KisConfig::presetIconSize(bool defaultValue) const { return (defaultValue ? 60 : m_cfg.readEntry("presetIconSize", 60)); } void KisConfig::setPresetIconSize(const int value) const { m_cfg.writeEntry("presetIconSize", value); } bool KisConfig::firstRun(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("firstRun", true)); } void KisConfig::setFirstRun(const bool first) const { m_cfg.writeEntry("firstRun", first); } int KisConfig::horizontalSplitLines(bool defaultValue) const { return (defaultValue ? 1 : m_cfg.readEntry("horizontalSplitLines", 1)); } void KisConfig::setHorizontalSplitLines(const int numberLines) const { m_cfg.writeEntry("horizontalSplitLines", numberLines); } int KisConfig::verticalSplitLines(bool defaultValue) const { return (defaultValue ? 1 : m_cfg.readEntry("verticalSplitLines", 1)); } void KisConfig::setVerticalSplitLines(const int numberLines) const { m_cfg.writeEntry("verticalSplitLines", numberLines); } bool KisConfig::clicklessSpacePan(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("clicklessSpacePan", true)); } void KisConfig::setClicklessSpacePan(const bool toggle) const { m_cfg.writeEntry("clicklessSpacePan", toggle); } bool KisConfig::hideDockersFullscreen(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("hideDockersFullScreen", true)); } void KisConfig::setHideDockersFullscreen(const bool value) const { m_cfg.writeEntry("hideDockersFullScreen", value); } bool KisConfig::showDockers(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("showDockers", true)); } void KisConfig::setShowDockers(const bool value) const { m_cfg.writeEntry("showDockers", value); } bool KisConfig::showStatusBar(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("showStatusBar", true)); } void KisConfig::setShowStatusBar(const bool value) const { m_cfg.writeEntry("showStatusBar", value); } bool KisConfig::hideMenuFullscreen(bool defaultValue) const { return (defaultValue ? true: m_cfg.readEntry("hideMenuFullScreen", true)); } void KisConfig::setHideMenuFullscreen(const bool value) const { m_cfg.writeEntry("hideMenuFullScreen", value); } bool KisConfig::hideScrollbarsFullscreen(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("hideScrollbarsFullScreen", true)); } void KisConfig::setHideScrollbarsFullscreen(const bool value) const { m_cfg.writeEntry("hideScrollbarsFullScreen", value); } bool KisConfig::hideStatusbarFullscreen(bool defaultValue) const { return (defaultValue ? true: m_cfg.readEntry("hideStatusbarFullScreen", true)); } void KisConfig::setHideStatusbarFullscreen(const bool value) const { m_cfg.writeEntry("hideStatusbarFullScreen", value); } bool KisConfig::hideTitlebarFullscreen(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("hideTitleBarFullscreen", true)); } void KisConfig::setHideTitlebarFullscreen(const bool value) const { m_cfg.writeEntry("hideTitleBarFullscreen", value); } bool KisConfig::hideToolbarFullscreen(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("hideToolbarFullscreen", true)); } void KisConfig::setHideToolbarFullscreen(const bool value) const { m_cfg.writeEntry("hideToolbarFullscreen", value); } bool KisConfig::fullscreenMode(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("fullscreenMode", true)); } void KisConfig::setFullscreenMode(const bool value) const { m_cfg.writeEntry("fullscreenMode", value); } QStringList KisConfig::favoriteCompositeOps(bool defaultValue) const { return (defaultValue ? QStringList() : m_cfg.readEntry("favoriteCompositeOps", QString("normal,erase,multiply,burn,darken,add,dodge,screen,overlay,soft_light_svg,luminize,lighten,saturation,color,divide").split(','))); } void KisConfig::setFavoriteCompositeOps(const QStringList& compositeOps) const { m_cfg.writeEntry("favoriteCompositeOps", compositeOps); } QString KisConfig::exportConfigurationXML(const QString &filterId, bool defaultValue) const { return (defaultValue ? QString() : m_cfg.readEntry("ExportConfiguration-" + filterId, QString())); } KisPropertiesConfigurationSP KisConfig::exportConfiguration(const QString &filterId, bool defaultValue) const { KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration(); const QString xmlData = exportConfigurationXML(filterId, defaultValue); cfg->fromXML(xmlData); return cfg; } void KisConfig::setExportConfiguration(const QString &filterId, KisPropertiesConfigurationSP properties) const { QString exportConfig = properties->toXML(); m_cfg.writeEntry("ExportConfiguration-" + filterId, exportConfig); } QString KisConfig::importConfiguration(const QString &filterId, bool defaultValue) const { return (defaultValue ? QString() : m_cfg.readEntry("ImportConfiguration-" + filterId, QString())); } void KisConfig::setImportConfiguration(const QString &filterId, KisPropertiesConfigurationSP properties) const { QString importConfig = properties->toXML(); m_cfg.writeEntry("ImportConfiguration-" + filterId, importConfig); } bool KisConfig::useOcio(bool defaultValue) const { #ifdef HAVE_OCIO return (defaultValue ? false : m_cfg.readEntry("Krita/Ocio/UseOcio", false)); #else Q_UNUSED(defaultValue); return false; #endif } void KisConfig::setUseOcio(bool useOCIO) const { m_cfg.writeEntry("Krita/Ocio/UseOcio", useOCIO); } int KisConfig::favoritePresets(bool defaultValue) const { return (defaultValue ? 10 : m_cfg.readEntry("numFavoritePresets", 10)); } void KisConfig::setFavoritePresets(const int value) { m_cfg.writeEntry("numFavoritePresets", value); } bool KisConfig::levelOfDetailEnabled(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("levelOfDetailEnabled", false)); } void KisConfig::setLevelOfDetailEnabled(bool value) { m_cfg.writeEntry("levelOfDetailEnabled", value); } KisOcioConfiguration KisConfig::ocioConfiguration(bool defaultValue) const { KisOcioConfiguration cfg; if (!defaultValue) { cfg.mode = (KisOcioConfiguration::Mode)m_cfg.readEntry("Krita/Ocio/OcioColorManagementMode", 0); cfg.configurationPath = m_cfg.readEntry("Krita/Ocio/OcioConfigPath", QString()); cfg.lutPath = m_cfg.readEntry("Krita/Ocio/OcioLutPath", QString()); cfg.inputColorSpace = m_cfg.readEntry("Krita/Ocio/InputColorSpace", QString()); cfg.displayDevice = m_cfg.readEntry("Krita/Ocio/DisplayDevice", QString()); cfg.displayView = m_cfg.readEntry("Krita/Ocio/DisplayView", QString()); cfg.look = m_cfg.readEntry("Krita/Ocio/DisplayLook", QString()); } return cfg; } void KisConfig::setOcioConfiguration(const KisOcioConfiguration &cfg) { m_cfg.writeEntry("Krita/Ocio/OcioColorManagementMode", (int) cfg.mode); m_cfg.writeEntry("Krita/Ocio/OcioConfigPath", cfg.configurationPath); m_cfg.writeEntry("Krita/Ocio/OcioLutPath", cfg.lutPath); m_cfg.writeEntry("Krita/Ocio/InputColorSpace", cfg.inputColorSpace); m_cfg.writeEntry("Krita/Ocio/DisplayDevice", cfg.displayDevice); m_cfg.writeEntry("Krita/Ocio/DisplayView", cfg.displayView); m_cfg.writeEntry("Krita/Ocio/DisplayLook", cfg.look); } KisConfig::OcioColorManagementMode KisConfig::ocioColorManagementMode(bool defaultValue) const { // FIXME: this option duplicates ocioConfiguration(), please deprecate it return (OcioColorManagementMode)(defaultValue ? INTERNAL : m_cfg.readEntry("Krita/Ocio/OcioColorManagementMode", (int) INTERNAL)); } void KisConfig::setOcioColorManagementMode(OcioColorManagementMode mode) const { // FIXME: this option duplicates ocioConfiguration(), please deprecate it m_cfg.writeEntry("Krita/Ocio/OcioColorManagementMode", (int) mode); } int KisConfig::ocioLutEdgeSize(bool defaultValue) const { return (defaultValue ? 64 : m_cfg.readEntry("Krita/Ocio/LutEdgeSize", 64)); } void KisConfig::setOcioLutEdgeSize(int value) { m_cfg.writeEntry("Krita/Ocio/LutEdgeSize", value); } bool KisConfig::ocioLockColorVisualRepresentation(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("Krita/Ocio/OcioLockColorVisualRepresentation", false)); } void KisConfig::setOcioLockColorVisualRepresentation(bool value) { m_cfg.writeEntry("Krita/Ocio/OcioLockColorVisualRepresentation", value); } QString KisConfig::defaultPalette(bool defaultValue) const { return (defaultValue ? QString() : m_cfg.readEntry("defaultPalette", "Default")); } void KisConfig::setDefaultPalette(const QString& name) const { m_cfg.writeEntry("defaultPalette", name); } QString KisConfig::toolbarSlider(int sliderNumber, bool defaultValue) const { QString def = "flow"; if (sliderNumber == 1) { def = "opacity"; } if (sliderNumber == 2) { def = "size"; } return (defaultValue ? def : m_cfg.readEntry(QString("toolbarslider_%1").arg(sliderNumber), def)); } void KisConfig::setToolbarSlider(int sliderNumber, const QString &slider) { m_cfg.writeEntry(QString("toolbarslider_%1").arg(sliderNumber), slider); } int KisConfig::layerThumbnailSize(bool defaultValue) const { return (defaultValue ? 20 : m_cfg.readEntry("layerThumbnailSize", 20)); } void KisConfig::setLayerThumbnailSize(int size) { m_cfg.writeEntry("layerThumbnailSize", size); } bool KisConfig::sliderLabels(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("sliderLabels", true)); } void KisConfig::setSliderLabels(bool enabled) { m_cfg.writeEntry("sliderLabels", enabled); } QString KisConfig::currentInputProfile(bool defaultValue) const { return (defaultValue ? QString() : m_cfg.readEntry("currentInputProfile", QString())); } void KisConfig::setCurrentInputProfile(const QString& name) { m_cfg.writeEntry("currentInputProfile", name); } bool KisConfig::useSystemMonitorProfile(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("ColorManagement/UseSystemMonitorProfile", false)); } void KisConfig::setUseSystemMonitorProfile(bool _useSystemMonitorProfile) const { m_cfg.writeEntry("ColorManagement/UseSystemMonitorProfile", _useSystemMonitorProfile); } bool KisConfig::presetStripVisible(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("presetStripVisible", true)); } void KisConfig::setPresetStripVisible(bool visible) { m_cfg.writeEntry("presetStripVisible", visible); } bool KisConfig::scratchpadVisible(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("scratchpadVisible", true)); } void KisConfig::setScratchpadVisible(bool visible) { m_cfg.writeEntry("scratchpadVisible", visible); } bool KisConfig::showSingleChannelAsColor(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("showSingleChannelAsColor", false)); } void KisConfig::setShowSingleChannelAsColor(bool asColor) { m_cfg.writeEntry("showSingleChannelAsColor", asColor); } bool KisConfig::hidePopups(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("hidePopups", false)); } void KisConfig::setHidePopups(bool hidepopups) { m_cfg.writeEntry("hidePopups", hidepopups); } int KisConfig::numDefaultLayers(bool defaultValue) const { return (defaultValue ? 2 : m_cfg.readEntry("NumberOfLayersForNewImage", 2)); } void KisConfig::setNumDefaultLayers(int num) { m_cfg.writeEntry("NumberOfLayersForNewImage", num); } quint8 KisConfig::defaultBackgroundOpacity(bool defaultValue) const { return (defaultValue ? (int)OPACITY_OPAQUE_U8 : m_cfg.readEntry("BackgroundOpacityForNewImage", (int)OPACITY_OPAQUE_U8)); } void KisConfig::setDefaultBackgroundOpacity(quint8 value) { m_cfg.writeEntry("BackgroundOpacityForNewImage", (int)value); } QColor KisConfig::defaultBackgroundColor(bool defaultValue) const { return (defaultValue ? QColor(Qt::white) : m_cfg.readEntry("BackgroundColorForNewImage", QColor(Qt::white))); } void KisConfig::setDefaultBackgroundColor(const QColor &value) { m_cfg.writeEntry("BackgroundColorForNewImage", value); } KisConfig::BackgroundStyle KisConfig::defaultBackgroundStyle(bool defaultValue) const { return (KisConfig::BackgroundStyle)(defaultValue ? RASTER_LAYER : m_cfg.readEntry("BackgroundStyleForNewImage", (int)RASTER_LAYER)); } void KisConfig::setDefaultBackgroundStyle(KisConfig::BackgroundStyle value) { m_cfg.writeEntry("BackgroundStyleForNewImage", (int)value); } int KisConfig::lineSmoothingType(bool defaultValue) const { return (defaultValue ? 1 : m_cfg.readEntry("LineSmoothingType", 1)); } void KisConfig::setLineSmoothingType(int value) { m_cfg.writeEntry("LineSmoothingType", value); } qreal KisConfig::lineSmoothingDistance(bool defaultValue) const { return (defaultValue ? 50.0 : m_cfg.readEntry("LineSmoothingDistance", 50.0)); } void KisConfig::setLineSmoothingDistance(qreal value) { m_cfg.writeEntry("LineSmoothingDistance", value); } qreal KisConfig::lineSmoothingTailAggressiveness(bool defaultValue) const { return (defaultValue ? 0.15 : m_cfg.readEntry("LineSmoothingTailAggressiveness", 0.15)); } void KisConfig::setLineSmoothingTailAggressiveness(qreal value) { m_cfg.writeEntry("LineSmoothingTailAggressiveness", value); } bool KisConfig::lineSmoothingSmoothPressure(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("LineSmoothingSmoothPressure", false)); } void KisConfig::setLineSmoothingSmoothPressure(bool value) { m_cfg.writeEntry("LineSmoothingSmoothPressure", value); } bool KisConfig::lineSmoothingScalableDistance(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("LineSmoothingScalableDistance", true)); } void KisConfig::setLineSmoothingScalableDistance(bool value) { m_cfg.writeEntry("LineSmoothingScalableDistance", value); } qreal KisConfig::lineSmoothingDelayDistance(bool defaultValue) const { return (defaultValue ? 50.0 : m_cfg.readEntry("LineSmoothingDelayDistance", 50.0)); } void KisConfig::setLineSmoothingDelayDistance(qreal value) { m_cfg.writeEntry("LineSmoothingDelayDistance", value); } bool KisConfig::lineSmoothingUseDelayDistance(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("LineSmoothingUseDelayDistance", true)); } void KisConfig::setLineSmoothingUseDelayDistance(bool value) { m_cfg.writeEntry("LineSmoothingUseDelayDistance", value); } bool KisConfig::lineSmoothingFinishStabilizedCurve(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("LineSmoothingFinishStabilizedCurve", true)); } void KisConfig::setLineSmoothingFinishStabilizedCurve(bool value) { m_cfg.writeEntry("LineSmoothingFinishStabilizedCurve", value); } bool KisConfig::lineSmoothingStabilizeSensors(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("LineSmoothingStabilizeSensors", true)); } void KisConfig::setLineSmoothingStabilizeSensors(bool value) { m_cfg.writeEntry("LineSmoothingStabilizeSensors", value); } int KisConfig::tabletEventsDelay(bool defaultValue) const { return (defaultValue ? 10 : m_cfg.readEntry("tabletEventsDelay", 10)); } void KisConfig::setTabletEventsDelay(int value) { m_cfg.writeEntry("tabletEventsDelay", value); } bool KisConfig::trackTabletEventLatency(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("trackTabletEventLatency", false)); } void KisConfig::setTrackTabletEventLatency(bool value) { m_cfg.writeEntry("trackTabletEventLatency", value); } bool KisConfig::testingAcceptCompressedTabletEvents(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("testingAcceptCompressedTabletEvents", false)); } void KisConfig::setTestingAcceptCompressedTabletEvents(bool value) { m_cfg.writeEntry("testingAcceptCompressedTabletEvents", value); } bool KisConfig::shouldEatDriverShortcuts(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("shouldEatDriverShortcuts", false)); } bool KisConfig::testingCompressBrushEvents(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("testingCompressBrushEvents", false)); } void KisConfig::setTestingCompressBrushEvents(bool value) { m_cfg.writeEntry("testingCompressBrushEvents", value); } int KisConfig::workaroundX11SmoothPressureSteps(bool defaultValue) const { return (defaultValue ? 0 : m_cfg.readEntry("workaroundX11SmoothPressureSteps", 0)); } bool KisConfig::showCanvasMessages(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("showOnCanvasMessages", true)); } void KisConfig::setShowCanvasMessages(bool show) { m_cfg.writeEntry("showOnCanvasMessages", show); } bool KisConfig::compressKra(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("compressLayersInKra", false)); } void KisConfig::setCompressKra(bool compress) { m_cfg.writeEntry("compressLayersInKra", compress); } bool KisConfig::toolOptionsInDocker(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("ToolOptionsInDocker", true)); } void KisConfig::setToolOptionsInDocker(bool inDocker) { m_cfg.writeEntry("ToolOptionsInDocker", inDocker); } bool KisConfig::kineticScrollingEnabled(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("KineticScrollingEnabled", true)); } void KisConfig::setKineticScrollingEnabled(bool value) { m_cfg.writeEntry("KineticScrollingEnabled", value); } int KisConfig::kineticScrollingGesture(bool defaultValue) const { return (defaultValue ? 2 : m_cfg.readEntry("KineticScrollingGesture", 2)); } void KisConfig::setKineticScrollingGesture(int gesture) { m_cfg.writeEntry("KineticScrollingGesture", gesture); } int KisConfig::kineticScrollingSensitivity(bool defaultValue) const { return (defaultValue ? 75 : m_cfg.readEntry("KineticScrollingSensitivity", 75)); } void KisConfig::setKineticScrollingSensitivity(int sensitivity) { m_cfg.writeEntry("KineticScrollingSensitivity", sensitivity); } bool KisConfig::kineticScrollingHiddenScrollbars(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("KineticScrollingHideScrollbar", false)); } void KisConfig::setKineticScrollingHideScrollbars(bool scrollbar) { m_cfg.writeEntry("KineticScrollingHideScrollbar", scrollbar); } const KoColorSpace* KisConfig::customColorSelectorColorSpace(bool defaultValue) const { const KoColorSpace *cs = 0; KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector"); if (defaultValue || cfg.readEntry("useCustomColorSpace", true)) { KoColorSpaceRegistry* csr = KoColorSpaceRegistry::instance(); QString modelID = cfg.readEntry("customColorSpaceModel", "RGBA"); QString depthID = cfg.readEntry("customColorSpaceDepthID", "U8"); QString profile = cfg.readEntry("customColorSpaceProfile", "sRGB built-in - (lcms internal)"); if (profile == "default") { // qDebug() << "Falling back to default color profile."; profile = "sRGB built-in - (lcms internal)"; } cs = csr->colorSpace(modelID, depthID, profile); } return cs; } void KisConfig::setCustomColorSelectorColorSpace(const KoColorSpace *cs) { KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector"); cfg.writeEntry("useCustomColorSpace", bool(cs)); if(cs) { cfg.writeEntry("customColorSpaceModel", cs->colorModelId().id()); cfg.writeEntry("customColorSpaceDepthID", cs->colorDepthId().id()); cfg.writeEntry("customColorSpaceProfile", cs->profile()->name()); } KisConfigNotifier::instance()->notifyConfigChanged(); } bool KisConfig::enableOpenGLFramerateLogging(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("enableOpenGLFramerateLogging", false)); } void KisConfig::setEnableOpenGLFramerateLogging(bool value) const { m_cfg.writeEntry("enableOpenGLFramerateLogging", value); } bool KisConfig::enableBrushSpeedLogging(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("enableBrushSpeedLogging", false)); } void KisConfig::setEnableBrushSpeedLogging(bool value) const { m_cfg.writeEntry("enableBrushSpeedLogging", value); } void KisConfig::setEnableAmdVectorizationWorkaround(bool value) { m_cfg.writeEntry("amdDisableVectorWorkaround", value); } bool KisConfig::enableAmdVectorizationWorkaround(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("amdDisableVectorWorkaround", false)); } void KisConfig::setDisableAVXOptimizations(bool value) { m_cfg.writeEntry("disableAVXOptimizations", value); } bool KisConfig::disableAVXOptimizations(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("disableAVXOptimizations", false)); } void KisConfig::setAnimationDropFrames(bool value) { bool oldValue = animationDropFrames(); if (value == oldValue) return; m_cfg.writeEntry("animationDropFrames", value); KisConfigNotifier::instance()->notifyDropFramesModeChanged(); } bool KisConfig::animationDropFrames(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("animationDropFrames", true)); } int KisConfig::scrubbingUpdatesDelay(bool defaultValue) const { return (defaultValue ? 30 : m_cfg.readEntry("scrubbingUpdatesDelay", 30)); } void KisConfig::setScrubbingUpdatesDelay(int value) { m_cfg.writeEntry("scrubbingUpdatesDelay", value); } int KisConfig::scrubbingAudioUpdatesDelay(bool defaultValue) const { return (defaultValue ? -1 : m_cfg.readEntry("scrubbingAudioUpdatesDelay", -1)); } void KisConfig::setScrubbingAudioUpdatesDelay(int value) { m_cfg.writeEntry("scrubbingAudioUpdatesDelay", value); } int KisConfig::audioOffsetTolerance(bool defaultValue) const { return (defaultValue ? -1 : m_cfg.readEntry("audioOffsetTolerance", -1)); } void KisConfig::setAudioOffsetTolerance(int value) { m_cfg.writeEntry("audioOffsetTolerance", value); } bool KisConfig::switchSelectionCtrlAlt(bool defaultValue) const { return defaultValue ? false : m_cfg.readEntry("switchSelectionCtrlAlt", false); } void KisConfig::setSwitchSelectionCtrlAlt(bool value) { m_cfg.writeEntry("switchSelectionCtrlAlt", value); KisConfigNotifier::instance()->notifyConfigChanged(); } bool KisConfig::convertToImageColorspaceOnImport(bool defaultValue) const { return defaultValue ? false : m_cfg.readEntry("ConvertToImageColorSpaceOnImport", false); } void KisConfig::setConvertToImageColorspaceOnImport(bool value) { m_cfg.writeEntry("ConvertToImageColorSpaceOnImport", value); } int KisConfig::stabilizerSampleSize(bool defaultValue) const { #ifdef Q_OS_WIN const int defaultSampleSize = 50; #else const int defaultSampleSize = 15; #endif return defaultValue ? defaultSampleSize : m_cfg.readEntry("stabilizerSampleSize", defaultSampleSize); } void KisConfig::setStabilizerSampleSize(int value) { m_cfg.writeEntry("stabilizerSampleSize", value); } bool KisConfig::stabilizerDelayedPaint(bool defaultValue) const { const bool defaultEnabled = true; return defaultValue ? defaultEnabled : m_cfg.readEntry("stabilizerDelayedPaint", defaultEnabled); } void KisConfig::setStabilizerDelayedPaint(bool value) { m_cfg.writeEntry("stabilizerDelayedPaint", value); } bool KisConfig::showBrushHud(bool defaultValue) const { return defaultValue ? false : m_cfg.readEntry("showBrushHud", false); } void KisConfig::setShowBrushHud(bool value) { m_cfg.writeEntry("showBrushHud", value); } QString KisConfig::brushHudSetting(bool defaultValue) const { QString defaultDoc = "\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n"; return defaultValue ? defaultDoc : m_cfg.readEntry("brushHudSettings", defaultDoc); } void KisConfig::setBrushHudSetting(const QString &value) const { m_cfg.writeEntry("brushHudSettings", value); } bool KisConfig::calculateAnimationCacheInBackground(bool defaultValue) const { return defaultValue ? true : m_cfg.readEntry("calculateAnimationCacheInBackground", true); } void KisConfig::setCalculateAnimationCacheInBackground(bool value) { m_cfg.writeEntry("calculateAnimationCacheInBackground", value); } QColor KisConfig::defaultAssistantsColor(bool defaultValue) const { static const QColor defaultColor = QColor(176, 176, 176, 255); return defaultValue ? defaultColor : m_cfg.readEntry("defaultAssistantsColor", defaultColor); } void KisConfig::setDefaultAssistantsColor(const QColor &color) const { m_cfg.writeEntry("defaultAssistantsColor", color); } bool KisConfig::autoSmoothBezierCurves(bool defaultValue) const { return defaultValue ? false : m_cfg.readEntry("autoSmoothBezierCurves", false); } void KisConfig::setAutoSmoothBezierCurves(bool value) { m_cfg.writeEntry("autoSmoothBezierCurves", value); } bool KisConfig::activateTransformToolAfterPaste(bool defaultValue) const { return defaultValue ? false : m_cfg.readEntry("activateTransformToolAfterPaste", false); } void KisConfig::setActivateTransformToolAfterPaste(bool value) { m_cfg.writeEntry("activateTransformToolAfterPaste", value); } KisConfig::RootSurfaceFormat KisConfig::rootSurfaceFormat(bool defaultValue) const { const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); return rootSurfaceFormat(&kritarc, defaultValue); } void KisConfig::setRootSurfaceFormat(KisConfig::RootSurfaceFormat value) { const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); setRootSurfaceFormat(&kritarc, value); } KisConfig::RootSurfaceFormat KisConfig::rootSurfaceFormat(QSettings *displayrc, bool defaultValue) { QString textValue = "bt709-g22"; if (!defaultValue) { textValue = displayrc->value("rootSurfaceFormat", textValue).toString(); } return textValue == "bt709-g10" ? BT709_G10 : textValue == "bt2020-pq" ? BT2020_PQ : BT709_G22; } void KisConfig::setRootSurfaceFormat(QSettings *displayrc, KisConfig::RootSurfaceFormat value) { const QString textValue = value == BT709_G10 ? "bt709-g10" : value == BT2020_PQ ? "bt2020-pq" : "bt709-g22"; displayrc->setValue("rootSurfaceFormat", textValue); } bool KisConfig::useZip64(bool defaultValue) const { return defaultValue ? false : m_cfg.readEntry("UseZip64", false); } void KisConfig::setUseZip64(bool value) { m_cfg.writeEntry("UseZip64", value); } #include #include void KisConfig::writeKoColor(const QString& name, const KoColor& color) const { QDomDocument doc = QDomDocument(name); QDomElement el = doc.createElement(name); doc.appendChild(el); color.toXML(doc, el); m_cfg.writeEntry(name, doc.toString()); } //ported from kispropertiesconfig. KoColor KisConfig::readKoColor(const QString& name, const KoColor& _color) const { QDomDocument doc; KoColor color = _color; if (!m_cfg.readEntry(name).isNull()) { doc.setContent(m_cfg.readEntry(name)); QDomElement e = doc.documentElement().firstChild().toElement(); color = KoColor::fromXML(e, Integer16BitsColorDepthID.id()); } else { QString blackColor = "\n\n \n\n"; doc.setContent(blackColor); QDomElement e = doc.documentElement().firstChild().toElement(); color = KoColor::fromXML(e, Integer16BitsColorDepthID.id()); } return color; } diff --git a/libs/ui/kis_layer_manager.cc b/libs/ui/kis_layer_manager.cc index b2cadff1a5..4506ced736 100644 --- a/libs/ui/kis_layer_manager.cc +++ b/libs/ui/kis_layer_manager.cc @@ -1,1011 +1,1002 @@ /* * Copyright (C) 2006 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_layer_manager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_config.h" #include "kis_cursor.h" #include "dialogs/kis_dlg_adj_layer_props.h" #include "dialogs/kis_dlg_adjustment_layer.h" #include "dialogs/kis_dlg_layer_properties.h" #include "dialogs/kis_dlg_generator_layer.h" #include "dialogs/kis_dlg_file_layer.h" #include "dialogs/kis_dlg_layer_style.h" #include "dialogs/KisDlgChangeCloneSource.h" #include "kis_filter_manager.h" #include "kis_node_visitor.h" #include "kis_paint_layer.h" #include "commands/kis_image_commands.h" #include "commands/kis_node_commands.h" #include "kis_change_file_layer_command.h" #include "kis_canvas_resource_provider.h" #include "kis_selection_manager.h" #include "kis_statusbar.h" #include "KisViewManager.h" #include "kis_zoom_manager.h" #include "canvas/kis_canvas2.h" #include "widgets/kis_meta_data_merge_strategy_chooser_widget.h" #include "widgets/kis_wdg_generator.h" #include "kis_progress_widget.h" #include "kis_node_commands_adapter.h" #include "kis_node_manager.h" #include "kis_action.h" #include "kis_action_manager.h" #include "kis_raster_keyframe_channel.h" #include "kis_signal_compressor_with_param.h" #include "kis_abstract_projection_plane.h" #include "commands_new/kis_set_layer_style_command.h" #include "kis_post_execution_undo_adapter.h" #include "kis_selection_mask.h" #include "kis_layer_utils.h" #include "lazybrush/kis_colorize_mask.h" #include "kis_processing_applicator.h" #include "KisSaveGroupVisitor.h" KisLayerManager::KisLayerManager(KisViewManager * view) : m_view(view) , m_imageView(0) , m_imageFlatten(0) , m_imageMergeLayer(0) , m_groupLayersSave(0) , m_imageResizeToLayer(0) , m_flattenLayer(0) , m_rasterizeLayer(0) , m_commandsAdapter(new KisNodeCommandsAdapter(m_view)) , m_layerStyle(0) { } KisLayerManager::~KisLayerManager() { delete m_commandsAdapter; } void KisLayerManager::setView(QPointerview) { m_imageView = view; } KisLayerSP KisLayerManager::activeLayer() { if (m_imageView) { return m_imageView->currentLayer(); } return 0; } KisPaintDeviceSP KisLayerManager::activeDevice() { if (activeLayer()) { return activeLayer()->paintDevice(); } return 0; } void KisLayerManager::activateLayer(KisLayerSP layer) { if (m_imageView) { - emit sigLayerActivated(layer); layersUpdated(); if (layer) { m_view->canvasResourceProvider()->slotNodeActivated(layer.data()); } } } void KisLayerManager::setup(KisActionManager* actionManager) { m_imageFlatten = actionManager->createAction("flatten_image"); connect(m_imageFlatten, SIGNAL(triggered()), this, SLOT(flattenImage())); m_imageMergeLayer = actionManager->createAction("merge_layer"); connect(m_imageMergeLayer, SIGNAL(triggered()), this, SLOT(mergeLayer())); m_flattenLayer = actionManager->createAction("flatten_layer"); connect(m_flattenLayer, SIGNAL(triggered()), this, SLOT(flattenLayer())); m_rasterizeLayer = actionManager->createAction("rasterize_layer"); connect(m_rasterizeLayer, SIGNAL(triggered()), this, SLOT(rasterizeLayer())); m_groupLayersSave = actionManager->createAction("save_groups_as_images"); connect(m_groupLayersSave, SIGNAL(triggered()), this, SLOT(saveGroupLayers())); m_convertGroupAnimated = actionManager->createAction("convert_group_to_animated"); connect(m_convertGroupAnimated, SIGNAL(triggered()), this, SLOT(convertGroupToAnimated())); m_imageResizeToLayer = actionManager->createAction("resizeimagetolayer"); connect(m_imageResizeToLayer, SIGNAL(triggered()), this, SLOT(imageResizeToActiveLayer())); KisAction *action = actionManager->createAction("trim_to_image"); connect(action, SIGNAL(triggered()), this, SLOT(trimToImage())); m_layerStyle = actionManager->createAction("layer_style"); connect(m_layerStyle, SIGNAL(triggered()), this, SLOT(layerStyle())); } void KisLayerManager::updateGUI() { KisImageSP image = m_view->image(); KisLayerSP layer = activeLayer(); const bool isGroupLayer = layer && layer->inherits("KisGroupLayer"); m_imageMergeLayer->setText( isGroupLayer ? i18nc("@action:inmenu", "Merge Group") : i18nc("@action:inmenu", "Merge with Layer Below")); m_flattenLayer->setVisible(!isGroupLayer); if (m_view->statusBar()) m_view->statusBar()->setProfile(image); } void KisLayerManager::imageResizeToActiveLayer() { KisLayerSP layer; KisImageWSP image = m_view->image(); if (image && (layer = activeLayer())) { QRect cropRect = layer->projection()->nonDefaultPixelArea(); if (!cropRect.isEmpty()) { image->cropImage(cropRect); } else { m_view->showFloatingMessage( i18nc("floating message in layer manager", "Layer is empty "), QIcon(), 2000, KisFloatingMessage::Low); } } } void KisLayerManager::trimToImage() { KisImageWSP image = m_view->image(); if (image) { image->cropImage(image->bounds()); } } void KisLayerManager::layerProperties() { if (!m_view) return; if (!m_view->document()) return; KisLayerSP layer = activeLayer(); if (!layer) return; QList selectedNodes = m_view->nodeManager()->selectedNodes(); const bool multipleLayersSelected = selectedNodes.size() > 1; KisAdjustmentLayerSP adjustmentLayer = KisAdjustmentLayerSP(dynamic_cast(layer.data())); KisGeneratorLayerSP generatorLayer = KisGeneratorLayerSP(dynamic_cast(layer.data())); KisFileLayerSP fileLayer = KisFileLayerSP(dynamic_cast(layer.data())); if (adjustmentLayer && !multipleLayersSelected) { KisPaintDeviceSP dev = adjustmentLayer->projection(); KisDlgAdjLayerProps dlg(adjustmentLayer, adjustmentLayer.data(), dev, m_view, adjustmentLayer->filter().data(), adjustmentLayer->name(), i18n("Filter Layer Properties"), m_view->mainWindow(), "dlgadjlayerprops"); dlg.resize(dlg.minimumSizeHint()); KisFilterConfigurationSP configBefore(adjustmentLayer->filter()); KIS_ASSERT_RECOVER_RETURN(configBefore); QString xmlBefore = configBefore->toXML(); if (dlg.exec() == QDialog::Accepted) { adjustmentLayer->setName(dlg.layerName()); KisFilterConfigurationSP configAfter(dlg.filterConfiguration()); Q_ASSERT(configAfter); QString xmlAfter = configAfter->toXML(); if(xmlBefore != xmlAfter) { KisChangeFilterCmd *cmd = new KisChangeFilterCmd(adjustmentLayer, configBefore->name(), xmlBefore, configAfter->name(), xmlAfter, false); // FIXME: check whether is needed cmd->redo(); m_view->undoAdapter()->addCommand(cmd); m_view->document()->setModified(true); } } else { KisFilterConfigurationSP configAfter(dlg.filterConfiguration()); Q_ASSERT(configAfter); QString xmlAfter = configAfter->toXML(); if(xmlBefore != xmlAfter) { adjustmentLayer->setFilter(KisFilterRegistry::instance()->cloneConfiguration(configBefore.data())); adjustmentLayer->setDirty(); } } } else if (generatorLayer && !multipleLayersSelected) { KisFilterConfigurationSP configBefore(generatorLayer->filter()); Q_ASSERT(configBefore); QString xmlBefore = configBefore->toXML(); KisDlgGeneratorLayer *dlg = new KisDlgGeneratorLayer(generatorLayer->name(), m_view, m_view->mainWindow(), generatorLayer, configBefore); dlg->setCaption(i18n("Fill Layer Properties")); dlg->setAttribute(Qt::WA_DeleteOnClose); dlg->setConfiguration(configBefore.data()); dlg->resize(dlg->minimumSizeHint()); Qt::WindowFlags flags = dlg->windowFlags(); dlg->setWindowFlags(flags | Qt::Tool | Qt::Dialog); dlg->show(); } else if (fileLayer && !multipleLayersSelected){ QString basePath = QFileInfo(m_view->document()->url().toLocalFile()).absolutePath(); QString fileNameOld = fileLayer->fileName(); KisFileLayer::ScalingMethod scalingMethodOld = fileLayer->scalingMethod(); KisDlgFileLayer dlg(basePath, fileLayer->name(), m_view->mainWindow()); dlg.setCaption(i18n("File Layer Properties")); dlg.setFileName(fileNameOld); dlg.setScalingMethod(scalingMethodOld); if (dlg.exec() == QDialog::Accepted) { const QString fileNameNew = dlg.fileName(); KisFileLayer::ScalingMethod scalingMethodNew = dlg.scaleToImageResolution(); if(fileNameNew.isEmpty()){ QMessageBox::critical(m_view->mainWindow(), i18nc("@title:window", "Krita"), i18n("No file name specified")); return; } fileLayer->setName(dlg.layerName()); if (fileNameOld!= fileNameNew || scalingMethodOld != scalingMethodNew) { KisChangeFileLayerCmd *cmd = new KisChangeFileLayerCmd(fileLayer, basePath, fileNameOld, scalingMethodOld, basePath, fileNameNew, scalingMethodNew); m_view->undoAdapter()->addCommand(cmd); } } } else { // If layer == normal painting layer, vector layer, or group layer QList selectedNodes = m_view->nodeManager()->selectedNodes(); KisDlgLayerProperties *dialog = new KisDlgLayerProperties(selectedNodes, m_view); dialog->resize(dialog->minimumSizeHint()); dialog->setAttribute(Qt::WA_DeleteOnClose); Qt::WindowFlags flags = dialog->windowFlags(); dialog->setWindowFlags(flags | Qt::Tool | Qt::Dialog); dialog->show(); } } void KisLayerManager::changeCloneSource() { QList selectedNodes = m_view->nodeManager()->selectedNodes(); if (selectedNodes.isEmpty()) { return; } QList cloneLayers; KisNodeSP node; Q_FOREACH (node, selectedNodes) { KisCloneLayerSP cloneLayer(qobject_cast(node.data())); if (cloneLayer) { cloneLayers << cloneLayer; } } if (cloneLayers.isEmpty()) { return; } KisDlgChangeCloneSource *dialog = new KisDlgChangeCloneSource(cloneLayers, m_view); dialog->setCaption(i18n("Change Clone Layer")); dialog->resize(dialog->minimumSizeHint()); dialog->setAttribute(Qt::WA_DeleteOnClose); Qt::WindowFlags flags = dialog->windowFlags(); dialog->setWindowFlags(flags | Qt::Tool | Qt::Dialog); dialog->show(); } void KisLayerManager::convertNodeToPaintLayer(KisNodeSP source) { KisImageWSP image = m_view->image(); if (!image) return; KisLayer *srcLayer = qobject_cast(source.data()); if (srcLayer && (srcLayer->inherits("KisGroupLayer") || srcLayer->layerStyle() || srcLayer->childCount() > 0)) { image->flattenLayer(srcLayer); return; } KisPaintDeviceSP srcDevice = source->paintDevice() ? source->projection() : source->original(); bool putBehind = false; QString newCompositeOp = source->compositeOpId(); KisColorizeMask *colorizeMask = dynamic_cast(source.data()); if (colorizeMask) { srcDevice = colorizeMask->coloringProjection(); putBehind = colorizeMask->compositeOpId() == COMPOSITE_BEHIND; if (putBehind) { newCompositeOp = COMPOSITE_OVER; } } if (!srcDevice) return; KisPaintDeviceSP clone; if (*srcDevice->colorSpace() != *srcDevice->compositionSourceColorSpace()) { clone = new KisPaintDevice(srcDevice->compositionSourceColorSpace()); QRect rc(srcDevice->extent()); KisPainter::copyAreaOptimized(rc.topLeft(), srcDevice, clone, rc); } else { clone = new KisPaintDevice(*srcDevice); } KisLayerSP layer = new KisPaintLayer(image, source->name(), source->opacity(), clone); layer->setCompositeOpId(newCompositeOp); KisNodeSP parent = source->parent(); KisNodeSP above = source->prevSibling(); while (parent && !parent->allowAsChild(layer)) { above = above ? above->parent() : source->parent(); parent = above ? above->parent() : 0; } if (putBehind && above == source->parent()) { above = above->prevSibling(); } m_commandsAdapter->beginMacro(kundo2_i18n("Convert to a Paint Layer")); m_commandsAdapter->removeNode(source); m_commandsAdapter->addNode(layer, parent, above); m_commandsAdapter->endMacro(); } void KisLayerManager::convertGroupToAnimated() { KisGroupLayerSP group = dynamic_cast(activeLayer().data()); if (group.isNull()) return; KisPaintLayerSP animatedLayer = new KisPaintLayer(m_view->image(), group->name(), OPACITY_OPAQUE_U8); animatedLayer->enableAnimation(); KisRasterKeyframeChannel *contentChannel = dynamic_cast( animatedLayer->getKeyframeChannel(KisKeyframeChannel::Content.id(), true)); KIS_ASSERT_RECOVER_RETURN(contentChannel); KisNodeSP child = group->firstChild(); int time = 0; while (child) { contentChannel->importFrame(time, child->projection(), NULL); time++; child = child->nextSibling(); } m_commandsAdapter->beginMacro(kundo2_i18n("Convert to an animated layer")); m_commandsAdapter->addNode(animatedLayer, group->parent(), group); m_commandsAdapter->removeNode(group); m_commandsAdapter->endMacro(); } void KisLayerManager::convertLayerToFileLayer(KisNodeSP source) { KisImageSP image = m_view->image(); if (!image) return; QStringList listMimeFilter = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Export); KoDialog dlg; QWidget *page = new QWidget(&dlg); dlg.setMainWidget(page); QBoxLayout *layout = new QVBoxLayout(page); dlg.setWindowTitle(i18n("Save layers to...")); QLabel *lbl = new QLabel(i18n("Choose the location where the layer will be saved to. The new file layer will then reference this location.")); lbl->setWordWrap(true); layout->addWidget(lbl); KisFileNameRequester *urlRequester = new KisFileNameRequester(page); urlRequester->setMode(KoFileDialog::SaveFile); urlRequester->setMimeTypeFilters(listMimeFilter); urlRequester->setFileName(m_view->document()->url().toLocalFile()); if (m_view->document()->url().isLocalFile()) { QFileInfo location = QFileInfo(m_view->document()->url().toLocalFile()).completeBaseName(); location.setFile(location.dir(), location.completeBaseName() + "_" + source->name() + ".png"); urlRequester->setFileName(location.absoluteFilePath()); } else { const QFileInfo location = QFileInfo(QStandardPaths::writableLocation(QStandardPaths::HomeLocation)); const QString proposedFileName = QDir(location.absoluteFilePath()).absoluteFilePath(source->name() + ".png"); urlRequester->setFileName(proposedFileName); } - // We don't want .kra files as file layers, Krita cannot handle the load. - QStringList mimes = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Export); - int i = mimes.indexOf(KIS_MIME_TYPE); - if (i >= 0 && i < mimes.size()) { - mimes.removeAt(i); - } - urlRequester->setMimeTypeFilters(mimes); - layout->addWidget(urlRequester); if (!dlg.exec()) return; QString path = urlRequester->fileName(); if (path.isEmpty()) return; QFileInfo f(path); QString mimeType= KisMimeDatabase::mimeTypeForFile(f.fileName()); if (mimeType.isEmpty()) { mimeType = "image/png"; } QScopedPointer doc(KisPart::instance()->createDocument()); QRect bounds = source->exactBounds(); if (bounds.isEmpty()) { bounds = image->bounds(); } KisImageSP dst = new KisImage(doc->createUndoStore(), image->width(), image->height(), image->projection()->compositionSourceColorSpace(), source->name()); dst->setResolution(image->xRes(), image->yRes()); doc->setFileBatchMode(false); doc->setCurrentImage(dst); KisNodeSP node = source->clone(); dst->addNode(node); dst->initialRefreshGraph(); dst->cropImage(bounds); dst->waitForDone(); bool r = doc->exportDocumentSync(QUrl::fromLocalFile(path), mimeType.toLatin1()); if (!r) { qWarning() << "Converting layer to file layer. path:"<< path << "gave errors" << doc->errorMessage(); } else { QString basePath = QFileInfo(m_view->document()->url().toLocalFile()).absolutePath(); QString relativePath = QDir(basePath).relativeFilePath(path); KisFileLayer *fileLayer = new KisFileLayer(image, basePath, relativePath, KisFileLayer::None, source->name(), OPACITY_OPAQUE_U8); fileLayer->setX(bounds.x()); fileLayer->setY(bounds.y()); KisNodeSP dstParent = source->parent(); KisNodeSP dstAboveThis = source->prevSibling(); m_commandsAdapter->beginMacro(kundo2_i18n("Convert to a file layer")); m_commandsAdapter->removeNode(source); m_commandsAdapter->addNode(fileLayer, dstParent, dstAboveThis); m_commandsAdapter->endMacro(); } doc->closeUrl(false); } void KisLayerManager::adjustLayerPosition(KisNodeSP node, KisNodeSP activeNode, KisNodeSP &parent, KisNodeSP &above) { Q_ASSERT(activeNode); parent = activeNode; above = parent->lastChild(); if (parent->inherits("KisGroupLayer") && parent->collapsed()) { above = parent; parent = parent->parent(); return; } while (parent && (!parent->allowAsChild(node) || parent->userLocked())) { above = parent; parent = parent->parent(); } if (!parent) { warnKrita << "KisLayerManager::adjustLayerPosition:" << "No node accepted newly created node"; parent = m_view->image()->root(); above = parent->lastChild(); } } void KisLayerManager::addLayerCommon(KisNodeSP activeNode, KisNodeSP layer, bool updateImage, KisProcessingApplicator *applicator) { KisNodeSP parent; KisNodeSP above; adjustLayerPosition(layer, activeNode, parent, above); KisGroupLayer *group = dynamic_cast(parent.data()); const bool parentForceUpdate = group && !group->projectionIsValid(); updateImage |= parentForceUpdate; m_commandsAdapter->addNodeAsync(layer, parent, above, updateImage, updateImage, applicator); } KisLayerSP KisLayerManager::addPaintLayer(KisNodeSP activeNode) { KisImageWSP image = m_view->image(); KisLayerSP layer = new KisPaintLayer(image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8, image->colorSpace()); addLayerCommon(activeNode, layer, false, 0); return layer; } KisNodeSP KisLayerManager::addGroupLayer(KisNodeSP activeNode) { KisImageWSP image = m_view->image(); KisGroupLayerSP group = new KisGroupLayer(image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8); addLayerCommon(activeNode, group, false, 0); return group; } KisNodeSP KisLayerManager::addCloneLayer(KisNodeList nodes) { KisImageWSP image = m_view->image(); KisNodeList filteredNodes = KisLayerUtils::sortAndFilterMergableInternalNodes(nodes, false); if (filteredNodes.isEmpty()) return KisNodeSP(); KisNodeSP newAbove = filteredNodes.last(); KisNodeSP node, lastClonedNode; Q_FOREACH (node, filteredNodes) { lastClonedNode = new KisCloneLayer(qobject_cast(node.data()), image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8); addLayerCommon(newAbove, lastClonedNode, true, 0 ); } return lastClonedNode; } KisNodeSP KisLayerManager::addShapeLayer(KisNodeSP activeNode) { if (!m_view) return 0; if (!m_view->document()) return 0; KisImageWSP image = m_view->image(); KisShapeLayerSP layer = new KisShapeLayer(m_view->document()->shapeController(), image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8); addLayerCommon(activeNode, layer, false, 0); return layer; } KisNodeSP KisLayerManager::addAdjustmentLayer(KisNodeSP activeNode) { KisImageWSP image = m_view->image(); KisSelectionSP selection = m_view->selection(); KisProcessingApplicator applicator(image, 0, KisProcessingApplicator::NONE, KisImageSignalVector() << ModifiedSignal, kundo2_i18n("Add Layer")); KisAdjustmentLayerSP adjl = addAdjustmentLayer(activeNode, QString(), 0, selection, &applicator); KisPaintDeviceSP previewDevice = new KisPaintDevice(*adjl->original()); KisDlgAdjustmentLayer dlg(adjl, adjl.data(), previewDevice, image->nextLayerName(), i18n("New Filter Layer"), m_view, qApp->activeWindow()); dlg.resize(dlg.minimumSizeHint()); // ensure that the device may be free'd by the dialog // when it is not needed anymore previewDevice = 0; if (dlg.exec() != QDialog::Accepted || adjl->filter().isNull()) { // XXX: add messagebox warning if there's no filter set! applicator.cancel(); } else { adjl->setName(dlg.layerName()); applicator.end(); } return adjl; } KisAdjustmentLayerSP KisLayerManager::addAdjustmentLayer(KisNodeSP activeNode, const QString & name, KisFilterConfigurationSP filter, KisSelectionSP selection, KisProcessingApplicator *applicator) { KisImageWSP image = m_view->image(); KisAdjustmentLayerSP layer = new KisAdjustmentLayer(image, name, filter, selection); addLayerCommon(activeNode, layer, true, applicator); return layer; } KisNodeSP KisLayerManager::addGeneratorLayer(KisNodeSP activeNode) { KisImageWSP image = m_view->image(); QColor currentForeground = m_view->canvasResourceProvider()->fgColor().toQColor(); KisDlgGeneratorLayer dlg(image->nextLayerName(), m_view, m_view->mainWindow(), 0, 0); KisFilterConfigurationSP defaultConfig = dlg.configuration(); defaultConfig->setProperty("color", currentForeground); dlg.setConfiguration(defaultConfig); dlg.resize(dlg.minimumSizeHint()); if (dlg.exec() == QDialog::Accepted) { KisSelectionSP selection = m_view->selection(); KisFilterConfigurationSP generator = dlg.configuration(); QString name = dlg.layerName(); KisNodeSP node = new KisGeneratorLayer(image, name, generator, selection); addLayerCommon(activeNode, node, true, 0); return node; } return 0; } void KisLayerManager::flattenImage() { KisImageSP image = m_view->image(); if (!m_view->blockUntilOperationsFinished(image)) return; if (image) { bool doIt = true; if (image->nHiddenLayers() > 0) { int answer = QMessageBox::warning(m_view->mainWindow(), i18nc("@title:window", "Flatten Image"), i18n("The image contains hidden layers that will be lost. Do you want to flatten the image?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No); if (answer != QMessageBox::Yes) { doIt = false; } } if (doIt) { image->flatten(m_view->activeNode()); } } } inline bool isSelectionMask(KisNodeSP node) { return dynamic_cast(node.data()); } bool tryMergeSelectionMasks(KisNodeSP currentNode, KisImageSP image) { bool result = false; KisNodeSP prevNode = currentNode->prevSibling(); if (isSelectionMask(currentNode) && prevNode && isSelectionMask(prevNode)) { QList mergedNodes; mergedNodes.append(currentNode); mergedNodes.append(prevNode); image->mergeMultipleLayers(mergedNodes, currentNode); result = true; } return result; } bool tryFlattenGroupLayer(KisNodeSP currentNode, KisImageSP image) { bool result = false; if (currentNode->inherits("KisGroupLayer")) { KisGroupLayer *layer = qobject_cast(currentNode.data()); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(layer, false); image->flattenLayer(layer); result = true; } return result; } void KisLayerManager::mergeLayer() { KisImageSP image = m_view->image(); if (!image) return; KisLayerSP layer = activeLayer(); if (!layer) return; if (!m_view->blockUntilOperationsFinished(image)) return; QList selectedNodes = m_view->nodeManager()->selectedNodes(); if (selectedNodes.size() > 1) { image->mergeMultipleLayers(selectedNodes, m_view->activeNode()); } else if (tryMergeSelectionMasks(m_view->activeNode(), image)) { // already done! } else if (tryFlattenGroupLayer(m_view->activeNode(), image)) { // already done! } else { if (!layer->prevSibling()) return; KisLayer *prevLayer = qobject_cast(layer->prevSibling().data()); if (!prevLayer) return; if (prevLayer->userLocked()) { m_view->showFloatingMessage( i18nc("floating message in layer manager", "Layer is locked "), QIcon(), 2000, KisFloatingMessage::Low); } else if (layer->metaData()->isEmpty() && prevLayer->metaData()->isEmpty()) { image->mergeDown(layer, KisMetaData::MergeStrategyRegistry::instance()->get("Drop")); } else { const KisMetaData::MergeStrategy* strategy = KisMetaDataMergeStrategyChooserWidget::showDialog(m_view->mainWindow()); if (!strategy) return; image->mergeDown(layer, strategy); } } m_view->updateGUI(); } void KisLayerManager::flattenLayer() { KisImageSP image = m_view->image(); if (!image) return; KisLayerSP layer = activeLayer(); if (!layer) return; if (!m_view->blockUntilOperationsFinished(image)) return; convertNodeToPaintLayer(layer); m_view->updateGUI(); } void KisLayerManager::rasterizeLayer() { KisImageSP image = m_view->image(); if (!image) return; KisLayerSP layer = activeLayer(); if (!layer) return; if (!m_view->blockUntilOperationsFinished(image)) return; KisPaintLayerSP paintLayer = new KisPaintLayer(image, layer->name(), layer->opacity()); KisPainter gc(paintLayer->paintDevice()); QRect rc = layer->projection()->exactBounds(); gc.bitBlt(rc.topLeft(), layer->projection(), rc); m_commandsAdapter->beginMacro(kundo2_i18n("Rasterize Layer")); m_commandsAdapter->addNode(paintLayer.data(), layer->parent().data(), layer.data()); int childCount = layer->childCount(); for (int i = 0; i < childCount; i++) { m_commandsAdapter->moveNode(layer->firstChild(), paintLayer, paintLayer->lastChild()); } m_commandsAdapter->removeNode(layer); m_commandsAdapter->endMacro(); updateGUI(); } void KisLayerManager::layersUpdated() { KisLayerSP layer = activeLayer(); if (!layer) return; m_view->updateGUI(); } void KisLayerManager::saveGroupLayers() { QStringList listMimeFilter = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Export); KoDialog dlg; QWidget *page = new QWidget(&dlg); dlg.setMainWidget(page); QBoxLayout *layout = new QVBoxLayout(page); KisFileNameRequester *urlRequester = new KisFileNameRequester(page); urlRequester->setMode(KoFileDialog::SaveFile); if (m_view->document()->url().isLocalFile()) { urlRequester->setStartDir(QFileInfo(m_view->document()->url().toLocalFile()).absolutePath()); } urlRequester->setMimeTypeFilters(listMimeFilter); urlRequester->setFileName(m_view->document()->url().toLocalFile()); layout->addWidget(urlRequester); QCheckBox *chkInvisible = new QCheckBox(i18n("Convert Invisible Groups"), page); chkInvisible->setChecked(false); layout->addWidget(chkInvisible); QCheckBox *chkDepth = new QCheckBox(i18n("Export Only Toplevel Groups"), page); chkDepth->setChecked(true); layout->addWidget(chkDepth); if (!dlg.exec()) return; QString path = urlRequester->fileName(); if (path.isEmpty()) return; QFileInfo f(path); QString mimeType= KisMimeDatabase::mimeTypeForFile(f.fileName(), false); if (mimeType.isEmpty()) { mimeType = "image/png"; } QString extension = KisMimeDatabase::suffixesForMimeType(mimeType).first(); QString basename = f.completeBaseName(); KisImageSP image = m_view->image(); if (!image) return; KisSaveGroupVisitor v(image, chkInvisible->isChecked(), chkDepth->isChecked(), f.absolutePath(), basename, extension, mimeType); image->rootLayer()->accept(v); } bool KisLayerManager::activeLayerHasSelection() { return (activeLayer()->selection() != 0); } KisNodeSP KisLayerManager::addFileLayer(KisNodeSP activeNode) { QString basePath; QUrl url = m_view->document()->url(); if (url.isLocalFile()) { basePath = QFileInfo(url.toLocalFile()).absolutePath(); } KisImageWSP image = m_view->image(); KisDlgFileLayer dlg(basePath, image->nextLayerName(), m_view->mainWindow()); dlg.resize(dlg.minimumSizeHint()); if (dlg.exec() == QDialog::Accepted) { QString name = dlg.layerName(); QString fileName = dlg.fileName(); if(fileName.isEmpty()){ QMessageBox::critical(m_view->mainWindow(), i18nc("@title:window", "Krita"), i18n("No file name specified")); return 0; } KisFileLayer::ScalingMethod scalingMethod = dlg.scaleToImageResolution(); KisNodeSP node = new KisFileLayer(image, basePath, fileName, scalingMethod, name, OPACITY_OPAQUE_U8); addLayerCommon(activeNode, node, true, 0); return node; } return 0; } void updateLayerStyles(KisLayerSP layer, KisDlgLayerStyle *dlg) { KisSetLayerStyleCommand::updateLayerStyle(layer, dlg->style()->clone()); } void KisLayerManager::layerStyle() { KisImageWSP image = m_view->image(); if (!image) return; KisLayerSP layer = activeLayer(); if (!layer) return; if (!m_view->blockUntilOperationsFinished(image)) return; KisPSDLayerStyleSP oldStyle; if (layer->layerStyle()) { oldStyle = layer->layerStyle()->clone(); } else { oldStyle = toQShared(new KisPSDLayerStyle()); } KisDlgLayerStyle dlg(oldStyle->clone(), m_view->canvasResourceProvider()); std::function updateCall(std::bind(updateLayerStyles, layer, &dlg)); SignalToFunctionProxy proxy(updateCall); connect(&dlg, SIGNAL(configChanged()), &proxy, SLOT(start())); if (dlg.exec() == QDialog::Accepted) { KisPSDLayerStyleSP newStyle = dlg.style(); KUndo2CommandSP command = toQShared( new KisSetLayerStyleCommand(layer, oldStyle, newStyle)); image->postExecutionUndoAdapter()->addCommand(command); } } diff --git a/libs/ui/kis_layer_manager.h b/libs/ui/kis_layer_manager.h index d938ef0cf4..0e93d4eab8 100644 --- a/libs/ui/kis_layer_manager.h +++ b/libs/ui/kis_layer_manager.h @@ -1,136 +1,134 @@ /* * 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_LAYER_MANAGER #define KIS_LAYER_MANAGER #include #include #include #include "kis_adjustment_layer.h" #include "kis_types.h" #include "KisView.h" #include class KisViewManager; class KisNodeCommandsAdapter; class KisAction; class KisActionManager; class KisProcessingApplicator; /** * KisLayerManager takes care of the gui around working with layers: * adding, removing, editing. It also keeps track of the active layer * for this view. */ class KisLayerManager : public QObject { Q_OBJECT public: KisLayerManager(KisViewManager * view); ~KisLayerManager() override; void setView(QPointerview); Q_SIGNALS: - void sigLayerActivated(KisLayerSP layer); - private: friend class KisNodeManager; /** * Activate the specified layer. The layer may be 0. */ void activateLayer(KisLayerSP layer); KisLayerSP activeLayer(); KisPaintDeviceSP activeDevice(); void setup(KisActionManager *actionManager); void updateGUI(); private Q_SLOTS: void mergeLayer(); void imageResizeToActiveLayer(); void trimToImage(); void layerProperties(); void flattenImage(); void flattenLayer(); void rasterizeLayer(); void layersUpdated(); void saveGroupLayers(); bool activeLayerHasSelection(); void convertNodeToPaintLayer(KisNodeSP source); void convertGroupToAnimated(); void convertLayerToFileLayer(KisNodeSP source); KisLayerSP addPaintLayer(KisNodeSP activeNode); KisNodeSP addGroupLayer(KisNodeSP activeNode); KisNodeSP addCloneLayer(KisNodeList nodes); KisNodeSP addShapeLayer(KisNodeSP activeNode); KisNodeSP addAdjustmentLayer(KisNodeSP activeNode); KisAdjustmentLayerSP addAdjustmentLayer(KisNodeSP activeNode, const QString & name, KisFilterConfigurationSP filter, KisSelectionSP selection, KisProcessingApplicator *applicator); KisNodeSP addGeneratorLayer(KisNodeSP activeNode); KisNodeSP addFileLayer(KisNodeSP activeNode); void layerStyle(); void changeCloneSource(); private: void adjustLayerPosition(KisNodeSP node, KisNodeSP activeNode, KisNodeSP &parent, KisNodeSP &above); void addLayerCommon(KisNodeSP activeNode, KisNodeSP layer, bool updateImage = true, KisProcessingApplicator *applicator = 0); private: KisViewManager * m_view; QPointerm_imageView; KisAction *m_imageFlatten; KisAction *m_imageMergeLayer; KisAction *m_groupLayersSave; KisAction *m_convertGroupAnimated; KisAction *m_imageResizeToLayer; KisAction *m_flattenLayer; KisAction *m_rasterizeLayer; KisNodeCommandsAdapter* m_commandsAdapter; KisAction *m_layerStyle; }; #endif diff --git a/libs/ui/kis_node_manager.cpp b/libs/ui/kis_node_manager.cpp index dfb57edb76..138114d2bc 100644 --- a/libs/ui/kis_node_manager.cpp +++ b/libs/ui/kis_node_manager.cpp @@ -1,1582 +1,1580 @@ /* * 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_node_manager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KisPart.h" #include "canvas/kis_canvas2.h" #include "kis_shape_controller.h" #include "kis_canvas_resource_provider.h" #include "KisViewManager.h" #include "KisDocument.h" #include "kis_mask_manager.h" #include "kis_group_layer.h" #include "kis_layer_manager.h" #include "kis_selection_manager.h" #include "kis_node_commands_adapter.h" #include "kis_action.h" #include "kis_action_manager.h" #include "kis_processing_applicator.h" #include "kis_sequential_iterator.h" #include "kis_transaction.h" #include "kis_node_selection_adapter.h" #include "kis_node_insertion_adapter.h" #include "kis_node_juggler_compressed.h" #include "KisNodeDisplayModeAdapter.h" #include "kis_clipboard.h" #include "kis_node_dummies_graph.h" #include "kis_mimedata.h" #include "kis_layer_utils.h" #include "krita_utils.h" #include "kis_shape_layer.h" #include "processing/kis_mirror_processing_visitor.h" #include "KisView.h" #include #include #include struct KisNodeManager::Private { Private(KisNodeManager *_q, KisViewManager *v) : q(_q) , view(v) , imageView(0) , layerManager(v) , maskManager(v) , commandsAdapter(v) , nodeSelectionAdapter(new KisNodeSelectionAdapter(q)) , nodeInsertionAdapter(new KisNodeInsertionAdapter(q)) , nodeDisplayModeAdapter(new KisNodeDisplayModeAdapter()) , lastRequestedIsolatedModeStatus(false) { } KisNodeManager * q; KisViewManager * view; QPointerimageView; KisLayerManager layerManager; KisMaskManager maskManager; KisNodeCommandsAdapter commandsAdapter; QScopedPointer nodeSelectionAdapter; QScopedPointer nodeInsertionAdapter; QScopedPointer nodeDisplayModeAdapter; KisAction *showInTimeline; KisNodeList selectedNodes; QPointer nodeJuggler; KisNodeWSP previouslyActiveNode; bool activateNodeImpl(KisNodeSP node); KisSignalMapper nodeCreationSignalMapper; KisSignalMapper nodeConversionSignalMapper; bool lastRequestedIsolatedModeStatus; void saveDeviceAsImage(KisPaintDeviceSP device, const QString &defaultName, const QRect &bounds, qreal xRes, qreal yRes, quint8 opacity); void mergeTransparencyMaskAsAlpha(bool writeToLayers); KisNodeJugglerCompressed* lazyGetJuggler(const KUndo2MagicString &actionName); }; bool KisNodeManager::Private::activateNodeImpl(KisNodeSP node) { Q_ASSERT(view); Q_ASSERT(view->canvasBase()); Q_ASSERT(view->canvasBase()->globalShapeManager()); Q_ASSERT(imageView); if (node && node == q->activeNode()) { return false; } // Set the selection on the shape manager to the active layer // and set call KoSelection::setActiveLayer( KoShapeLayer* layer ) // with the parent of the active layer. KoSelection *selection = view->canvasBase()->globalShapeManager()->selection(); Q_ASSERT(selection); selection->deselectAll(); if (!node) { selection->setActiveLayer(0); imageView->setCurrentNode(0); maskManager.activateMask(0); layerManager.activateLayer(0); previouslyActiveNode = q->activeNode(); } else { previouslyActiveNode = q->activeNode(); KoShape * shape = view->document()->shapeForNode(node); //if (!shape) return false; KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(shape, false); selection->select(shape); KoShapeLayer * shapeLayer = dynamic_cast(shape); //if (!shapeLayer) return false; KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(shapeLayer, false); // shapeLayer->setGeometryProtected(node->userLocked()); // shapeLayer->setVisible(node->visible()); selection->setActiveLayer(shapeLayer); imageView->setCurrentNode(node); if (KisLayerSP layer = qobject_cast(node.data())) { maskManager.activateMask(0); layerManager.activateLayer(layer); } else if (KisMaskSP mask = dynamic_cast(node.data())) { maskManager.activateMask(mask); // XXX_NODE: for now, masks cannot be nested. layerManager.activateLayer(static_cast(node->parent().data())); } } return true; } KisNodeManager::KisNodeManager(KisViewManager *view) : m_d(new Private(this, view)) { - - connect(&m_d->layerManager, SIGNAL(sigLayerActivated(KisLayerSP)), SIGNAL(sigLayerActivated(KisLayerSP))); } KisNodeManager::~KisNodeManager() { delete m_d; } void KisNodeManager::setView(QPointerimageView) { m_d->maskManager.setView(imageView); m_d->layerManager.setView(imageView); if (m_d->imageView) { KisShapeController *shapeController = dynamic_cast(m_d->imageView->document()->shapeController()); Q_ASSERT(shapeController); shapeController->disconnect(SIGNAL(sigActivateNode(KisNodeSP)), this); m_d->imageView->image()->disconnect(this); } m_d->imageView = imageView; if (m_d->imageView) { KisShapeController *shapeController = dynamic_cast(m_d->imageView->document()->shapeController()); Q_ASSERT(shapeController); connect(shapeController, SIGNAL(sigActivateNode(KisNodeSP)), SLOT(slotNonUiActivatedNode(KisNodeSP))); connect(m_d->imageView->image(), SIGNAL(sigIsolatedModeChanged()),this, SLOT(slotUpdateIsolateModeActionImageStatusChange())); connect(m_d->imageView->image(), SIGNAL(sigRequestNodeReselection(KisNodeSP,KisNodeList)),this, SLOT(slotImageRequestNodeReselection(KisNodeSP,KisNodeList))); m_d->imageView->resourceProvider()->slotNodeActivated(m_d->imageView->currentNode()); } } #define NEW_LAYER_ACTION(id, layerType) \ { \ action = actionManager->createAction(id); \ m_d->nodeCreationSignalMapper.setMapping(action, layerType); \ connect(action, SIGNAL(triggered()), \ &m_d->nodeCreationSignalMapper, SLOT(map())); \ } #define CONVERT_NODE_ACTION_2(id, layerType, exclude) \ { \ action = actionManager->createAction(id); \ action->setExcludedNodeTypes(QStringList(exclude)); \ actionManager->addAction(id, action); \ m_d->nodeConversionSignalMapper.setMapping(action, layerType); \ connect(action, SIGNAL(triggered()), \ &m_d->nodeConversionSignalMapper, SLOT(map())); \ } #define CONVERT_NODE_ACTION(id, layerType) \ CONVERT_NODE_ACTION_2(id, layerType, layerType) void KisNodeManager::setup(KActionCollection * actionCollection, KisActionManager* actionManager) { m_d->layerManager.setup(actionManager); m_d->maskManager.setup(actionCollection, actionManager); KisAction * action = 0; action = actionManager->createAction("mirrorNodeX"); connect(action, SIGNAL(triggered()), this, SLOT(mirrorNodeX())); action = actionManager->createAction("mirrorNodeY"); connect(action, SIGNAL(triggered()), this, SLOT(mirrorNodeY())); action = actionManager->createAction("mirrorAllNodesX"); connect(action, SIGNAL(triggered()), this, SLOT(mirrorAllNodesX())); action = actionManager->createAction("mirrorAllNodesY"); connect(action, SIGNAL(triggered()), this, SLOT(mirrorAllNodesY())); action = actionManager->createAction("activateNextLayer"); connect(action, SIGNAL(triggered()), this, SLOT(activateNextNode())); action = actionManager->createAction("activatePreviousLayer"); connect(action, SIGNAL(triggered()), this, SLOT(activatePreviousNode())); action = actionManager->createAction("switchToPreviouslyActiveNode"); connect(action, SIGNAL(triggered()), this, SLOT(switchToPreviouslyActiveNode())); action = actionManager->createAction("save_node_as_image"); connect(action, SIGNAL(triggered()), this, SLOT(saveNodeAsImage())); action = actionManager->createAction("save_vector_node_to_svg"); connect(action, SIGNAL(triggered()), this, SLOT(saveVectorLayerAsImage())); action->setActivationFlags(KisAction::ACTIVE_SHAPE_LAYER); action = actionManager->createAction("duplicatelayer"); connect(action, SIGNAL(triggered()), this, SLOT(duplicateActiveNode())); action = actionManager->createAction("copy_layer_clipboard"); connect(action, SIGNAL(triggered()), this, SLOT(copyLayersToClipboard())); action = actionManager->createAction("cut_layer_clipboard"); connect(action, SIGNAL(triggered()), this, SLOT(cutLayersToClipboard())); action = actionManager->createAction("paste_layer_from_clipboard"); connect(action, SIGNAL(triggered()), this, SLOT(pasteLayersFromClipboard())); action = actionManager->createAction("create_quick_group"); connect(action, SIGNAL(triggered()), this, SLOT(createQuickGroup())); action = actionManager->createAction("create_quick_clipping_group"); connect(action, SIGNAL(triggered()), this, SLOT(createQuickClippingGroup())); action = actionManager->createAction("quick_ungroup"); connect(action, SIGNAL(triggered()), this, SLOT(quickUngroup())); action = actionManager->createAction("select_all_layers"); connect(action, SIGNAL(triggered()), this, SLOT(selectAllNodes())); action = actionManager->createAction("select_visible_layers"); connect(action, SIGNAL(triggered()), this, SLOT(selectVisibleNodes())); action = actionManager->createAction("select_locked_layers"); connect(action, SIGNAL(triggered()), this, SLOT(selectLockedNodes())); action = actionManager->createAction("select_invisible_layers"); connect(action, SIGNAL(triggered()), this, SLOT(selectInvisibleNodes())); action = actionManager->createAction("select_unlocked_layers"); connect(action, SIGNAL(triggered()), this, SLOT(selectUnlockedNodes())); action = actionManager->createAction("new_from_visible"); connect(action, SIGNAL(triggered()), this, SLOT(createFromVisible())); action = actionManager->createAction("show_in_timeline"); action->setCheckable(true); connect(action, SIGNAL(toggled(bool)), this, SLOT(slotShowHideTimeline(bool))); m_d->showInTimeline = action; NEW_LAYER_ACTION("add_new_paint_layer", "KisPaintLayer"); NEW_LAYER_ACTION("add_new_group_layer", "KisGroupLayer"); NEW_LAYER_ACTION("add_new_clone_layer", "KisCloneLayer"); NEW_LAYER_ACTION("add_new_shape_layer", "KisShapeLayer"); NEW_LAYER_ACTION("add_new_adjustment_layer", "KisAdjustmentLayer"); NEW_LAYER_ACTION("add_new_fill_layer", "KisGeneratorLayer"); NEW_LAYER_ACTION("add_new_file_layer", "KisFileLayer"); NEW_LAYER_ACTION("add_new_transparency_mask", "KisTransparencyMask"); NEW_LAYER_ACTION("add_new_filter_mask", "KisFilterMask"); NEW_LAYER_ACTION("add_new_colorize_mask", "KisColorizeMask"); NEW_LAYER_ACTION("add_new_transform_mask", "KisTransformMask"); NEW_LAYER_ACTION("add_new_selection_mask", "KisSelectionMask"); connect(&m_d->nodeCreationSignalMapper, SIGNAL(mapped(QString)), this, SLOT(createNode(QString))); CONVERT_NODE_ACTION("convert_to_paint_layer", "KisPaintLayer"); CONVERT_NODE_ACTION_2("convert_to_selection_mask", "KisSelectionMask", QStringList() << "KisSelectionMask" << "KisColorizeMask"); CONVERT_NODE_ACTION_2("convert_to_filter_mask", "KisFilterMask", QStringList() << "KisFilterMask" << "KisColorizeMask"); CONVERT_NODE_ACTION_2("convert_to_transparency_mask", "KisTransparencyMask", QStringList() << "KisTransparencyMask" << "KisColorizeMask"); CONVERT_NODE_ACTION("convert_to_animated", "animated"); CONVERT_NODE_ACTION_2("convert_to_file_layer", "KisFileLayer", QStringList() << "KisFileLayer" << "KisCloneLayer"); connect(&m_d->nodeConversionSignalMapper, SIGNAL(mapped(QString)), this, SLOT(convertNode(QString))); action = actionManager->createAction("isolate_layer"); connect(action, SIGNAL(triggered(bool)), this, SLOT(toggleIsolateMode(bool))); action = actionManager->createAction("toggle_layer_visibility"); connect(action, SIGNAL(triggered()), this, SLOT(toggleVisibility())); action = actionManager->createAction("toggle_layer_lock"); connect(action, SIGNAL(triggered()), this, SLOT(toggleLock())); action = actionManager->createAction("toggle_layer_inherit_alpha"); connect(action, SIGNAL(triggered()), this, SLOT(toggleInheritAlpha())); action = actionManager->createAction("toggle_layer_alpha_lock"); connect(action, SIGNAL(triggered()), this, SLOT(toggleAlphaLock())); action = actionManager->createAction("split_alpha_into_mask"); connect(action, SIGNAL(triggered()), this, SLOT(slotSplitAlphaIntoMask())); action = actionManager->createAction("split_alpha_write"); connect(action, SIGNAL(triggered()), this, SLOT(slotSplitAlphaWrite())); // HINT: we can save even when the nodes are not editable action = actionManager->createAction("split_alpha_save_merged"); connect(action, SIGNAL(triggered()), this, SLOT(slotSplitAlphaSaveMerged())); connect(this, SIGNAL(sigNodeActivated(KisNodeSP)), SLOT(slotUpdateIsolateModeAction())); connect(this, SIGNAL(sigNodeActivated(KisNodeSP)), SLOT(slotTryRestartIsolatedMode())); } void KisNodeManager::updateGUI() { // enable/disable all relevant actions m_d->layerManager.updateGUI(); m_d->maskManager.updateGUI(); } KisNodeSP KisNodeManager::activeNode() { if (m_d->imageView) { return m_d->imageView->currentNode(); } return 0; } KisLayerSP KisNodeManager::activeLayer() { return m_d->layerManager.activeLayer(); } const KoColorSpace* KisNodeManager::activeColorSpace() { if (m_d->maskManager.activeDevice()) { return m_d->maskManager.activeDevice()->colorSpace(); } else { Q_ASSERT(m_d->layerManager.activeLayer()); if (m_d->layerManager.activeLayer()->parentLayer()) return m_d->layerManager.activeLayer()->parentLayer()->colorSpace(); else return m_d->view->image()->colorSpace(); } } void KisNodeManager::moveNodeAt(KisNodeSP node, KisNodeSP parent, int index) { if (parent->allowAsChild(node)) { if (node->inherits("KisSelectionMask") && parent->inherits("KisLayer")) { KisSelectionMask *m = dynamic_cast(node.data()); KisLayer *l = qobject_cast(parent.data()); if (m && m->active() && l && l->selectionMask()) { l->selectionMask()->setActive(false); } } m_d->commandsAdapter.moveNode(node, parent, index); } } void KisNodeManager::moveNodesDirect(KisNodeList nodes, KisNodeSP parent, KisNodeSP aboveThis) { KUndo2MagicString actionName = kundo2_i18n("Move Nodes"); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); juggler->moveNode(nodes, parent, aboveThis); } void KisNodeManager::copyNodesDirect(KisNodeList nodes, KisNodeSP parent, KisNodeSP aboveThis) { KUndo2MagicString actionName = kundo2_i18n("Copy Nodes"); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); juggler->copyNode(nodes, parent, aboveThis); } void KisNodeManager::addNodesDirect(KisNodeList nodes, KisNodeSP parent, KisNodeSP aboveThis) { KUndo2MagicString actionName = kundo2_i18n("Add Nodes"); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); juggler->addNode(nodes, parent, aboveThis); } void KisNodeManager::toggleIsolateActiveNode() { KisImageWSP image = m_d->view->image(); KisNodeSP activeNode = this->activeNode(); KIS_ASSERT_RECOVER_RETURN(activeNode); if (activeNode == image->isolatedModeRoot()) { toggleIsolateMode(false); } else { toggleIsolateMode(true); } } void KisNodeManager::toggleIsolateMode(bool checked) { KisImageWSP image = m_d->view->image(); KisNodeSP activeNode = this->activeNode(); if (checked && activeNode) { // Transform and colorize masks don't have pixel data... if (activeNode->inherits("KisTransformMask") || activeNode->inherits("KisColorizeMask")) return; if (!image->startIsolatedMode(activeNode)) { KisAction *action = m_d->view->actionManager()->actionByName("isolate_layer"); action->setChecked(false); } } else { image->stopIsolatedMode(); } m_d->lastRequestedIsolatedModeStatus = checked; } void KisNodeManager::slotUpdateIsolateModeActionImageStatusChange() { slotUpdateIsolateModeAction(); KisNodeSP isolatedRootNode = m_d->view->image()->isolatedModeRoot(); if (this->activeNode() && bool(isolatedRootNode) != m_d->lastRequestedIsolatedModeStatus) { slotTryRestartIsolatedMode(); } } void KisNodeManager::slotUpdateIsolateModeAction() { KisAction *action = m_d->view->actionManager()->actionByName("isolate_layer"); Q_ASSERT(action); KisNodeSP activeNode = this->activeNode(); KisNodeSP isolatedRootNode = m_d->view->image()->isolatedModeRoot(); action->setChecked(isolatedRootNode && isolatedRootNode == activeNode); } void KisNodeManager::slotTryRestartIsolatedMode() { /** * It might be that we have multiple Krita windows open. In such a case * only the currently active one should restart isolated mode */ if (!m_d->view->mainWindow()->isActiveWindow()) return; KisNodeSP isolatedRootNode = m_d->view->image()->isolatedModeRoot(); if (!isolatedRootNode && !m_d->lastRequestedIsolatedModeStatus) return; this->toggleIsolateMode(true); } KisNodeSP KisNodeManager::createNode(const QString & nodeType, bool quiet, KisPaintDeviceSP copyFrom) { if (!m_d->view->blockUntilOperationsFinished(m_d->view->image())) { return 0; } KisNodeSP activeNode = this->activeNode(); if (!activeNode) { activeNode = m_d->view->image()->root(); } KIS_ASSERT_RECOVER_RETURN_VALUE(activeNode, 0); // XXX: make factories for this kind of stuff, // with a registry if (nodeType == "KisPaintLayer") { return m_d->layerManager.addPaintLayer(activeNode); } else if (nodeType == "KisGroupLayer") { return m_d->layerManager.addGroupLayer(activeNode); } else if (nodeType == "KisAdjustmentLayer") { return m_d->layerManager.addAdjustmentLayer(activeNode); } else if (nodeType == "KisGeneratorLayer") { return m_d->layerManager.addGeneratorLayer(activeNode); } else if (nodeType == "KisShapeLayer") { return m_d->layerManager.addShapeLayer(activeNode); } else if (nodeType == "KisCloneLayer") { KisNodeList nodes = selectedNodes(); if (nodes.isEmpty()) { nodes.append(activeNode); } return m_d->layerManager.addCloneLayer(nodes); } else if (nodeType == "KisTransparencyMask") { return m_d->maskManager.createTransparencyMask(activeNode, copyFrom, false); } else if (nodeType == "KisFilterMask") { return m_d->maskManager.createFilterMask(activeNode, copyFrom, quiet, false); } else if (nodeType == "KisColorizeMask") { return m_d->maskManager.createColorizeMask(activeNode); } else if (nodeType == "KisTransformMask") { return m_d->maskManager.createTransformMask(activeNode); } else if (nodeType == "KisSelectionMask") { return m_d->maskManager.createSelectionMask(activeNode, copyFrom, false); } else if (nodeType == "KisFileLayer") { return m_d->layerManager.addFileLayer(activeNode); } return 0; } void KisNodeManager::createFromVisible() { KisLayerUtils::newLayerFromVisible(m_d->view->image(), m_d->view->image()->root()->lastChild()); } void KisNodeManager::slotShowHideTimeline(bool value) { Q_FOREACH (KisNodeSP node, selectedNodes()) { node->setUseInTimeline(value); } } KisLayerSP KisNodeManager::createPaintLayer() { KisNodeSP node = createNode("KisPaintLayer"); return dynamic_cast(node.data()); } void KisNodeManager::convertNode(const QString &nodeType) { if (!m_d->view->blockUntilOperationsFinished(m_d->view->image())) { return; } KisNodeSP activeNode = this->activeNode(); if (!activeNode) return; if (nodeType == "KisPaintLayer") { m_d->layerManager.convertNodeToPaintLayer(activeNode); } else if (nodeType == "KisSelectionMask" || nodeType == "KisFilterMask" || nodeType == "KisTransparencyMask") { KisPaintDeviceSP copyFrom = activeNode->paintDevice() ? activeNode->paintDevice() : activeNode->projection(); m_d->commandsAdapter.beginMacro(kundo2_i18n("Convert to a Selection Mask")); bool result = false; if (nodeType == "KisSelectionMask") { result = !m_d->maskManager.createSelectionMask(activeNode, copyFrom, true).isNull(); } else if (nodeType == "KisFilterMask") { result = !m_d->maskManager.createFilterMask(activeNode, copyFrom, false, true).isNull(); } else if (nodeType == "KisTransparencyMask") { result = !m_d->maskManager.createTransparencyMask(activeNode, copyFrom, true).isNull(); } m_d->commandsAdapter.endMacro(); if (!result) { m_d->view->blockUntilOperationsFinishedForced(m_d->imageView->image()); m_d->commandsAdapter.undoLastCommand(); } } else if (nodeType == "KisFileLayer") { m_d->layerManager.convertLayerToFileLayer(activeNode); } else { warnKrita << "Unsupported node conversion type:" << nodeType; } } void KisNodeManager::slotSomethingActivatedNodeImpl(KisNodeSP node) { KisDummiesFacadeBase *dummiesFacade = dynamic_cast(m_d->imageView->document()->shapeController()); KIS_SAFE_ASSERT_RECOVER_RETURN(dummiesFacade); const bool nodeVisible = !isNodeHidden(node, !m_d->nodeDisplayModeAdapter->showGlobalSelectionMask()); if (!nodeVisible) { return; } KIS_ASSERT_RECOVER_RETURN(node != activeNode()); if (m_d->activateNodeImpl(node)) { emit sigUiNeedChangeActiveNode(node); emit sigNodeActivated(node); nodesUpdated(); if (node) { bool toggled = m_d->view->actionCollection()->action("view_show_canvas_only")->isChecked(); if (toggled) { m_d->view->showFloatingMessage( activeLayer()->name(), QIcon(), 1600, KisFloatingMessage::Medium, Qt::TextSingleLine); } } } } void KisNodeManager::slotNonUiActivatedNode(KisNodeSP node) { // the node must still be in the graph, some asynchronous // signals may easily break this requirement if (node && !node->graphListener()) { node = 0; } if (node == activeNode()) return; slotSomethingActivatedNodeImpl(node); if (node) { bool toggled = m_d->view->actionCollection()->action("view_show_canvas_only")->isChecked(); if (toggled) { m_d->view->showFloatingMessage( activeLayer()->name(), QIcon(), 1600, KisFloatingMessage::Medium, Qt::TextSingleLine); } } } void KisNodeManager::slotUiActivatedNode(KisNodeSP node) { // the node must still be in the graph, some asynchronous // signals may easily break this requirement if (node && !node->graphListener()) { node = 0; } if (node) { QStringList vectorTools = QStringList() << "InteractionTool" << "KarbonPatternTool" << "KarbonGradientTool" << "KarbonCalligraphyTool" << "CreateShapesTool" << "PathTool"; QStringList pixelTools = QStringList() << "KritaShape/KisToolBrush" << "KritaShape/KisToolDyna" << "KritaShape/KisToolMultiBrush" << "KritaFill/KisToolFill" << "KritaFill/KisToolGradient"; KisSelectionMask *selectionMask = dynamic_cast(node.data()); const bool nodeHasVectorAbilities = node->inherits("KisShapeLayer") || (selectionMask && selectionMask->selection()->hasShapeSelection()); if (nodeHasVectorAbilities) { if (pixelTools.contains(KoToolManager::instance()->activeToolId())) { KoToolManager::instance()->switchToolRequested("InteractionTool"); } } else { if (vectorTools.contains(KoToolManager::instance()->activeToolId())) { KoToolManager::instance()->switchToolRequested("KritaShape/KisToolBrush"); } } } if (node == activeNode()) return; slotSomethingActivatedNodeImpl(node); } void KisNodeManager::nodesUpdated() { KisNodeSP node = activeNode(); if (!node) return; m_d->layerManager.layersUpdated(); m_d->maskManager.masksUpdated(); m_d->view->updateGUI(); m_d->view->selectionManager()->selectionChanged(); { KisSignalsBlocker b(m_d->showInTimeline); m_d->showInTimeline->setChecked(node->useInTimeline()); } } KisPaintDeviceSP KisNodeManager::activePaintDevice() { return m_d->maskManager.activeMask() ? m_d->maskManager.activeDevice() : m_d->layerManager.activeDevice(); } void KisNodeManager::nodeProperties(KisNodeSP node) { if ((selectedNodes().size() > 1 && node->inherits("KisLayer")) || node->inherits("KisLayer")) { m_d->layerManager.layerProperties(); } else if (node->inherits("KisMask")) { m_d->maskManager.maskProperties(); } } void KisNodeManager::changeCloneSource() { m_d->layerManager.changeCloneSource(); } qint32 KisNodeManager::convertOpacityToInt(qreal opacity) { /** * Scales opacity from the range 0...100 * to the integer range 0...255 */ return qMin(255, int(opacity * 2.55 + 0.5)); } void KisNodeManager::setNodeName(KisNodeSP node, const QString &name) { if (!node) return; if (node->name() == name) return; m_d->commandsAdapter.setNodeName(node, name); } void KisNodeManager::setNodeOpacity(KisNodeSP node, qint32 opacity) { if (!node) return; if (node->opacity() == opacity) return; m_d->commandsAdapter.setOpacity(node, opacity); } void KisNodeManager::setNodeCompositeOp(KisNodeSP node, const KoCompositeOp* compositeOp) { if (!node) return; if (node->compositeOp() == compositeOp) return; m_d->commandsAdapter.setCompositeOp(node, compositeOp); } void KisNodeManager::slotImageRequestNodeReselection(KisNodeSP activeNode, const KisNodeList &selectedNodes) { if (activeNode) { slotNonUiActivatedNode(activeNode); } if (!selectedNodes.isEmpty()) { slotSetSelectedNodes(selectedNodes); } } void KisNodeManager::slotSetSelectedNodes(const KisNodeList &nodes) { m_d->selectedNodes = nodes; emit sigUiNeedChangeSelectedNodes(nodes); } KisNodeList KisNodeManager::selectedNodes() { return m_d->selectedNodes; } KisNodeSelectionAdapter* KisNodeManager::nodeSelectionAdapter() const { return m_d->nodeSelectionAdapter.data(); } KisNodeInsertionAdapter* KisNodeManager::nodeInsertionAdapter() const { return m_d->nodeInsertionAdapter.data(); } KisNodeDisplayModeAdapter *KisNodeManager::nodeDisplayModeAdapter() const { return m_d->nodeDisplayModeAdapter.data(); } bool KisNodeManager::isNodeHidden(KisNodeSP node, bool isGlobalSelectionHidden) { if (node && node->isFakeNode()) { return true; } if (isGlobalSelectionHidden && dynamic_cast(node.data()) && (!node->parent() || !node->parent()->parent())) { return true; } return false; } bool KisNodeManager::trySetNodeProperties(KisNodeSP node, KisImageSP image, KisBaseNode::PropertyList properties) const { const KisPaintLayer *paintLayer = dynamic_cast(node.data()); if (paintLayer) { const auto onionSkinOn = KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::onionSkins, true); if (properties.contains(onionSkinOn)) { const KisPaintDeviceSP &paintDevice = paintLayer->paintDevice(); if (paintDevice && paintDevice->defaultPixel().opacityU8() == 255) { m_d->view->showFloatingMessage(i18n("Onion skins require a layer with transparent background."), QIcon()); return false; } } } KisNodePropertyListCommand::setNodePropertiesNoUndo(node, image, properties); return true; } void KisNodeManager::nodeOpacityChanged(qreal opacity) { KisNodeSP node = activeNode(); setNodeOpacity(node, convertOpacityToInt(opacity)); } void KisNodeManager::nodeCompositeOpChanged(const KoCompositeOp* op) { KisNodeSP node = activeNode(); setNodeCompositeOp(node, op); } void KisNodeManager::duplicateActiveNode() { KUndo2MagicString actionName = kundo2_i18n("Duplicate Nodes"); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); juggler->duplicateNode(selectedNodes()); } KisNodeJugglerCompressed* KisNodeManager::Private::lazyGetJuggler(const KUndo2MagicString &actionName) { KisImageWSP image = view->image(); if (!nodeJuggler || (nodeJuggler && (nodeJuggler->isEnded() || !nodeJuggler->canMergeAction(actionName)))) { nodeJuggler = new KisNodeJugglerCompressed(actionName, image, q, 750); nodeJuggler->setAutoDelete(true); } return nodeJuggler; } void KisNodeManager::raiseNode() { KUndo2MagicString actionName = kundo2_i18n("Raise Nodes"); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); juggler->raiseNode(selectedNodes()); } void KisNodeManager::lowerNode() { KUndo2MagicString actionName = kundo2_i18n("Lower Nodes"); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); juggler->lowerNode(selectedNodes()); } void KisNodeManager::removeSingleNode(KisNodeSP node) { if (!node || !node->parent()) { return; } KisNodeList nodes; nodes << node; removeSelectedNodes(nodes); } void KisNodeManager::removeSelectedNodes(KisNodeList nodes) { KUndo2MagicString actionName = kundo2_i18n("Remove Nodes"); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); juggler->removeNode(nodes); } void KisNodeManager::removeNode() { removeSelectedNodes(selectedNodes()); } void KisNodeManager::mirrorNodeX() { KisNodeSP node = activeNode(); KUndo2MagicString commandName; if (node->inherits("KisLayer")) { commandName = kundo2_i18n("Mirror Layer X"); } else if (node->inherits("KisMask")) { commandName = kundo2_i18n("Mirror Mask X"); } mirrorNode(node, commandName, Qt::Horizontal, m_d->view->selection()); } void KisNodeManager::mirrorNodeY() { KisNodeSP node = activeNode(); KUndo2MagicString commandName; if (node->inherits("KisLayer")) { commandName = kundo2_i18n("Mirror Layer Y"); } else if (node->inherits("KisMask")) { commandName = kundo2_i18n("Mirror Mask Y"); } mirrorNode(node, commandName, Qt::Vertical, m_d->view->selection()); } void KisNodeManager::mirrorAllNodesX() { KisNodeSP node = m_d->view->image()->root(); mirrorNode(node, kundo2_i18n("Mirror All Layers X"), Qt::Horizontal, m_d->view->selection()); } void KisNodeManager::mirrorAllNodesY() { KisNodeSP node = m_d->view->image()->root(); mirrorNode(node, kundo2_i18n("Mirror All Layers Y"), Qt::Vertical, m_d->view->selection()); } void KisNodeManager::activateNextNode() { KisNodeSP activeNode = this->activeNode(); if (!activeNode) return; KisNodeSP node = activeNode->nextSibling(); while (node && node->childCount() > 0) { node = node->firstChild(); } if (!node && activeNode->parent() && activeNode->parent()->parent()) { node = activeNode->parent(); } while(node && isNodeHidden(node, true)) { node = node->nextSibling(); } if (node) { slotNonUiActivatedNode(node); } } void KisNodeManager::activatePreviousNode() { KisNodeSP activeNode = this->activeNode(); if (!activeNode) return; KisNodeSP node; if (activeNode->childCount() > 0) { node = activeNode->lastChild(); } else { node = activeNode->prevSibling(); } while (!node && activeNode->parent()) { node = activeNode->parent()->prevSibling(); activeNode = activeNode->parent(); } while(node && isNodeHidden(node, true)) { node = node->prevSibling(); } if (node) { slotNonUiActivatedNode(node); } } void KisNodeManager::switchToPreviouslyActiveNode() { if (m_d->previouslyActiveNode && m_d->previouslyActiveNode->parent()) { slotNonUiActivatedNode(m_d->previouslyActiveNode); } } void KisNodeManager::mirrorNode(KisNodeSP node, const KUndo2MagicString& actionName, Qt::Orientation orientation, KisSelectionSP selection) { KisImageSignalVector emitSignals; emitSignals << ModifiedSignal; KisProcessingApplicator applicator(m_d->view->image(), node, KisProcessingApplicator::RECURSIVE, emitSignals, actionName); KisProcessingVisitorSP visitor; if (selection) { visitor = new KisMirrorProcessingVisitor(selection, orientation); } else { visitor = new KisMirrorProcessingVisitor(m_d->view->image()->bounds(), orientation); } if (!selection) { applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); } else { applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT); } applicator.end(); nodesUpdated(); } void KisNodeManager::Private::saveDeviceAsImage(KisPaintDeviceSP device, const QString &defaultName, const QRect &bounds, qreal xRes, qreal yRes, quint8 opacity) { KoFileDialog dialog(view->mainWindow(), KoFileDialog::SaveFile, "savenodeasimage"); dialog.setCaption(i18n("Export \"%1\"", defaultName)); dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)); dialog.setMimeTypeFilters(KisImportExportManager::supportedMimeTypes(KisImportExportManager::Export)); QString filename = dialog.filename(); if (filename.isEmpty()) return; QUrl url = QUrl::fromLocalFile(filename); if (url.isEmpty()) return; QString mimefilter = KisMimeDatabase::mimeTypeForFile(filename, false); QScopedPointer doc(KisPart::instance()->createDocument()); KisImageSP dst = new KisImage(doc->createUndoStore(), bounds.width(), bounds.height(), device->compositionSourceColorSpace(), defaultName); dst->setResolution(xRes, yRes); doc->setCurrentImage(dst); KisPaintLayer* paintLayer = new KisPaintLayer(dst, "paint device", opacity); paintLayer->paintDevice()->makeCloneFrom(device, bounds); dst->addNode(paintLayer, dst->rootLayer(), KisLayerSP(0)); dst->initialRefreshGraph(); if (!doc->exportDocumentSync(url, mimefilter.toLatin1())) { QMessageBox::warning(0, i18nc("@title:window", "Krita"), i18n("Could not save the layer. %1", doc->errorMessage().toUtf8().data()), QMessageBox::Ok); } } void KisNodeManager::saveNodeAsImage() { KisNodeSP node = activeNode(); if (!node) { warnKrita << "BUG: Save Node As Image was called without any node selected"; return; } KisImageWSP image = m_d->view->image(); QRect saveRect = image->bounds() | node->exactBounds(); m_d->saveDeviceAsImage(node->projection(), node->name(), saveRect, image->xRes(), image->yRes(), node->opacity()); } #include "SvgWriter.h" void KisNodeManager::saveVectorLayerAsImage() { KisShapeLayerSP shapeLayer = qobject_cast(activeNode().data()); if (!shapeLayer) { return; } KoFileDialog dialog(m_d->view->mainWindow(), KoFileDialog::SaveFile, "savenodeasimage"); dialog.setCaption(i18nc("@title:window", "Export to SVG")); dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)); dialog.setMimeTypeFilters(QStringList() << "image/svg+xml", "image/svg+xml"); QString filename = dialog.filename(); if (filename.isEmpty()) return; QUrl url = QUrl::fromLocalFile(filename); if (url.isEmpty()) return; const QSizeF sizeInPx = m_d->view->image()->bounds().size(); const QSizeF sizeInPt(sizeInPx.width() / m_d->view->image()->xRes(), sizeInPx.height() / m_d->view->image()->yRes()); QList shapes = shapeLayer->shapes(); std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); SvgWriter writer(shapes); if (!writer.save(filename, sizeInPt, true)) { QMessageBox::warning(qApp->activeWindow(), i18nc("@title:window", "Krita"), i18n("Could not save to svg: %1", filename)); } } void KisNodeManager::slotSplitAlphaIntoMask() { KisNodeSP node = activeNode(); // guaranteed by KisActionManager KIS_ASSERT_RECOVER_RETURN(node->hasEditablePaintDevice()); KisPaintDeviceSP srcDevice = node->paintDevice(); const KoColorSpace *srcCS = srcDevice->colorSpace(); const QRect processRect = srcDevice->exactBounds() | srcDevice->defaultBounds()->bounds(); KisPaintDeviceSP selectionDevice = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8()); m_d->commandsAdapter.beginMacro(kundo2_i18n("Split Alpha into a Mask")); KisTransaction transaction(kundo2_noi18n("__split_alpha_channel__"), srcDevice); KisSequentialIterator srcIt(srcDevice, processRect); KisSequentialIterator dstIt(selectionDevice, processRect); while (srcIt.nextPixel() && dstIt.nextPixel()) { quint8 *srcPtr = srcIt.rawData(); quint8 *alpha8Ptr = dstIt.rawData(); *alpha8Ptr = srcCS->opacityU8(srcPtr); srcCS->setOpacity(srcPtr, OPACITY_OPAQUE_U8, 1); } m_d->commandsAdapter.addExtraCommand(transaction.endAndTake()); createNode("KisTransparencyMask", false, selectionDevice); m_d->commandsAdapter.endMacro(); } void KisNodeManager::Private::mergeTransparencyMaskAsAlpha(bool writeToLayers) { KisNodeSP node = q->activeNode(); KisNodeSP parentNode = node->parent(); // guaranteed by KisActionManager KIS_ASSERT_RECOVER_RETURN(node->inherits("KisTransparencyMask")); if (writeToLayers && !parentNode->hasEditablePaintDevice()) { QMessageBox::information(view->mainWindow(), i18nc("@title:window", "Layer %1 is not editable", parentNode->name()), i18n("Cannot write alpha channel of " "the parent layer \"%1\".\n" "The operation will be cancelled.", parentNode->name())); return; } KisPaintDeviceSP dstDevice; if (writeToLayers) { KIS_ASSERT_RECOVER_RETURN(parentNode->paintDevice()); dstDevice = parentNode->paintDevice(); } else { KisPaintDeviceSP copyDevice = parentNode->paintDevice(); if (!copyDevice) { copyDevice = parentNode->original(); } dstDevice = new KisPaintDevice(*copyDevice); } const KoColorSpace *dstCS = dstDevice->colorSpace(); KisPaintDeviceSP selectionDevice = node->paintDevice(); KIS_ASSERT_RECOVER_RETURN(selectionDevice->colorSpace()->pixelSize() == 1); const QRect processRect = selectionDevice->exactBounds() | dstDevice->exactBounds() | selectionDevice->defaultBounds()->bounds(); QScopedPointer transaction; if (writeToLayers) { commandsAdapter.beginMacro(kundo2_i18n("Write Alpha into a Layer")); transaction.reset(new KisTransaction(kundo2_noi18n("__write_alpha_channel__"), dstDevice)); } KisSequentialIterator srcIt(selectionDevice, processRect); KisSequentialIterator dstIt(dstDevice, processRect); while (srcIt.nextPixel() && dstIt.nextPixel()) { quint8 *alpha8Ptr = srcIt.rawData(); quint8 *dstPtr = dstIt.rawData(); dstCS->setOpacity(dstPtr, *alpha8Ptr, 1); } if (writeToLayers) { commandsAdapter.addExtraCommand(transaction->endAndTake()); commandsAdapter.removeNode(node); commandsAdapter.endMacro(); } else { KisImageWSP image = view->image(); QRect saveRect = image->bounds(); saveDeviceAsImage(dstDevice, parentNode->name(), saveRect, image->xRes(), image->yRes(), OPACITY_OPAQUE_U8); } } void KisNodeManager::slotSplitAlphaWrite() { m_d->mergeTransparencyMaskAsAlpha(true); } void KisNodeManager::slotSplitAlphaSaveMerged() { m_d->mergeTransparencyMaskAsAlpha(false); } void KisNodeManager::toggleLock() { KisNodeList nodes = this->selectedNodes(); KisNodeSP active = activeNode(); if (nodes.isEmpty() || !active) return; bool isLocked = active->userLocked(); for (auto &node : nodes) { node->setUserLocked(!isLocked); } } void KisNodeManager::toggleVisibility() { KisNodeList nodes = this->selectedNodes(); KisNodeSP active = activeNode(); if (nodes.isEmpty() || !active) return; bool isVisible = active->visible(); for (auto &node : nodes) { node->setVisible(!isVisible); node->setDirty(); } } void KisNodeManager::toggleAlphaLock() { KisNodeList nodes = this->selectedNodes(); KisNodeSP active = activeNode(); if (nodes.isEmpty() || !active) return; auto layer = qobject_cast(active.data()); if (!layer) { return; } bool isAlphaLocked = layer->alphaLocked(); for (auto &node : nodes) { auto layer = qobject_cast(node.data()); if (layer) { layer->setAlphaLocked(!isAlphaLocked); } } } void KisNodeManager::toggleInheritAlpha() { KisNodeList nodes = this->selectedNodes(); KisNodeSP active = activeNode(); if (nodes.isEmpty() || !active) return; auto layer = qobject_cast(active.data()); if (!layer) { return; } bool isAlphaDisabled = layer->alphaChannelDisabled(); for (auto &node : nodes) { auto layer = qobject_cast(node.data()); if (layer) { layer->disableAlphaChannel(!isAlphaDisabled); node->setDirty(); } } } void KisNodeManager::cutLayersToClipboard() { KisNodeList nodes = this->selectedNodes(); if (nodes.isEmpty()) return; KisClipboard::instance()->setLayers(nodes, m_d->view->image(), false); KUndo2MagicString actionName = kundo2_i18n("Cut Nodes"); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); juggler->removeNode(nodes); } void KisNodeManager::copyLayersToClipboard() { KisNodeList nodes = this->selectedNodes(); KisClipboard::instance()->setLayers(nodes, m_d->view->image(), true); } void KisNodeManager::pasteLayersFromClipboard() { const QMimeData *data = KisClipboard::instance()->layersMimeData(); if (!data) return; KisNodeSP activeNode = this->activeNode(); KisShapeController *shapeController = dynamic_cast(m_d->imageView->document()->shapeController()); Q_ASSERT(shapeController); KisDummiesFacadeBase *dummiesFacade = dynamic_cast(m_d->imageView->document()->shapeController()); Q_ASSERT(dummiesFacade); const bool copyNode = false; KisImageSP image = m_d->view->image(); KisNodeDummy *parentDummy = dummiesFacade->dummyForNode(activeNode); KisNodeDummy *aboveThisDummy = parentDummy ? parentDummy->lastChild() : 0; KisMimeData::insertMimeLayers(data, image, shapeController, parentDummy, aboveThisDummy, copyNode, nodeInsertionAdapter()); } void KisNodeManager::createQuickGroupImpl(KisNodeJugglerCompressed *juggler, const QString &overrideGroupName, KisNodeSP *newGroup, KisNodeSP *newLastChild) { KisNodeSP active = activeNode(); if (!active) return; KisImageSP image = m_d->view->image(); QString groupName = !overrideGroupName.isEmpty() ? overrideGroupName : image->nextLayerName(); KisGroupLayerSP group = new KisGroupLayer(image.data(), groupName, OPACITY_OPAQUE_U8); KisNodeList nodes1; nodes1 << group; KisNodeList nodes2; nodes2 = KisLayerUtils::sortMergableNodes(image->root(), selectedNodes()); KisLayerUtils::filterMergableNodes(nodes2); if (nodes2.size() == 0) return; if (KisLayerUtils::checkIsChildOf(active, nodes2)) { active = nodes2.first(); } KisNodeSP parent = active->parent(); KisNodeSP aboveThis = active; juggler->addNode(nodes1, parent, aboveThis); juggler->moveNode(nodes2, group, 0); *newGroup = group; *newLastChild = nodes2.last(); } void KisNodeManager::createQuickGroup() { KUndo2MagicString actionName = kundo2_i18n("Quick Group"); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); KisNodeSP parent; KisNodeSP above; createQuickGroupImpl(juggler, "", &parent, &above); } void KisNodeManager::createQuickClippingGroup() { KUndo2MagicString actionName = kundo2_i18n("Quick Clipping Group"); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); KisNodeSP parent; KisNodeSP above; KisImageSP image = m_d->view->image(); createQuickGroupImpl(juggler, image->nextLayerName(i18nc("default name for a clipping group layer", "Clipping Group")), &parent, &above); KisPaintLayerSP maskLayer = new KisPaintLayer(image.data(), i18nc("default name for quick clip group mask layer", "Mask Layer"), OPACITY_OPAQUE_U8, image->colorSpace()); maskLayer->disableAlphaChannel(true); juggler->addNode(KisNodeList() << maskLayer, parent, above); } void KisNodeManager::quickUngroup() { KisNodeSP active = activeNode(); if (!active) return; KisNodeSP parent = active->parent(); KisNodeSP aboveThis = active; KUndo2MagicString actionName = kundo2_i18n("Quick Ungroup"); if (parent && dynamic_cast(active.data())) { KisNodeList nodes = active->childNodes(QStringList(), KoProperties()); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); juggler->moveNode(nodes, parent, active); juggler->removeNode(KisNodeList() << active); } else if (parent && parent->parent()) { KisNodeSP grandParent = parent->parent(); KisNodeList allChildNodes = parent->childNodes(QStringList(), KoProperties()); KisNodeList allSelectedNodes = selectedNodes(); const bool removeParent = KritaUtils::compareListsUnordered(allChildNodes, allSelectedNodes); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); juggler->moveNode(allSelectedNodes, grandParent, parent); if (removeParent) { juggler->removeNode(KisNodeList() << parent); } } } void KisNodeManager::selectLayersImpl(const KoProperties &props, const KoProperties &invertedProps) { KisImageSP image = m_d->view->image(); KisNodeList nodes = KisLayerUtils::findNodesWithProps(image->root(), props, true); KisNodeList selectedNodes = this->selectedNodes(); if (KritaUtils::compareListsUnordered(nodes, selectedNodes)) { nodes = KisLayerUtils::findNodesWithProps(image->root(), invertedProps, true); } if (!nodes.isEmpty()) { slotImageRequestNodeReselection(nodes.last(), nodes); } } void KisNodeManager::selectAllNodes() { KoProperties props; selectLayersImpl(props, props); } void KisNodeManager::selectVisibleNodes() { KoProperties props; props.setProperty("visible", true); KoProperties invertedProps; invertedProps.setProperty("visible", false); selectLayersImpl(props, invertedProps); } void KisNodeManager::selectLockedNodes() { KoProperties props; props.setProperty("locked", true); KoProperties invertedProps; invertedProps.setProperty("locked", false); selectLayersImpl(props, invertedProps); } void KisNodeManager::selectInvisibleNodes() { KoProperties props; props.setProperty("visible", false); KoProperties invertedProps; invertedProps.setProperty("visible", true); selectLayersImpl(props, invertedProps); } void KisNodeManager::selectUnlockedNodes() { KoProperties props; props.setProperty("locked", false); KoProperties invertedProps; invertedProps.setProperty("locked", true); selectLayersImpl(props, invertedProps); } diff --git a/libs/ui/kis_node_manager.h b/libs/ui/kis_node_manager.h index 2686269ce7..3926ec87bb 100644 --- a/libs/ui/kis_node_manager.h +++ b/libs/ui/kis_node_manager.h @@ -1,271 +1,268 @@ /* * 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_NODE_MANAGER #define KIS_NODE_MANAGER #include #include #include "kis_types.h" #include "kis_base_node.h" #include class KActionCollection; class KoCompositeOp; class KoColorSpace; class KUndo2MagicString; class KisFilterStrategy; class KisViewManager; class KisActionManager; class KisView; class KisNodeSelectionAdapter; class KisNodeInsertionAdapter; class KisNodeDisplayModeAdapter; class KisNodeJugglerCompressed; class KoProperties; /** * The node manager passes requests for new layers or masks on to the mask and layer * managers. */ class KRITAUI_EXPORT KisNodeManager : public QObject { Q_OBJECT public: KisNodeManager(KisViewManager * view); ~KisNodeManager() override; void setView(QPointerimageView); Q_SIGNALS: /// emitted whenever a node is selected. void sigNodeActivated(KisNodeSP node); - /// emitted whenever a different layer is selected. - void sigLayerActivated(KisLayerSP layer); - /// for the layer box: this sets the current node in the layerbox /// without telling the node manager that the node is activated, /// preventing loops (I think...) void sigUiNeedChangeActiveNode(KisNodeSP node); void sigUiNeedChangeSelectedNodes(const KisNodeList &nodes); public: void setup(KActionCollection * collection, KisActionManager* actionManager); void updateGUI(); /// Convenience function to get the active layer or mask KisNodeSP activeNode(); /// convenience function to get the active layer. If a mask is /// active, it's parent layer is the active layer. KisLayerSP activeLayer(); /// Get the paint device the user wants to paint on now KisPaintDeviceSP activePaintDevice(); /** * @return the active color space used for composition, meaning the color space * of the active mask, or the color space of the parent of the active layer */ const KoColorSpace* activeColorSpace(); /** * Sets the name for the node in a universal way (masks/layers) */ void setNodeName(KisNodeSP node, const QString &name); /** * Sets opacity for the node in a universal way (masks/layers) */ void setNodeOpacity(KisNodeSP node, qint32 opacity); /** * Sets compositeOp for the node in a universal way (masks/layers) */ void setNodeCompositeOp(KisNodeSP node, const KoCompositeOp* compositeOp); KisNodeList selectedNodes(); KisNodeSelectionAdapter* nodeSelectionAdapter() const; KisNodeInsertionAdapter* nodeInsertionAdapter() const; KisNodeDisplayModeAdapter* nodeDisplayModeAdapter() const; static bool isNodeHidden(KisNodeSP node, bool isGlobalSelectionHidden); bool trySetNodeProperties(KisNodeSP node, KisImageSP image, KisBaseNode::PropertyList properties) const; public Q_SLOTS: /** * Explicitly activates \p node * The UI will be noticed that active node has been changed. * Both sigNodeActivated and sigUiNeedChangeActiveNode are emitted. * * WARNING: normally you needn't call this method manually. It is * automatically called when a node is added to the graph. If you * have some special cases when you need to activate a node, consider * adding them to KisDummiesFacadeBase instead. Calling this method * directly should be the last resort. * * \see slotUiActivatedNode for comparison */ void slotNonUiActivatedNode(KisNodeSP node); /** * Activates \p node. * All non-ui listeners are notified with sigNodeActivated, * sigUiNeedChangeActiveNode is *not* emitted. * * \see activateNode */ void slotUiActivatedNode(KisNodeSP node); /** * Adds a list of nodes without searching appropriate position for * it. You *must* ensure that the nodes are allowed to be added * to the parent, otherwise you'll get an assert. */ void addNodesDirect(KisNodeList nodes, KisNodeSP parent, KisNodeSP aboveThis); /** * Moves a list of nodes without searching appropriate position * for it. You *must* ensure that the nodes are allowed to be * added to the parent, otherwise you'll get an assert. */ void moveNodesDirect(KisNodeList nodes, KisNodeSP parent, KisNodeSP aboveThis); /** * Copies a list of nodes without searching appropriate position * for it. You *must* ensure that the nodes are allowed to be * added to the parent, otherwise you'll get an assert. */ void copyNodesDirect(KisNodeList nodes, KisNodeSP parent, KisNodeSP aboveThis); /** * Create new layer from actually visible */ void createFromVisible(); void slotShowHideTimeline(bool value); void toggleIsolateActiveNode(); void toggleIsolateMode(bool checked); void slotUpdateIsolateModeActionImageStatusChange(); void slotUpdateIsolateModeAction(); void slotTryRestartIsolatedMode(); void moveNodeAt(KisNodeSP node, KisNodeSP parent, int index); KisNodeSP createNode(const QString& nodeType, bool quiet = false, KisPaintDeviceSP copyFrom = 0); void convertNode(const QString &nodeType); void nodesUpdated(); void nodeProperties(KisNodeSP node); /// pop up a window for changing the source of the selected Clone Layers void changeCloneSource(); void nodeOpacityChanged(qreal opacity); void nodeCompositeOpChanged(const KoCompositeOp* op); void duplicateActiveNode(); void removeNode(); void mirrorNodeX(); void mirrorNodeY(); void mirrorAllNodesX(); void mirrorAllNodesY(); void mirrorNode(KisNodeSP node, const KUndo2MagicString& commandName, Qt::Orientation orientation, KisSelectionSP selection); void activateNextNode(); void activatePreviousNode(); void switchToPreviouslyActiveNode(); /** * move the active node up the nodestack. */ void raiseNode(); /** * move the active node down the nodestack */ void lowerNode(); void saveNodeAsImage(); void saveVectorLayerAsImage(); void slotSplitAlphaIntoMask(); void slotSplitAlphaWrite(); void slotSplitAlphaSaveMerged(); void toggleLock(); void toggleVisibility(); void toggleAlphaLock(); void toggleInheritAlpha(); /** * @brief slotSetSelectedNodes set the list of nodes selected in the layerbox. Selected nodes are not necessarily active nodes. * @param nodes the selected nodes */ void slotSetSelectedNodes(const KisNodeList &nodes); void slotImageRequestNodeReselection(KisNodeSP activeNode, const KisNodeList &selectedNodes); void cutLayersToClipboard(); void copyLayersToClipboard(); void pasteLayersFromClipboard(); void createQuickGroup(); void createQuickClippingGroup(); void quickUngroup(); void selectAllNodes(); void selectVisibleNodes(); void selectLockedNodes(); void selectInvisibleNodes(); void selectUnlockedNodes(); public: void removeSingleNode(KisNodeSP node); KisLayerSP createPaintLayer(); private: /** * Scales opacity from the range 0...1 * to the integer range 0...255 */ qint32 convertOpacityToInt(qreal opacity); void removeSelectedNodes(KisNodeList selectedNodes); void slotSomethingActivatedNodeImpl(KisNodeSP node); void createQuickGroupImpl(KisNodeJugglerCompressed *juggler, const QString &overrideGroupName, KisNodeSP *newGroup, KisNodeSP *newLastChild); void selectLayersImpl(const KoProperties &props, const KoProperties &invertedProps); struct Private; Private * const m_d; }; #endif diff --git a/libs/ui/kis_node_model.h b/libs/ui/kis_node_model.h index 52d8df1662..9237ad6243 100644 --- a/libs/ui/kis_node_model.h +++ b/libs/ui/kis_node_model.h @@ -1,195 +1,195 @@ /* * 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_NODE_MODEL #define KIS_NODE_MODEL #include "kritaui_export.h" #include #include #include #include #include #include #include class KisDummiesFacadeBase; class KisNodeDummy; class KisShapeController; class KisModelIndexConverterBase; class KisNodeSelectionAdapter; class KisNodeInsertionAdapter; class KisSelectionActionsAdapter; class KisNodeDisplayModeAdapter; class KisNodeManager; /** * KisNodeModel offers a Qt model-view compatible view of the node * hierarchy. The KisNodeView displays a thumbnail and a row of * icon properties for every document section. * * Note that there's a discrepancy between the krita node tree model * and the model Qt wants to see: we hide the root node from Qt. * * The node model also shows an inverse view of the layer tree: we want * the first layer to show up at the bottom. * * See also the Qt documentation for QAbstractItemModel. * This class extends that interface to provide a name and set of toggle * properties (like visible, locked, selected.) * */ class KRITAUI_EXPORT KisNodeModel : public QAbstractItemModel { Q_OBJECT public: /// Extensions to Qt::ItemDataRole. enum ItemDataRole { /// Whether the section is the active one ActiveRole = Qt::UserRole + 1, /// A list of properties the part has. PropertiesRole, /// The aspect ratio of the section as a floating point value: width divided by height. AspectRatioRole, /// Use to communicate a progress report to the section delegate on an action (a value of -1 or a QVariant() disable the progress bar ProgressRole, - /// Speacial activation role which is emitted when the user Atl-clicks on a section + /// Special activation role which is emitted when the user Atl-clicks on a section /// The item is first activated with ActiveRole, then a separate AlternateActiveRole comes AlternateActiveRole, // When a layer is not (recursively) visible, then it should be gayed out ShouldGrayOutRole, // An index of a color label associated with the node ColorLabelIndexRole, // Instruct this model to update all its items' Qt::ItemIsDropEnabled flags in order to // reflect if the item allows an "onto" drop of the given QMimeData*. DropEnabled, // Instructs the model to activate "select opaque" action, // the selection action (of type SelectionAction) value // is passed via QVariant as integer SelectOpaqueRole, // Returns a text explaining why the node has been excluded from // projection rendering. If the node is not excluded, then empty // string is returned DropReasonRole, /// This is to ensure that we can extend the data role in the future, since it's not possible to add a role after BeginThumbnailRole (due to "Hack") ReservedRole = Qt::UserRole + 99, /** * For values of BeginThumbnailRole or higher, a thumbnail of the layer of which neither dimension * is larger than (int) value - (int) BeginThumbnailRole. * This is a hack to work around the fact that Interview doesn't have a nice way to * request thumbnails of arbitrary size. */ BeginThumbnailRole }; public: // from QAbstractItemModel KisNodeModel(QObject * parent); ~KisNodeModel() override; void setDummiesFacade(KisDummiesFacadeBase *dummiesFacade, KisImageWSP image, KisShapeController *shapeController, KisSelectionActionsAdapter *selectionActionsAdapter, KisNodeManager *nodeManager); KisNodeSP nodeFromIndex(const QModelIndex &index) const; QModelIndex indexFromNode(KisNodeSP node) const; bool showGlobalSelection() const; public Q_SLOTS: void setShowGlobalSelection(bool value); public: int rowCount(const QModelIndex &parent = QModelIndex()) const override; int columnCount(const QModelIndex &parent = QModelIndex()) const override; QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; QModelIndex parent(const QModelIndex &index) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; Qt::ItemFlags flags(const QModelIndex &index) const override; bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; QStringList mimeTypes() const override; QMimeData* mimeData(const QModelIndexList & indexes) const override; bool dropMimeData(const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent) override; bool canDropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) const override; Qt::DropActions supportedDragActions() const override; Qt::DropActions supportedDropActions() const override; bool hasDummiesFacade(); static bool belongsToIsolatedGroup(KisImageSP image, KisNodeSP node, KisDummiesFacadeBase *dummiesFacade); Q_SIGNALS: void toggleIsolateActiveNode(); protected Q_SLOTS: void slotBeginInsertDummy(KisNodeDummy *parent, int index, const QString &metaObjectType); void slotEndInsertDummy(KisNodeDummy *dummy); void slotBeginRemoveDummy(KisNodeDummy *dummy); void slotEndRemoveDummy(); void slotDummyChanged(KisNodeDummy *dummy); void slotIsolatedModeChanged(); void slotNodeDisplayModeChanged(bool showRootNode, bool showGlobalSelectionMask); void processUpdateQueue(); void progressPercentageChanged(int, const KisNodeSP); protected: virtual KisModelIndexConverterBase *createIndexConverter(); KisModelIndexConverterBase *indexConverter() const; KisDummiesFacadeBase *dummiesFacade() const; private: friend class KisModelIndexConverter; friend class KisModelIndexConverterShowAll; void connectDummy(KisNodeDummy *dummy, bool needConnect); void connectDummies(KisNodeDummy *dummy, bool needConnect); void resetIndexConverter(); void regenerateItems(KisNodeDummy *dummy); bool belongsToIsolatedGroup(KisNodeSP node) const; void setDropEnabled(const QMimeData *data); void updateDropEnabled(const QList &nodes, QModelIndex parent = QModelIndex()); private: struct Private; Private * const m_d; }; #endif diff --git a/libs/ui/kis_painting_assistant.h b/libs/ui/kis_painting_assistant.h index 1a8b58eff1..e6a4c8c2e5 100644 --- a/libs/ui/kis_painting_assistant.h +++ b/libs/ui/kis_painting_assistant.h @@ -1,250 +1,250 @@ /* * Copyright (c) 2008 Cyrille Berger * Copyright (c) 2017 Scott Petrovic * * 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_PAINTING_ASSISTANT_H_ #define _KIS_PAINTING_ASSISTANT_H_ #include #include #include #include #include #include #include #include #include #include #include class QPainter; class QRect; class QRectF; class KoStore; class KisCoordinatesConverter; class KisCanvas2; class QDomDocument; class QDomElement; #include #include class KisPaintingAssistantHandle; typedef KisSharedPtr KisPaintingAssistantHandleSP; class KisPaintingAssistant; class QPainterPath; enum HandleType { NORMAL, SIDE, CORNER, VANISHING_POINT, ANCHOR }; /** * Represent an handle of the assistant, used to edit the parameters * of an assistants. Handles can be shared between assistants. */ class KRITAUI_EXPORT KisPaintingAssistantHandle : public QPointF, public KisShared { friend class KisPaintingAssistant; public: KisPaintingAssistantHandle(double x, double y); explicit KisPaintingAssistantHandle(QPointF p); KisPaintingAssistantHandle(const KisPaintingAssistantHandle&); ~KisPaintingAssistantHandle(); void mergeWith(KisPaintingAssistantHandleSP); void uncache(); KisPaintingAssistantHandle& operator=(const QPointF&); void setType(char type); char handleType() const; /** * Returns the pointer to the "chief" assistant, * which is supposed to handle transformations of the * handle, when all the assistants are transformed */ KisPaintingAssistant* chiefAssistant() const; private: void registerAssistant(KisPaintingAssistant*); void unregisterAssistant(KisPaintingAssistant*); bool containsAssistant(KisPaintingAssistant*) const; private: struct Private; Private* const d; }; /** * A KisPaintingAssistant is an object that assist the drawing on the canvas. * With this class you can implement virtual equivalent to ruler or compas. */ class KRITAUI_EXPORT KisPaintingAssistant { public: KisPaintingAssistant(const QString& id, const QString& name); virtual ~KisPaintingAssistant(); virtual KisPaintingAssistantSP clone(QMap &handleMap) const = 0; const QString& id() const; const QString& name() const; bool isSnappingActive() const; void setSnappingActive(bool set); /** * Adjust the position given in parameter. * @param point the coordinates in point in the document reference * @param strokeBegin the coordinates of the beginning of the stroke */ virtual QPointF adjustPosition(const QPointF& point, const QPointF& strokeBegin) = 0; virtual void endStroke() { } virtual QPointF getEditorPosition() const = 0; // Returns editor widget position in document-space coordinates. virtual int numHandles() const = 0; void replaceHandle(KisPaintingAssistantHandleSP _handle, KisPaintingAssistantHandleSP _with); void addHandle(KisPaintingAssistantHandleSP handle, HandleType type); QPointF viewportConstrainedEditorPosition(const KisCoordinatesConverter* converter, const QSize editorSize); QColor effectiveAssistantColor() const; bool useCustomColor(); void setUseCustomColor(bool useCustomColor); void setAssistantCustomColor(QColor color); QColor assistantCustomColor(); void setAssistantGlobalColorCache(const QColor &color); virtual void drawAssistant(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter *converter, bool cached = true,KisCanvas2 *canvas=0, bool assistantVisible=true, bool previewVisible=true); void uncache(); const QList& handles() const; QList handles(); const QList& sideHandles() const; QList sideHandles(); QByteArray saveXml( QMap &handleMap); virtual void saveCustomXml(QXmlStreamWriter* xml); //in case specific assistants have custom properties (like vanishing point) void loadXml(KoStore *store, QMap &handleMap, QString path); virtual bool loadCustomXml(QXmlStreamReader* xml); void saveXmlList(QDomDocument& doc, QDomElement& ssistantsElement, int count); void findPerspectiveAssistantHandleLocation(); KisPaintingAssistantHandleSP oppHandleOne(); /** * Get the topLeft, bottomLeft, topRight and BottomRight corners of the assistant * Some assistants like the perspective grid have custom logic built around certain handles */ const KisPaintingAssistantHandleSP topLeft() const; KisPaintingAssistantHandleSP topLeft(); const KisPaintingAssistantHandleSP topRight() const; KisPaintingAssistantHandleSP topRight(); const KisPaintingAssistantHandleSP bottomLeft() const; KisPaintingAssistantHandleSP bottomLeft(); const KisPaintingAssistantHandleSP bottomRight() const; KisPaintingAssistantHandleSP bottomRight(); const KisPaintingAssistantHandleSP topMiddle() const; KisPaintingAssistantHandleSP topMiddle(); const KisPaintingAssistantHandleSP rightMiddle() const; KisPaintingAssistantHandleSP rightMiddle(); const KisPaintingAssistantHandleSP leftMiddle() const; KisPaintingAssistantHandleSP leftMiddle(); const KisPaintingAssistantHandleSP bottomMiddle() const; KisPaintingAssistantHandleSP bottomMiddle(); // calculates whether a point is near one of the corner points of the assistant // returns: a corner point from the perspective assistant if the given node is close // only called once in code when calculating the perspective assistant KisPaintingAssistantHandleSP closestCornerHandleFromPoint(QPointF point); // determines if two points are close to each other // only used by the nodeNearPoint function (perspective grid assistant). bool areTwoPointsClose(const QPointF& pointOne, const QPointF& pointTwo); /// determines if the assistant has enough handles to be considered created /// new assistants get in a "creation" phase where they are currently being made on the canvas /// it will return false if we are in the middle of creating the assistant. virtual bool isAssistantComplete() const; - /// Transform the assistant using the given \p tranform. Please note that \p transform + /// Transform the assistant using the given \p transform. Please note that \p transform /// should be in 'document' coordinate system. /// Used with image-wide transformations. virtual void transform(const QTransform &transform); public: /** * This will render the final output. The drawCache does rendering most of the time so be sure to check that */ void drawPath(QPainter& painter, const QPainterPath& path, bool drawActive=true); void drawPreview(QPainter& painter, const QPainterPath& path); static double norm2(const QPointF& p); protected: explicit KisPaintingAssistant(const KisPaintingAssistant &rhs, QMap &handleMap); virtual QRect boundingRect() const; /// performance layer where the graphics can be drawn from a cache instead of generated every render update virtual void drawCache(QPainter& gc, const KisCoordinatesConverter *converter, bool assistantVisible=true) = 0; void initHandles(QList _handles); QList m_handles; QPointF pixelToView(const QPoint pixelCoords) const; public: /// clones the list of assistants /// the originally shared handles will still be shared /// the cloned assistants do not share any handle with the original assistants static QList cloneAssistantList(const QList &list); private: struct Private; Private* const d; }; /** * Allow to create a painting assistant. */ class KRITAUI_EXPORT KisPaintingAssistantFactory { public: KisPaintingAssistantFactory(); virtual ~KisPaintingAssistantFactory(); virtual QString id() const = 0; virtual QString name() const = 0; virtual KisPaintingAssistant* createPaintingAssistant() const = 0; }; class KRITAUI_EXPORT KisPaintingAssistantFactoryRegistry : public KoGenericRegistry { public: KisPaintingAssistantFactoryRegistry(); ~KisPaintingAssistantFactoryRegistry() override; static KisPaintingAssistantFactoryRegistry* instance(); }; #endif diff --git a/libs/ui/kis_paintop_box.cc b/libs/ui/kis_paintop_box.cc index f47d622bed..1a6e64a9de 100644 --- a/libs/ui/kis_paintop_box.cc +++ b/libs/ui/kis_paintop_box.cc @@ -1,1377 +1,1377 @@ /* * kis_paintop_box.cc - part of KImageShop/Krayon/Krita * * Copyright (c) 2004 Boudewijn Rempt (boud@valdyas.org) * Copyright (c) 2009-2011 Sven Langkamp (sven.langkamp@gmail.com) * Copyright (c) 2010 Lukáš Tvrdý * Copyright (C) 2011 Silvio Heinrich * Copyright (C) 2011 Srikanth Tiyyagura * 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 "kis_paintop_box.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_canvas2.h" #include "kis_node_manager.h" #include "KisViewManager.h" #include "kis_canvas_resource_provider.h" #include "KisResourceServerProvider.h" #include "kis_favorite_resource_manager.h" #include "kis_config.h" #include "kis_popup_button.h" #include "widgets/kis_iconwidget.h" #include "widgets/kis_tool_options_popup.h" #include "widgets/kis_paintop_presets_popup.h" #include "widgets/kis_paintop_presets_chooser_popup.h" #include "widgets/kis_workspace_chooser.h" #include "widgets/kis_paintop_list_widget.h" #include "widgets/kis_slider_spin_box.h" #include "widgets/kis_cmb_composite.h" #include "widgets/kis_widget_chooser.h" #include "tool/kis_tool.h" #include "kis_signals_blocker.h" #include "kis_action_manager.h" #include "KisHighlightedToolButton.h" typedef KoResourceServerSimpleConstruction > KisPaintOpPresetResourceServer; typedef KoResourceServerAdapter > KisPaintOpPresetResourceServerAdapter; KisPaintopBox::KisPaintopBox(KisViewManager *view, QWidget *parent, const char *name) : QWidget(parent) , m_resourceProvider(view->canvasResourceProvider()) , m_optionWidget(0) , m_toolOptionsPopupButton(0) , m_brushEditorPopupButton(0) , m_presetSelectorPopupButton(0) , m_toolOptionsPopup(0) , m_viewManager(view) , m_previousNode(0) , m_currTabletToolID(KoInputDevice::invalid()) , m_presetsEnabled(true) , m_blockUpdate(false) , m_dirtyPresetsEnabled(false) , m_eraserBrushSizeEnabled(false) , m_eraserBrushOpacityEnabled(false) { Q_ASSERT(view != 0); setObjectName(name); KisConfig cfg(true); m_dirtyPresetsEnabled = cfg.useDirtyPresets(); m_eraserBrushSizeEnabled = cfg.useEraserBrushSize(); m_eraserBrushOpacityEnabled = cfg.useEraserBrushOpacity(); KAcceleratorManager::setNoAccel(this); setWindowTitle(i18n("Painter's Toolchest")); m_favoriteResourceManager = new KisFavoriteResourceManager(this); KConfigGroup grp = KSharedConfig::openConfig()->group("krita").group("Toolbar BrushesAndStuff"); int iconsize = grp.readEntry("IconSize", 32); if (!cfg.toolOptionsInDocker()) { m_toolOptionsPopupButton = new KisPopupButton(this); m_toolOptionsPopupButton->setIcon(KisIconUtils::loadIcon("configure")); m_toolOptionsPopupButton->setToolTip(i18n("Tool Settings")); m_toolOptionsPopupButton->setFixedSize(iconsize, iconsize); } m_brushEditorPopupButton = new KisIconWidget(this); m_brushEditorPopupButton->setIcon(KisIconUtils::loadIcon("paintop_settings_02")); m_brushEditorPopupButton->setToolTip(i18n("Edit brush settings")); m_brushEditorPopupButton->setFixedSize(iconsize, iconsize); m_presetSelectorPopupButton = new KisPopupButton(this); m_presetSelectorPopupButton->setIcon(KisIconUtils::loadIcon("paintop_settings_01")); m_presetSelectorPopupButton->setToolTip(i18n("Choose brush preset")); m_presetSelectorPopupButton->setFixedSize(iconsize, iconsize); m_eraseModeButton = new KisHighlightedToolButton(this); m_eraseModeButton->setFixedSize(iconsize, iconsize); m_eraseModeButton->setCheckable(true); m_eraseAction = m_viewManager->actionManager()->createAction("erase_action"); m_eraseModeButton->setDefaultAction(m_eraseAction); m_reloadButton = new QToolButton(this); m_reloadButton->setFixedSize(iconsize, iconsize); m_reloadAction = m_viewManager->actionManager()->createAction("reload_preset_action"); m_reloadButton->setDefaultAction(m_reloadAction); m_alphaLockButton = new KisHighlightedToolButton(this); m_alphaLockButton->setFixedSize(iconsize, iconsize); m_alphaLockButton->setCheckable(true); KisAction* alphaLockAction = m_viewManager->actionManager()->createAction("preserve_alpha"); m_alphaLockButton->setDefaultAction(alphaLockAction); // horizontal and vertical mirror toolbar buttons // mirror tool options for the X Mirror QMenu *toolbarMenuXMirror = new QMenu(); hideCanvasDecorationsX = m_viewManager->actionManager()->createAction("mirrorX-hideDecorations"); toolbarMenuXMirror->addAction(hideCanvasDecorationsX); lockActionX = m_viewManager->actionManager()->createAction("mirrorX-lock"); toolbarMenuXMirror->addAction(lockActionX); moveToCenterActionX = m_viewManager->actionManager()->createAction("mirrorX-moveToCenter"); toolbarMenuXMirror->addAction(moveToCenterActionX); // mirror tool options for the Y Mirror QMenu *toolbarMenuYMirror = new QMenu(); hideCanvasDecorationsY = m_viewManager->actionManager()->createAction("mirrorY-hideDecorations"); toolbarMenuYMirror->addAction(hideCanvasDecorationsY); lockActionY = m_viewManager->actionManager()->createAction("mirrorY-lock"); toolbarMenuYMirror->addAction(lockActionY); moveToCenterActionY = m_viewManager->actionManager()->createAction("mirrorY-moveToCenter"); toolbarMenuYMirror->addAction(moveToCenterActionY); // create horizontal and vertical mirror buttons m_hMirrorButton = new KisHighlightedToolButton(this); int menuPadding = 10; m_hMirrorButton->setFixedSize(iconsize + menuPadding, iconsize); m_hMirrorButton->setCheckable(true); m_hMirrorAction = m_viewManager->actionManager()->createAction("hmirror_action"); m_hMirrorButton->setDefaultAction(m_hMirrorAction); m_hMirrorButton->setMenu(toolbarMenuXMirror); m_hMirrorButton->setPopupMode(QToolButton::MenuButtonPopup); m_vMirrorButton = new KisHighlightedToolButton(this); m_vMirrorButton->setFixedSize(iconsize + menuPadding, iconsize); m_vMirrorButton->setCheckable(true); m_vMirrorAction = m_viewManager->actionManager()->createAction("vmirror_action"); m_vMirrorButton->setDefaultAction(m_vMirrorAction); m_vMirrorButton->setMenu(toolbarMenuYMirror); m_vMirrorButton->setPopupMode(QToolButton::MenuButtonPopup); // add connections for horizontal and mirrror buttons connect(lockActionX, SIGNAL(toggled(bool)), this, SLOT(slotLockXMirrorToggle(bool))); connect(lockActionY, SIGNAL(toggled(bool)), this, SLOT(slotLockYMirrorToggle(bool))); connect(moveToCenterActionX, SIGNAL(triggered(bool)), this, SLOT(slotMoveToCenterMirrorX())); connect(moveToCenterActionY, SIGNAL(triggered(bool)), this, SLOT(slotMoveToCenterMirrorY())); connect(hideCanvasDecorationsX, SIGNAL(toggled(bool)), this, SLOT(slotHideDecorationMirrorX(bool))); connect(hideCanvasDecorationsY, SIGNAL(toggled(bool)), this, SLOT(slotHideDecorationMirrorY(bool))); const bool sliderLabels = cfg.sliderLabels(); int sliderWidth; if (sliderLabels) { sliderWidth = 150 * logicalDpiX() / 96; } else { sliderWidth = 120 * logicalDpiX() / 96; } for (int i = 0; i < 3; ++i) { m_sliderChooser[i] = new KisWidgetChooser(i + 1); KisDoubleSliderSpinBox* slOpacity; KisDoubleSliderSpinBox* slFlow; KisDoubleSliderSpinBox* slSize; if (sliderLabels) { slOpacity = m_sliderChooser[i]->addWidget("opacity"); slFlow = m_sliderChooser[i]->addWidget("flow"); slSize = m_sliderChooser[i]->addWidget("size"); slOpacity->setPrefix(QString("%1 ").arg(i18n("Opacity:"))); slFlow->setPrefix(QString("%1 ").arg(i18n("Flow:"))); slSize->setPrefix(QString("%1 ").arg(i18n("Size:"))); } else { slOpacity = m_sliderChooser[i]->addWidget("opacity", i18n("Opacity:")); slFlow = m_sliderChooser[i]->addWidget("flow", i18n("Flow:")); slSize = m_sliderChooser[i]->addWidget("size", i18n("Size:")); } slOpacity->setRange(0, 100, 0); slOpacity->setValue(100); slOpacity->setSingleStep(5); slOpacity->setSuffix(i18n("%")); slOpacity->setMinimumWidth(qMax(sliderWidth, slOpacity->sizeHint().width())); slOpacity->setFixedHeight(iconsize); slOpacity->setBlockUpdateSignalOnDrag(true); slFlow->setRange(0, 100, 0); slFlow->setValue(100); slFlow->setSingleStep(5); slFlow->setSuffix(i18n("%")); slFlow->setMinimumWidth(qMax(sliderWidth, slFlow->sizeHint().width())); slFlow->setFixedHeight(iconsize); slFlow->setBlockUpdateSignalOnDrag(true); slSize->setRange(0.01, cfg.readEntry("maximumBrushSize", 1000), 2); slSize->setValue(100); slSize->setSingleStep(1); slSize->setExponentRatio(3.0); slSize->setSuffix(i18n(" px")); slSize->setMinimumWidth(qMax(sliderWidth, slSize->sizeHint().width())); slSize->setFixedHeight(iconsize); slSize->setBlockUpdateSignalOnDrag(true); m_sliderChooser[i]->chooseWidget(cfg.toolbarSlider(i + 1)); } m_cmbCompositeOp = new KisCompositeOpComboBox(); m_cmbCompositeOp->setFixedHeight(iconsize); - Q_FOREACH (KisAction * a, m_cmbCompositeOp->blendmodeActions()) { + Q_FOREACH (KisAction * a, m_cmbCompositeOp->createBlendmodeActions()) { m_viewManager->actionManager()->addAction(a->text(), a); } m_workspaceWidget = new KisPopupButton(this); m_workspaceWidget->setIcon(KisIconUtils::loadIcon("view-choose")); m_workspaceWidget->setToolTip(i18n("Choose workspace")); m_workspaceWidget->setFixedSize(iconsize, iconsize); m_workspaceWidget->setPopupWidget(new KisWorkspaceChooser(view)); QHBoxLayout* baseLayout = new QHBoxLayout(this); m_paintopWidget = new QWidget(this); baseLayout->addWidget(m_paintopWidget); baseLayout->setSpacing(4); baseLayout->setContentsMargins(0, 0, 0, 0); m_layout = new QHBoxLayout(m_paintopWidget); if (!cfg.toolOptionsInDocker()) { m_layout->addWidget(m_toolOptionsPopupButton); } m_layout->addWidget(m_brushEditorPopupButton); m_layout->addWidget(m_presetSelectorPopupButton); m_layout->setSpacing(4); m_layout->setContentsMargins(0, 0, 0, 0); QWidget* compositeActions = new QWidget(this); QHBoxLayout* compositeLayout = new QHBoxLayout(compositeActions); compositeLayout->addWidget(m_cmbCompositeOp); compositeLayout->addWidget(m_eraseModeButton); compositeLayout->addWidget(m_alphaLockButton); compositeLayout->setSpacing(4); compositeLayout->setContentsMargins(0, 0, 0, 0); compositeLayout->addWidget(m_reloadButton); QWidgetAction * action; action = new QWidgetAction(this); view->actionCollection()->addAction("composite_actions", action); action->setText(i18n("Brush composite")); action->setDefaultWidget(compositeActions); action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("brushslider1", action); view->actionCollection()->addAction("brushslider1", action); action->setDefaultWidget(m_sliderChooser[0]); connect(action, SIGNAL(triggered()), m_sliderChooser[0], SLOT(showPopupWidget())); connect(m_viewManager->mainWindow(), SIGNAL(themeChanged()), m_sliderChooser[0], SLOT(updateThemedIcons())); action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("brushslider2", action); view->actionCollection()->addAction("brushslider2", action); action->setDefaultWidget(m_sliderChooser[1]); connect(action, SIGNAL(triggered()), m_sliderChooser[1], SLOT(showPopupWidget())); connect(m_viewManager->mainWindow(), SIGNAL(themeChanged()), m_sliderChooser[1], SLOT(updateThemedIcons())); action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("brushslider3", action); view->actionCollection()->addAction("brushslider3", action); action->setDefaultWidget(m_sliderChooser[2]); connect(action, SIGNAL(triggered()), m_sliderChooser[2], SLOT(showPopupWidget())); connect(m_viewManager->mainWindow(), SIGNAL(themeChanged()), m_sliderChooser[2], SLOT(updateThemedIcons())); action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("next_favorite_preset", action); view->actionCollection()->addAction("next_favorite_preset", action); connect(action, SIGNAL(triggered()), this, SLOT(slotNextFavoritePreset())); action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("previous_favorite_preset", action); view->actionCollection()->addAction("previous_favorite_preset", action); connect(action, SIGNAL(triggered()), this, SLOT(slotPreviousFavoritePreset())); action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("previous_preset", action); view->actionCollection()->addAction("previous_preset", action); connect(action, SIGNAL(triggered()), this, SLOT(slotSwitchToPreviousPreset())); if (!cfg.toolOptionsInDocker()) { action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("show_tool_options", action); view->actionCollection()->addAction("show_tool_options", action); connect(action, SIGNAL(triggered()), m_toolOptionsPopupButton, SLOT(showPopupWidget())); } action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("show_brush_editor", action); view->actionCollection()->addAction("show_brush_editor", action); connect(action, SIGNAL(triggered()), m_brushEditorPopupButton, SLOT(showPopupWidget())); action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("show_brush_presets", action); view->actionCollection()->addAction("show_brush_presets", action); connect(action, SIGNAL(triggered()), m_presetSelectorPopupButton, SLOT(showPopupWidget())); QWidget* mirrorActions = new QWidget(this); QHBoxLayout* mirrorLayout = new QHBoxLayout(mirrorActions); mirrorLayout->addWidget(m_hMirrorButton); mirrorLayout->addWidget(m_vMirrorButton); mirrorLayout->setSpacing(4); mirrorLayout->setContentsMargins(0, 0, 0, 0); action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("mirror_actions", action); action->setDefaultWidget(mirrorActions); view->actionCollection()->addAction("mirror_actions", action); action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("workspaces", action); view->actionCollection()->addAction("workspaces", action); action->setDefaultWidget(m_workspaceWidget); if (!cfg.toolOptionsInDocker()) { m_toolOptionsPopup = new KisToolOptionsPopup(); m_toolOptionsPopupButton->setPopupWidget(m_toolOptionsPopup); m_toolOptionsPopup->switchDetached(false); } m_savePresetWidget = new KisPresetSaveWidget(this); m_presetsPopup = new KisPaintOpPresetsPopup(m_resourceProvider, m_favoriteResourceManager, m_savePresetWidget); m_brushEditorPopupButton->setPopupWidget(m_presetsPopup); m_presetsPopup->parentWidget()->setWindowTitle(i18n("Brush Editor")); connect(m_presetsPopup, SIGNAL(brushEditorShown()), SLOT(slotUpdateOptionsWidgetPopup())); connect(m_viewManager->mainWindow(), SIGNAL(themeChanged()), m_presetsPopup, SLOT(updateThemedIcons())); m_presetsChooserPopup = new KisPaintOpPresetsChooserPopup(); m_presetsChooserPopup->setMinimumHeight(550); m_presetsChooserPopup->setMinimumWidth(450); m_presetSelectorPopupButton->setPopupWidget(m_presetsChooserPopup); m_currCompositeOpID = KoCompositeOpRegistry::instance().getDefaultCompositeOp().id(); slotNodeChanged(view->activeNode()); // Get all the paintops QList keys = KisPaintOpRegistry::instance()->keys(); QList factoryList; Q_FOREACH (const QString & paintopId, keys) { factoryList.append(KisPaintOpRegistry::instance()->get(paintopId)); } m_presetsPopup->setPaintOpList(factoryList); connect(m_presetsPopup , SIGNAL(paintopActivated(QString)) , SLOT(slotSetPaintop(QString))); connect(m_presetsPopup , SIGNAL(defaultPresetClicked()) , SLOT(slotSetupDefaultPreset())); connect(m_presetsPopup , SIGNAL(signalResourceSelected(KoResource*)), SLOT(resourceSelected(KoResource*))); connect(m_presetsPopup , SIGNAL(reloadPresetClicked()) , SLOT(slotReloadPreset())); connect(m_presetsPopup , SIGNAL(dirtyPresetToggled(bool)) , SLOT(slotDirtyPresetToggled(bool))); connect(m_presetsPopup , SIGNAL(eraserBrushSizeToggled(bool)) , SLOT(slotEraserBrushSizeToggled(bool))); connect(m_presetsPopup , SIGNAL(eraserBrushOpacityToggled(bool)) , SLOT(slotEraserBrushOpacityToggled(bool))); connect(m_presetsPopup, SIGNAL(createPresetFromScratch(QString)), this, SLOT(slotCreatePresetFromScratch(QString))); connect(m_presetsChooserPopup, SIGNAL(resourceSelected(KoResource*)) , SLOT(resourceSelected(KoResource*))); connect(m_presetsChooserPopup, SIGNAL(resourceClicked(KoResource*)) , SLOT(resourceSelected(KoResource*))); connect(m_resourceProvider , SIGNAL(sigNodeChanged(KisNodeSP)) , SLOT(slotNodeChanged(KisNodeSP))); connect(m_cmbCompositeOp , SIGNAL(currentIndexChanged(int)) , SLOT(slotSetCompositeMode(int))); connect(m_eraseAction , SIGNAL(toggled(bool)) , SLOT(slotToggleEraseMode(bool))); connect(alphaLockAction , SIGNAL(toggled(bool)) , SLOT(slotToggleAlphaLockMode(bool))); m_disablePressureAction = m_viewManager->actionManager()->createAction("disable_pressure"); connect(m_disablePressureAction , SIGNAL(toggled(bool)) , SLOT(slotDisablePressureMode(bool))); m_disablePressureAction->setChecked(true); connect(m_hMirrorAction , SIGNAL(toggled(bool)) , SLOT(slotHorizontalMirrorChanged(bool))); connect(m_vMirrorAction , SIGNAL(toggled(bool)) , SLOT(slotVerticalMirrorChanged(bool))); connect(m_reloadAction , SIGNAL(triggered()) , SLOT(slotReloadPreset())); connect(m_sliderChooser[0]->getWidget("opacity"), SIGNAL(valueChanged(qreal)), SLOT(slotSlider1Changed())); connect(m_sliderChooser[0]->getWidget("flow") , SIGNAL(valueChanged(qreal)), SLOT(slotSlider1Changed())); connect(m_sliderChooser[0]->getWidget("size") , SIGNAL(valueChanged(qreal)), SLOT(slotSlider1Changed())); connect(m_sliderChooser[1]->getWidget("opacity"), SIGNAL(valueChanged(qreal)), SLOT(slotSlider2Changed())); connect(m_sliderChooser[1]->getWidget("flow") , SIGNAL(valueChanged(qreal)), SLOT(slotSlider2Changed())); connect(m_sliderChooser[1]->getWidget("size") , SIGNAL(valueChanged(qreal)), SLOT(slotSlider2Changed())); connect(m_sliderChooser[2]->getWidget("opacity"), SIGNAL(valueChanged(qreal)), SLOT(slotSlider3Changed())); connect(m_sliderChooser[2]->getWidget("flow") , SIGNAL(valueChanged(qreal)), SLOT(slotSlider3Changed())); connect(m_sliderChooser[2]->getWidget("size") , SIGNAL(valueChanged(qreal)), SLOT(slotSlider3Changed())); //Needed to connect canvas to favorite resource manager connect(m_viewManager->canvasResourceProvider(), SIGNAL(sigFGColorChanged(KoColor)), SLOT(slotUnsetEraseMode())); connect(m_resourceProvider, SIGNAL(sigFGColorUsed(KoColor)), m_favoriteResourceManager, SLOT(slotAddRecentColor(KoColor))); connect(m_resourceProvider, SIGNAL(sigFGColorChanged(KoColor)), m_favoriteResourceManager, SLOT(slotChangeFGColorSelector(KoColor))); connect(m_resourceProvider, SIGNAL(sigBGColorChanged(KoColor)), m_favoriteResourceManager, SLOT(slotSetBGColor(KoColor))); // cold initialization m_favoriteResourceManager->slotChangeFGColorSelector(m_resourceProvider->fgColor()); m_favoriteResourceManager->slotSetBGColor(m_resourceProvider->bgColor()); connect(m_favoriteResourceManager, SIGNAL(sigSetFGColor(KoColor)), m_resourceProvider, SLOT(slotSetFGColor(KoColor))); connect(m_favoriteResourceManager, SIGNAL(sigSetBGColor(KoColor)), m_resourceProvider, SLOT(slotSetBGColor(KoColor))); connect(m_favoriteResourceManager, SIGNAL(sigEnableChangeColor(bool)), m_resourceProvider, SLOT(slotResetEnableFGChange(bool))); connect(view->mainWindow(), SIGNAL(themeChanged()), this, SLOT(slotUpdateSelectionIcon())); connect(m_resourceProvider->resourceManager(), SIGNAL(canvasResourceChanged(int,QVariant)), this, SLOT(slotCanvasResourceChanged(int,QVariant))); slotInputDeviceChanged(KoToolManager::instance()->currentInputDevice()); KisPaintOpPresetResourceServer *rserver = KisResourceServerProvider::instance()->paintOpPresetServer(); m_eraserName = "eraser_circle"; m_defaultPresetName = "basic_tip_default"; bool foundEraser = false; bool foundTip = false; for (int i=0; iresourceCount(); i++) { KisPaintOpPresetSP resource = rserver->resources().at(i); if (resource->name().toLower().contains("eraser_circle")) { m_eraserName = resource->name(); foundEraser = true; } else if (foundEraser == false && (resource->name().toLower().contains("eraser") || resource->filename().toLower().contains("eraser"))) { m_eraserName = resource->name(); foundEraser = true; } if (resource->name().toLower().contains("basic_tip_default")) { m_defaultPresetName = resource->name(); foundTip = true; } else if (foundTip == false && (resource->name().toLower().contains("default") || resource->filename().toLower().contains("default"))) { m_defaultPresetName = resource->name(); foundTip = true; } } } KisPaintopBox::~KisPaintopBox() { KisConfig cfg(false); QMapIterator iter(m_tabletToolMap); while (iter.hasNext()) { iter.next(); if ((iter.key().pointer) == QTabletEvent::Eraser) { cfg.writeEntry(QString("LastEraser") , iter.value().preset->name()); } else { cfg.writeEntry(QString("LastPreset"), iter.value().preset->name()); } } // Do not delete the widget, since it is global to the application, not owned by the view m_presetsPopup->setPaintOpSettingsWidget(0); qDeleteAll(m_paintopOptionWidgets); delete m_favoriteResourceManager; for (int i = 0; i < 3; ++i) { delete m_sliderChooser[i]; } } void KisPaintopBox::restoreResource(KoResource* resource) { KisPaintOpPreset* preset = dynamic_cast(resource); if (preset) { setCurrentPaintop(preset); m_presetsPopup->setPresetImage(preset->image()); m_presetsPopup->resourceSelected(resource); } } void KisPaintopBox::newOptionWidgets(const QList > &optionWidgetList) { if (m_toolOptionsPopup) { m_toolOptionsPopup->newOptionWidgets(optionWidgetList); } } void KisPaintopBox::resourceSelected(KoResource* resource) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_optionWidget); m_presetsPopup->setCreatingBrushFromScratch(false); // show normal UI elements when we are not creating KisPaintOpPreset* preset = dynamic_cast(resource); if (preset && preset != m_resourceProvider->currentPreset()) { if (!preset->settings()->isLoadable()) return; if (!m_dirtyPresetsEnabled) { KisSignalsBlocker blocker(m_optionWidget); if (!preset->load()) { warnKrita << "failed to load the preset."; } } //qDebug() << "resourceSelected" << resource->name(); setCurrentPaintop(preset); m_presetsPopup->setPresetImage(preset->image()); m_presetsPopup->resourceSelected(resource); } } void KisPaintopBox::setCurrentPaintop(const KoID& paintop) { KisPaintOpPresetSP preset = activePreset(paintop); Q_ASSERT(preset && preset->settings()); //qDebug() << "setCurrentPaintop();" << paintop << preset; setCurrentPaintop(preset); } void KisPaintopBox::setCurrentPaintop(KisPaintOpPresetSP preset) { //qDebug() << "setCurrentPaintop(); " << preset->name(); if (preset == m_resourceProvider->currentPreset()) { if (preset == m_tabletToolMap[m_currTabletToolID].preset) { return; } } Q_ASSERT(preset); const KoID& paintop = preset->paintOp(); m_presetConnections.clear(); if (m_resourceProvider->currentPreset()) { m_resourceProvider->setPreviousPaintOpPreset(m_resourceProvider->currentPreset()); if (m_optionWidget) { m_optionWidget->hide(); } } if (!m_paintopOptionWidgets.contains(paintop)) m_paintopOptionWidgets[paintop] = KisPaintOpRegistry::instance()->get(paintop.id())->createConfigWidget(this); m_optionWidget = m_paintopOptionWidgets[paintop]; KisSignalsBlocker b(m_optionWidget); preset->setOptionsWidget(m_optionWidget); m_optionWidget->setImage(m_viewManager->image()); m_optionWidget->setNode(m_viewManager->activeNode()); m_presetsPopup->setPaintOpSettingsWidget(m_optionWidget); m_resourceProvider->setPaintOpPreset(preset); Q_ASSERT(m_optionWidget && m_presetSelectorPopupButton); m_presetConnections.addConnection(m_optionWidget, SIGNAL(sigConfigurationUpdated()), this, SLOT(slotGuiChangedCurrentPreset())); m_presetConnections.addConnection(m_optionWidget, SIGNAL(sigSaveLockedConfig(KisPropertiesConfigurationSP)), this, SLOT(slotSaveLockedOptionToPreset(KisPropertiesConfigurationSP))); m_presetConnections.addConnection(m_optionWidget, SIGNAL(sigDropLockedConfig(KisPropertiesConfigurationSP)), this, SLOT(slotDropLockedOption(KisPropertiesConfigurationSP))); // load the current brush engine icon for the brush editor toolbar button m_brushEditorPopupButton->setResource(preset.data()); m_presetsPopup->setCurrentPaintOpId(paintop.id()); ////qDebug() << "\tsetting the new preset for" << m_currTabletToolID.uniqueID << "to" << preset->name(); m_paintOpPresetMap[m_resourceProvider->currentPreset()->paintOp()] = preset; m_tabletToolMap[m_currTabletToolID].preset = preset; m_tabletToolMap[m_currTabletToolID].paintOpID = preset->paintOp(); if (m_presetsPopup->currentPaintOpId() != paintop.id()) { // Must change the paintop as the current one is not supported // by the new colorspace. dbgKrita << "current paintop " << paintop.name() << " was not set, not supported by colorspace"; } m_currCompositeOpID = preset->settings()->paintOpCompositeOp(); updateCompositeOp(m_currCompositeOpID); } void KisPaintopBox::slotUpdateOptionsWidgetPopup() { KisPaintOpPresetSP preset = m_resourceProvider->currentPreset(); // This happens when we have a new brush engine for which no default preset exists yet. if (!preset) return; KIS_SAFE_ASSERT_RECOVER_RETURN(preset); KIS_SAFE_ASSERT_RECOVER_RETURN(m_optionWidget); m_optionWidget->setConfigurationSafe(preset->settings()); m_presetsPopup->resourceSelected(preset.data()); m_presetsPopup->updateViewSettings(); // the m_viewManager->image() is set earlier, but the reference will be missing when the stamp button is pressed // need to later do some research on how and when we should be using weak shared pointers (WSP) that creates this situation m_optionWidget->setImage(m_viewManager->image()); } KisPaintOpPresetSP KisPaintopBox::defaultPreset(const KoID& paintOp) { QString defaultName = paintOp.id() + ".kpp"; QString path = KoResourcePaths::findResource("kis_defaultpresets", defaultName); KisPaintOpPresetSP preset = new KisPaintOpPreset(path); if (!preset->load()) { preset = KisPaintOpRegistry::instance()->defaultPreset(paintOp); } Q_ASSERT(preset); Q_ASSERT(preset->valid()); return preset; } KisPaintOpPresetSP KisPaintopBox::activePreset(const KoID& paintOp) { if (m_paintOpPresetMap[paintOp] == 0) { m_paintOpPresetMap[paintOp] = defaultPreset(paintOp); } return m_paintOpPresetMap[paintOp]; } void KisPaintopBox::updateCompositeOp(QString compositeOpID) { if (!m_optionWidget) return; KisSignalsBlocker blocker(m_optionWidget); KisNodeSP node = m_resourceProvider->currentNode(); if (node && node->paintDevice()) { if (!node->paintDevice()->colorSpace()->hasCompositeOp(compositeOpID)) compositeOpID = KoCompositeOpRegistry::instance().getDefaultCompositeOp().id(); { KisSignalsBlocker b1(m_cmbCompositeOp); m_cmbCompositeOp->selectCompositeOp(KoID(compositeOpID)); } if (compositeOpID != m_currCompositeOpID) { m_currCompositeOpID = compositeOpID; } if (compositeOpID == COMPOSITE_ERASE || m_resourceProvider->eraserMode()) { m_eraseModeButton->setChecked(true); } else { m_eraseModeButton->setChecked(false); } } else if (!node) { KisSignalsBlocker b1(m_cmbCompositeOp); m_cmbCompositeOp->selectCompositeOp(KoID(compositeOpID)); m_currCompositeOpID = compositeOpID; } } void KisPaintopBox::setWidgetState(int flags) { if (flags & (ENABLE_COMPOSITEOP | DISABLE_COMPOSITEOP)) { m_cmbCompositeOp->setEnabled(flags & ENABLE_COMPOSITEOP); m_eraseModeButton->setEnabled(flags & ENABLE_COMPOSITEOP); } if (flags & (ENABLE_PRESETS | DISABLE_PRESETS)) { m_presetSelectorPopupButton->setEnabled(flags & ENABLE_PRESETS); m_brushEditorPopupButton->setEnabled(flags & ENABLE_PRESETS); } for (int i = 0; i < 3; ++i) { if (flags & (ENABLE_OPACITY | DISABLE_OPACITY)) m_sliderChooser[i]->getWidget("opacity")->setEnabled(flags & ENABLE_OPACITY); if (flags & (ENABLE_FLOW | DISABLE_FLOW)) m_sliderChooser[i]->getWidget("flow")->setEnabled(flags & ENABLE_FLOW); if (flags & (ENABLE_SIZE | DISABLE_SIZE)) m_sliderChooser[i]->getWidget("size")->setEnabled(flags & ENABLE_SIZE); } } void KisPaintopBox::setSliderValue(const QString& sliderID, qreal value) { for (int i = 0; i < 3; ++i) { KisDoubleSliderSpinBox* slider = m_sliderChooser[i]->getWidget(sliderID); KisSignalsBlocker b(slider); if (sliderID == "opacity" || sliderID == "flow") { // opacity and flows UI stored at 0-100% slider->setValue(value*100); } else { slider->setValue(value); // brush size } } } void KisPaintopBox::slotSetPaintop(const QString& paintOpId) { if (KisPaintOpRegistry::instance()->get(paintOpId) != 0) { KoID id(paintOpId, KisPaintOpRegistry::instance()->get(paintOpId)->name()); //qDebug() << "slotsetpaintop" << id; setCurrentPaintop(id); } } void KisPaintopBox::slotInputDeviceChanged(const KoInputDevice& inputDevice) { TabletToolMap::iterator toolData = m_tabletToolMap.find(inputDevice); //qDebug() << "slotInputDeviceChanged()" << inputDevice.device() << inputDevice.uniqueTabletId(); m_currTabletToolID = TabletToolID(inputDevice); if (toolData == m_tabletToolMap.end()) { KisConfig cfg(true); KisPaintOpPresetResourceServer *rserver = KisResourceServerProvider::instance()->paintOpPresetServer(); KisPaintOpPresetSP preset; if (inputDevice.pointer() == QTabletEvent::Eraser) { preset = rserver->resourceByName(cfg.readEntry(QString("LastEraser_%1").arg(inputDevice.uniqueTabletId()), m_eraserName)); } else { preset = rserver->resourceByName(cfg.readEntry(QString("LastPreset_%1").arg(inputDevice.uniqueTabletId()), m_defaultPresetName)); //if (preset) //qDebug() << "found stored preset " << preset->name() << "for" << inputDevice.uniqueTabletId(); //else //qDebug() << "no preset found for" << inputDevice.uniqueTabletId(); } if (!preset) { preset = rserver->resourceByName(m_defaultPresetName); } if (preset) { //qDebug() << "inputdevicechanged 1" << preset; setCurrentPaintop(preset); } } else { if (toolData->preset) { //qDebug() << "inputdevicechanged 2" << toolData->preset; setCurrentPaintop(toolData->preset); } else { //qDebug() << "inputdevicechanged 3" << toolData->paintOpID; setCurrentPaintop(toolData->paintOpID); } } } void KisPaintopBox::slotCreatePresetFromScratch(QString paintop) { //First try to select an available default preset for that engine. If it doesn't exist, then //manually set the engine to use a new preset. KoID id(paintop, KisPaintOpRegistry::instance()->get(paintop)->name()); KisPaintOpPresetSP preset = defaultPreset(id); slotSetPaintop(paintop); // change the paintop settings area and update the UI if (!preset) { m_presetsPopup->setCreatingBrushFromScratch(true); // disable UI elements while creating from scratch preset = m_resourceProvider->currentPreset(); } else { m_resourceProvider->setPaintOpPreset(preset); preset->setOptionsWidget(m_optionWidget); } m_presetsPopup->resourceSelected(preset.data()); // this helps update the UI on the brush editor } void KisPaintopBox::slotCanvasResourceChanged(int key, const QVariant &value) { if (m_viewManager) { sender()->blockSignals(true); KisPaintOpPresetSP preset = m_viewManager->canvasResourceProvider()->resourceManager()->resource(KisCanvasResourceProvider::CurrentPaintOpPreset).value(); if (preset && m_resourceProvider->currentPreset()->name() != preset->name()) { QString compositeOp = preset->settings()->getString("CompositeOp"); updateCompositeOp(compositeOp); resourceSelected(preset.data()); } /** * Update currently selected preset in both the popup widgets */ m_presetsChooserPopup->canvasResourceChanged(preset); m_presetsPopup->currentPresetChanged(preset); if (key == KisCanvasResourceProvider::CurrentCompositeOp) { if (m_resourceProvider->currentCompositeOp() != m_currCompositeOpID) { updateCompositeOp(m_resourceProvider->currentCompositeOp()); } } if (key == KisCanvasResourceProvider::Size) { setSliderValue("size", m_resourceProvider->size()); } if (key == KisCanvasResourceProvider::Opacity) { setSliderValue("opacity", m_resourceProvider->opacity()); } if (key == KisCanvasResourceProvider::Flow) { setSliderValue("flow", m_resourceProvider->flow()); } if (key == KisCanvasResourceProvider::EraserMode) { m_eraseAction->setChecked(value.toBool()); } if (key == KisCanvasResourceProvider::DisablePressure) { m_disablePressureAction->setChecked(value.toBool()); } if (key == KisCanvasResourceProvider::MirrorHorizontal) { m_hMirrorAction->setChecked(value.toBool()); } if (key == KisCanvasResourceProvider::MirrorVertical) { m_vMirrorAction->setChecked(value.toBool()); } sender()->blockSignals(false); } } void KisPaintopBox::slotSetupDefaultPreset() { KisPaintOpPresetSP preset = defaultPreset(m_resourceProvider->currentPreset()->paintOp()); preset->setOptionsWidget(m_optionWidget); m_resourceProvider->setPaintOpPreset(preset); // tell the brush editor that the resource has changed // so it can update everything m_presetsPopup->resourceSelected(preset.data()); } void KisPaintopBox::slotNodeChanged(const KisNodeSP node) { if (m_previousNode.isValid() && m_previousNode->paintDevice()) disconnect(m_previousNode->paintDevice().data(), SIGNAL(colorSpaceChanged(const KoColorSpace*)), this, SLOT(slotColorSpaceChanged(const KoColorSpace*))); // Reconnect colorspace change of node if (node && node->paintDevice()) { connect(node->paintDevice().data(), SIGNAL(colorSpaceChanged(const KoColorSpace*)), this, SLOT(slotColorSpaceChanged(const KoColorSpace*))); m_resourceProvider->setCurrentCompositeOp(m_currCompositeOpID); m_previousNode = node; slotColorSpaceChanged(node->colorSpace()); } if (m_optionWidget) { m_optionWidget->setNode(node); } } void KisPaintopBox::slotColorSpaceChanged(const KoColorSpace* colorSpace) { m_cmbCompositeOp->validate(colorSpace); } void KisPaintopBox::slotToggleEraseMode(bool checked) { const bool oldEraserMode = m_resourceProvider->eraserMode(); m_resourceProvider->setEraserMode(checked); if (oldEraserMode != checked && m_eraserBrushSizeEnabled) { const qreal currentSize = m_resourceProvider->size(); KisPaintOpSettingsSP settings = m_resourceProvider->currentPreset()->settings(); // remember brush size. set the eraser size to the normal brush size if not set if (checked) { settings->setSavedBrushSize(currentSize); if (qFuzzyIsNull(settings->savedEraserSize())) { settings->setSavedEraserSize(currentSize); } } else { settings->setSavedEraserSize(currentSize); if (qFuzzyIsNull(settings->savedBrushSize())) { settings->setSavedBrushSize(currentSize); } } //update value in UI (this is the main place the value is 'stored' in memory) qreal newSize = checked ? settings->savedEraserSize() : settings->savedBrushSize(); m_resourceProvider->setSize(newSize); } if (oldEraserMode != checked && m_eraserBrushOpacityEnabled) { const qreal currentOpacity = m_resourceProvider->opacity(); KisPaintOpSettingsSP settings = m_resourceProvider->currentPreset()->settings(); // remember brush opacity. set the eraser opacity to the normal brush opacity if not set if (checked) { settings->setSavedBrushOpacity(currentOpacity); if (qFuzzyIsNull(settings->savedEraserOpacity())) { settings->setSavedEraserOpacity(currentOpacity); } } else { settings->setSavedEraserOpacity(currentOpacity); if (qFuzzyIsNull(settings->savedBrushOpacity())) { settings->setSavedBrushOpacity(currentOpacity); } } //update value in UI (this is the main place the value is 'stored' in memory) qreal newOpacity = checked ? settings->savedEraserOpacity() : settings->savedBrushOpacity(); m_resourceProvider->setOpacity(newOpacity); } } void KisPaintopBox::slotSetCompositeMode(int index) { Q_UNUSED(index); QString compositeOp = m_cmbCompositeOp->selectedCompositeOp().id(); m_resourceProvider->setCurrentCompositeOp(compositeOp); } void KisPaintopBox::slotHorizontalMirrorChanged(bool value) { m_resourceProvider->setMirrorHorizontal(value); } void KisPaintopBox::slotVerticalMirrorChanged(bool value) { m_resourceProvider->setMirrorVertical(value); } void KisPaintopBox::sliderChanged(int n) { if (!m_optionWidget) // widget will not exist if the are no documents open return; KisSignalsBlocker blocker(m_optionWidget); // flow and opacity are shown as 0-100% on the UI, but their data is actually 0-1. Convert those two values // back for further work qreal opacity = m_sliderChooser[n]->getWidget("opacity")->value()/100; qreal flow = m_sliderChooser[n]->getWidget("flow")->value()/100; qreal size = m_sliderChooser[n]->getWidget("size")->value(); setSliderValue("opacity", opacity); setSliderValue("flow" , flow); setSliderValue("size" , size); if (m_presetsEnabled) { // IMPORTANT: set the PaintOp size before setting the other properties // it won't work the other way // TODO: why?! m_resourceProvider->setSize(size); m_resourceProvider->setOpacity(opacity); m_resourceProvider->setFlow(flow); KisLockedPropertiesProxySP propertiesProxy = KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(m_resourceProvider->currentPreset()->settings()); propertiesProxy->setProperty("OpacityValue", opacity); propertiesProxy->setProperty("FlowValue", flow); m_optionWidget->setConfigurationSafe(m_resourceProvider->currentPreset()->settings().data()); } else { m_resourceProvider->setOpacity(opacity); } m_presetsPopup->resourceSelected(m_resourceProvider->currentPreset().data()); } void KisPaintopBox::slotSlider1Changed() { sliderChanged(0); } void KisPaintopBox::slotSlider2Changed() { sliderChanged(1); } void KisPaintopBox::slotSlider3Changed() { sliderChanged(2); } void KisPaintopBox::slotToolChanged(KoCanvasController* canvas, int toolId) { Q_UNUSED(canvas); Q_UNUSED(toolId); if (!m_viewManager->canvasBase()) return; QString id = KoToolManager::instance()->activeToolId(); KisTool* tool = dynamic_cast(KoToolManager::instance()->toolById(m_viewManager->canvasBase(), id)); if (tool) { int flags = tool->flags(); if (flags & KisTool::FLAG_USES_CUSTOM_COMPOSITEOP) { setWidgetState(ENABLE_COMPOSITEOP | ENABLE_OPACITY); } else { setWidgetState(DISABLE_COMPOSITEOP | DISABLE_OPACITY); } if (flags & KisTool::FLAG_USES_CUSTOM_PRESET) { setWidgetState(ENABLE_PRESETS); if (!m_resourceProvider->currentPreset()) return; // block updates of avoid some over updating of the option widget m_blockUpdate = true; setSliderValue("size", m_resourceProvider->size()); { qreal opacity = m_resourceProvider->currentPreset()->settings()->paintOpOpacity(); m_resourceProvider->setOpacity(opacity); setSliderValue("opacity", opacity); setWidgetState(ENABLE_OPACITY); } { setSliderValue("flow", m_resourceProvider->currentPreset()->settings()->paintOpFlow()); setWidgetState(ENABLE_FLOW); } { updateCompositeOp(m_resourceProvider->currentPreset()->settings()->paintOpCompositeOp()); setWidgetState(ENABLE_COMPOSITEOP); } m_blockUpdate = false; m_presetsEnabled = true; } else { setWidgetState(DISABLE_PRESETS); m_presetsEnabled = false; } if (flags & KisTool::FLAG_USES_CUSTOM_SIZE) { setWidgetState(ENABLE_SIZE | ENABLE_FLOW); } else { setWidgetState(DISABLE_SIZE | DISABLE_FLOW); } } else setWidgetState(DISABLE_ALL); } void KisPaintopBox::slotPreviousFavoritePreset() { if (!m_favoriteResourceManager) return; QVector presets = m_favoriteResourceManager->favoritePresetList(); for (int i=0; i < presets.size(); ++i) { if (m_resourceProvider->currentPreset() && m_resourceProvider->currentPreset()->name() == presets[i]->name()) { if (i > 0) { m_favoriteResourceManager->slotChangeActivePaintop(i - 1); } else { m_favoriteResourceManager->slotChangeActivePaintop(m_favoriteResourceManager->numFavoritePresets() - 1); } //floating message should have least 2 lines, otherwise //preset thumbnail will be too small to distinguish //(because size of image on floating message depends on amount of lines in msg) m_viewManager->showFloatingMessage( i18n("%1\nselected", m_resourceProvider->currentPreset()->name()), QIcon(QPixmap::fromImage(m_resourceProvider->currentPreset()->image()))); return; } } } void KisPaintopBox::slotNextFavoritePreset() { if (!m_favoriteResourceManager) return; QVector presets = m_favoriteResourceManager->favoritePresetList(); for(int i = 0; i < presets.size(); ++i) { if (m_resourceProvider->currentPreset()->name() == presets[i]->name()) { if (i < m_favoriteResourceManager->numFavoritePresets() - 1) { m_favoriteResourceManager->slotChangeActivePaintop(i + 1); } else { m_favoriteResourceManager->slotChangeActivePaintop(0); } m_viewManager->showFloatingMessage( i18n("%1\nselected", m_resourceProvider->currentPreset()->name()), QIcon(QPixmap::fromImage(m_resourceProvider->currentPreset()->image()))); return; } } } void KisPaintopBox::slotSwitchToPreviousPreset() { if (m_resourceProvider->previousPreset()) { //qDebug() << "slotSwitchToPreviousPreset();" << m_resourceProvider->previousPreset(); setCurrentPaintop(m_resourceProvider->previousPreset()); m_viewManager->showFloatingMessage( i18n("%1\nselected", m_resourceProvider->currentPreset()->name()), QIcon(QPixmap::fromImage(m_resourceProvider->currentPreset()->image()))); } } void KisPaintopBox::slotUnsetEraseMode() { m_eraseAction->setChecked(false); } void KisPaintopBox::slotToggleAlphaLockMode(bool checked) { if (checked) { m_alphaLockButton->actions()[0]->setIcon(KisIconUtils::loadIcon("transparency-locked")); } else { m_alphaLockButton->actions()[0]->setIcon(KisIconUtils::loadIcon("transparency-unlocked")); } m_resourceProvider->setGlobalAlphaLock(checked); } void KisPaintopBox::slotDisablePressureMode(bool checked) { if (checked) { m_disablePressureAction->setIcon(KisIconUtils::loadIcon("transform_icons_penPressure")); } else { m_disablePressureAction->setIcon(KisIconUtils::loadIcon("transform_icons_penPressure_locked")); } m_resourceProvider->setDisablePressure(checked); } void KisPaintopBox::slotReloadPreset() { KisSignalsBlocker blocker(m_optionWidget); //Here using the name and fetching the preset from the server was the only way the load was working. Otherwise it was not loading. KisPaintOpPresetResourceServer * rserver = KisResourceServerProvider::instance()->paintOpPresetServer(); KisPaintOpPresetSP preset = rserver->resourceByName(m_resourceProvider->currentPreset()->name()); if (preset) { preset->load(); } if (m_resourceProvider->currentPreset() != preset) { m_resourceProvider->setPaintOpPreset(preset); } else { /** * HACK ALERT: here we emit a private signal from the resource manager to * ensure that all the subscribers of resource-changed signal got the * notification. That is especially important for * KisPaintopTransformationConnector. See bug 392622. */ emit m_resourceProvider->resourceManager()->canvasResourceChanged( KisCanvasResourceProvider::CurrentPaintOpPreset, QVariant::fromValue(preset)); } } void KisPaintopBox::slotGuiChangedCurrentPreset() // Called only when UI is changed and not when preset is changed { KisPaintOpPresetSP preset = m_resourceProvider->currentPreset(); { /** * Here we postpone all the settings updates events until the entire writing * operation will be finished. As soon as it is finished, the updates will be * emitted happily (if there were any). */ KisPaintOpPreset::UpdatedPostponer postponer(preset.data()); QStringList preserveProperties; preserveProperties << "lodUserAllowed"; preserveProperties << "lodSizeThreshold"; // clear all the properties before dumping the stuff into the preset, // some of the options add the values incrementally // (e.g. KisPaintOpUtils::RequiredBrushFilesListTag), therefore they // may add up if we pass the same preset multiple times preset->settings()->resetSettings(preserveProperties); m_optionWidget->writeConfigurationSafe(const_cast(preset->settings().data())); } // we should also update the preset strip to update the status of the "dirty" mark m_presetsPopup->resourceSelected(m_resourceProvider->currentPreset().data()); // TODO!!!!!!!! //m_presetsPopup->updateViewSettings(); } void KisPaintopBox::slotSaveLockedOptionToPreset(KisPropertiesConfigurationSP p) { QMapIterator i(p->getProperties()); while (i.hasNext()) { i.next(); m_resourceProvider->currentPreset()->settings()->setProperty(i.key(), QVariant(i.value())); if (m_resourceProvider->currentPreset()->settings()->hasProperty(i.key() + "_previous")) { m_resourceProvider->currentPreset()->settings()->removeProperty(i.key() + "_previous"); } } slotGuiChangedCurrentPreset(); } void KisPaintopBox::slotDropLockedOption(KisPropertiesConfigurationSP p) { KisSignalsBlocker blocker(m_optionWidget); KisPaintOpPresetSP preset = m_resourceProvider->currentPreset(); { KisPaintOpPreset::DirtyStateSaver dirtySaver(preset.data()); QMapIterator i(p->getProperties()); while (i.hasNext()) { i.next(); if (preset->settings()->hasProperty(i.key() + "_previous")) { preset->settings()->setProperty(i.key(), preset->settings()->getProperty(i.key() + "_previous")); preset->settings()->removeProperty(i.key() + "_previous"); } } } } void KisPaintopBox::slotDirtyPresetToggled(bool value) { if (!value) { slotReloadPreset(); m_presetsPopup->resourceSelected(m_resourceProvider->currentPreset().data()); m_presetsPopup->updateViewSettings(); } m_dirtyPresetsEnabled = value; KisConfig cfg(false); cfg.setUseDirtyPresets(m_dirtyPresetsEnabled); } void KisPaintopBox::slotEraserBrushSizeToggled(bool value) { m_eraserBrushSizeEnabled = value; KisConfig cfg(false); cfg.setUseEraserBrushSize(m_eraserBrushSizeEnabled); } void KisPaintopBox::slotEraserBrushOpacityToggled(bool value) { m_eraserBrushOpacityEnabled = value; KisConfig cfg(false); cfg.setUseEraserBrushOpacity(m_eraserBrushOpacityEnabled); } void KisPaintopBox::slotUpdateSelectionIcon() { m_hMirrorAction->setIcon(KisIconUtils::loadIcon("symmetry-horizontal")); m_vMirrorAction->setIcon(KisIconUtils::loadIcon("symmetry-vertical")); KisConfig cfg(true); if (!cfg.toolOptionsInDocker() && m_toolOptionsPopupButton) { m_toolOptionsPopupButton->setIcon(KisIconUtils::loadIcon("configure")); } m_presetSelectorPopupButton->setIcon(KisIconUtils::loadIcon("paintop_settings_01")); m_brushEditorPopupButton->setIcon(KisIconUtils::loadIcon("paintop_settings_02")); m_workspaceWidget->setIcon(KisIconUtils::loadIcon("view-choose")); m_eraseAction->setIcon(KisIconUtils::loadIcon("draw-eraser")); m_reloadAction->setIcon(KisIconUtils::loadIcon("view-refresh")); if (m_disablePressureAction->isChecked()) { m_disablePressureAction->setIcon(KisIconUtils::loadIcon("transform_icons_penPressure")); } else { m_disablePressureAction->setIcon(KisIconUtils::loadIcon("transform_icons_penPressure_locked")); } } void KisPaintopBox::slotLockXMirrorToggle(bool toggleLock) { m_resourceProvider->setMirrorHorizontalLock(toggleLock); } void KisPaintopBox::slotLockYMirrorToggle(bool toggleLock) { m_resourceProvider->setMirrorVerticalLock(toggleLock); } void KisPaintopBox::slotHideDecorationMirrorX(bool toggled) { m_resourceProvider->setMirrorHorizontalHideDecorations(toggled); } void KisPaintopBox::slotHideDecorationMirrorY(bool toggled) { m_resourceProvider->setMirrorVerticalHideDecorations(toggled); } void KisPaintopBox::slotMoveToCenterMirrorX() { m_resourceProvider->mirrorHorizontalMoveCanvasToCenter(); } void KisPaintopBox::slotMoveToCenterMirrorY() { m_resourceProvider->mirrorVerticalMoveCanvasToCenter(); } diff --git a/libs/ui/kis_paintop_option.h b/libs/ui/kis_paintop_option.h index e04d32bea7..c25147e96e 100644 --- a/libs/ui/kis_paintop_option.h +++ b/libs/ui/kis_paintop_option.h @@ -1,133 +1,133 @@ /* This file is part of the KDE project * Copyright (C) Boudewijn Rempt , (C) 2008 * * 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_PAINTOP_OPTION_H #define KIS_PAINTOP_OPTION_H #include #include #include #include #include class QWidget; class QString; class KisPaintopLodLimitations; /** * Base interface for paintop options. A paintop option * can be enabled/disabled, has a configuration page * (for example, a curve), a user-visible name and can * be serialized and deserialized into KisPaintOpPresets * * Because KisPaintOpOption classes create a QWidget in * their constructor (the configuration page) you CANNOT * create those objects in a KisPaintOp. KisPaintOps are * created in non-gui threads. * * Options are disabled by default. */ class KRITAUI_EXPORT KisPaintOpOption : public QObject { Q_OBJECT public: enum PaintopCategory { GENERAL, COLOR, TEXTURE, FILTER, MASKING_BRUSH }; KisPaintOpOption(KisPaintOpOption::PaintopCategory category, bool checked); ~KisPaintOpOption() override; KisPaintOpOption::PaintopCategory category() const; virtual bool isCheckable() const; virtual bool isChecked() const; virtual void setChecked(bool checked); void setLocked(bool value); bool isLocked() const; /** * Reimplement this to use the image in the option widget */ virtual void setImage(KisImageWSP image); virtual void setNode(KisNodeWSP node); void startReadOptionSetting(const KisPropertiesConfigurationSP setting); void startWriteOptionSetting(KisPropertiesConfigurationSP setting) const; QWidget *configurationPage() const; virtual void lodLimitations(KisPaintopLodLimitations *l) const; protected: void setConfigurationPage(QWidget *page); protected: /** * Re-implement this to save the configuration to the paint configuration. */ virtual void writeOptionSetting(KisPropertiesConfigurationSP setting) const { Q_UNUSED(setting); } /** - * Re-implement this to set te widgets with the values in @p setting. + * Re-implement this to set the widgets with the values in @p setting. */ virtual void readOptionSetting(const KisPropertiesConfigurationSP setting) { Q_UNUSED(setting); } protected Q_SLOTS: void emitSettingChanged(); void emitCheckedChanged(); Q_SIGNALS: /** * emit this whenever a setting has changed. It will update the preview */ void sigSettingChanged(); /** * emit this whenever a checked state of the option has changed. It as always * emitted *before* sigSettingChanged() */ void sigCheckedChanged(bool value); protected: bool m_checkable; bool m_locked; private: struct Private; Private* const m_d; }; #endif diff --git a/libs/ui/kis_safe_document_loader.cpp b/libs/ui/kis_safe_document_loader.cpp index 488d2cd426..343c1d4ae8 100644 --- a/libs/ui/kis_safe_document_loader.cpp +++ b/libs/ui/kis_safe_document_loader.cpp @@ -1,275 +1,275 @@ /* * Copyright (c) 2013 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_safe_document_loader.h" #include #include #include #include #include #include #include #include #include #include "KisDocument.h" #include #include "kis_signal_compressor.h" #include "KisPart.h" class FileSystemWatcherWrapper : public QObject { Q_OBJECT public: FileSystemWatcherWrapper() { connect(&m_watcher, SIGNAL(fileChanged(QString)), SIGNAL(fileChanged(QString))); connect(&m_watcher, SIGNAL(fileChanged(QString)), SLOT(slotFileChanged(QString))); } bool addPath(const QString &file) { bool result = true; const QString ufile = unifyFilePath(file); if (m_pathCount.contains(ufile)) { m_pathCount[ufile]++; } else { m_pathCount.insert(ufile, 1); result = m_watcher.addPath(ufile); } return result; } bool removePath(const QString &file) { bool result = true; const QString ufile = unifyFilePath(file); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(m_pathCount.contains(ufile), false); if (m_pathCount[ufile] == 1) { m_pathCount.remove(ufile); result = m_watcher.removePath(ufile); } else { m_pathCount[ufile]--; } return result; } QStringList files() const { return m_watcher.files(); } private Q_SLOTS: void slotFileChanged(const QString &path) { // re-add the file after QSaveFile optimization if (!m_watcher.files().contains(path) && QFileInfo(path).exists()) { m_watcher.addPath(path); } } Q_SIGNALS: void fileChanged(const QString &path); private: QString unifyFilePath(const QString &path) { return QFileInfo(path).absoluteFilePath(); } private: QFileSystemWatcher m_watcher; QHash m_pathCount; }; Q_GLOBAL_STATIC(FileSystemWatcherWrapper, s_fileSystemWatcher) struct KisSafeDocumentLoader::Private { Private() : fileChangedSignalCompressor(500 /* ms */, KisSignalCompressor::POSTPONE) { } QScopedPointer doc; KisSignalCompressor fileChangedSignalCompressor; bool isLoading = false; bool fileChangedFlag = false; QString path; QString temporaryPath; qint64 initialFileSize = 0; QDateTime initialFileTimeStamp; }; KisSafeDocumentLoader::KisSafeDocumentLoader(const QString &path, QObject *parent) : QObject(parent), m_d(new Private()) { connect(s_fileSystemWatcher, SIGNAL(fileChanged(QString)), SLOT(fileChanged(QString))); connect(&m_d->fileChangedSignalCompressor, SIGNAL(timeout()), SLOT(fileChangedCompressed())); setPath(path); } KisSafeDocumentLoader::~KisSafeDocumentLoader() { if (!m_d->path.isEmpty()) { s_fileSystemWatcher->removePath(m_d->path); } delete m_d; } void KisSafeDocumentLoader::setPath(const QString &path) { if (path.isEmpty()) return; if (!m_d->path.isEmpty()) { s_fileSystemWatcher->removePath(m_d->path); } m_d->path = path; s_fileSystemWatcher->addPath(m_d->path); } void KisSafeDocumentLoader::reloadImage() { fileChangedCompressed(true); } void KisSafeDocumentLoader::fileChanged(QString path) { if (path == m_d->path) { m_d->fileChangedFlag = true; m_d->fileChangedSignalCompressor.start(); } } void KisSafeDocumentLoader::fileChangedCompressed(bool sync) { if (m_d->isLoading) return; QFileInfo initialFileInfo(m_d->path); m_d->initialFileSize = initialFileInfo.size(); m_d->initialFileTimeStamp = initialFileInfo.lastModified(); if (s_fileSystemWatcher->files().contains(m_d->path) == false && initialFileInfo.exists()) { - //When a path is renamed it is removed, so we ought to readd it. + //When a path is renamed it is removed, so we ought to re-add it. s_fileSystemWatcher->addPath(m_d->path); } // it may happen when the file is flushed by // so other application if (!m_d->initialFileSize) return; m_d->isLoading = true; m_d->fileChangedFlag = false; m_d->temporaryPath = QDir::tempPath() + QDir::separator() + QString("krita_file_layer_copy_%1_%2.%3") .arg(QApplication::applicationPid()) .arg(qrand()) .arg(initialFileInfo.suffix()); QFile::copy(m_d->path, m_d->temporaryPath); if (!sync) { QTimer::singleShot(100, this, SLOT(delayedLoadStart())); } else { QApplication::processEvents(); delayedLoadStart(); } } void KisSafeDocumentLoader::delayedLoadStart() { QFileInfo originalInfo(m_d->path); QFileInfo tempInfo(m_d->temporaryPath); bool successfullyLoaded = false; if (!m_d->fileChangedFlag && originalInfo.size() == m_d->initialFileSize && originalInfo.lastModified() == m_d->initialFileTimeStamp && tempInfo.size() == m_d->initialFileSize) { m_d->doc.reset(KisPart::instance()->createDocument()); if (m_d->path.toLower().endsWith("ora") || m_d->path.toLower().endsWith("kra")) { QScopedPointer store(KoStore::createStore(m_d->temporaryPath, KoStore::Read)); if (store && !store->bad()) { if (store->open(QString("mergedimage.png"))) { QByteArray bytes = store->read(store->size()); store->close(); QImage mergedImage; mergedImage.loadFromData(bytes); Q_ASSERT(!mergedImage.isNull()); KisImageSP image = new KisImage(0, mergedImage.width(), mergedImage.height(), KoColorSpaceRegistry::instance()->rgb8(), ""); KisPaintLayerSP layer = new KisPaintLayer(image, "", OPACITY_OPAQUE_U8); layer->paintDevice()->convertFromQImage(mergedImage, 0); image->addNode(layer, image->rootLayer()); image->initialRefreshGraph(); m_d->doc->setCurrentImage(image); successfullyLoaded = true; } else { qWarning() << "delayedLoadStart: Could not open mergedimage.png"; } } else { qWarning() << "delayedLoadStart: Store was bad"; } } else { successfullyLoaded = m_d->doc->openUrl(QUrl::fromLocalFile(m_d->temporaryPath), KisDocument::DontAddToRecent); } } else { dbgKrita << "File was modified externally. Restarting."; dbgKrita << ppVar(m_d->fileChangedFlag); dbgKrita << ppVar(m_d->initialFileSize); dbgKrita << ppVar(m_d->initialFileTimeStamp); dbgKrita << ppVar(originalInfo.size()); dbgKrita << ppVar(originalInfo.lastModified()); dbgKrita << ppVar(tempInfo.size()); } QFile::remove(m_d->temporaryPath); m_d->isLoading = false; if (!successfullyLoaded) { // Restart the attempt m_d->fileChangedSignalCompressor.start(); } else { KisPaintDeviceSP paintDevice = new KisPaintDevice(m_d->doc->image()->colorSpace()); KisPaintDeviceSP projection = m_d->doc->image()->projection(); paintDevice->makeCloneFrom(projection, projection->extent()); emit loadingFinished(paintDevice, m_d->doc->image()->xRes(), m_d->doc->image()->yRes()); } m_d->doc.reset(); } #include "kis_safe_document_loader.moc" diff --git a/libs/ui/layerstyles/WdgColorOverlay.ui b/libs/ui/layerstyles/WdgColorOverlay.ui index 8c71bf73a5..90aec95387 100644 --- a/libs/ui/layerstyles/WdgColorOverlay.ui +++ b/libs/ui/layerstyles/WdgColorOverlay.ui @@ -1,136 +1,136 @@ WdgColorOverlay 0 0 400 300 Color Overlay Color QFormLayout::AllNonFixedFieldsGrow Ble&nd Mode: cmbCompositeOp - + 0 0 Set the blend mode for the layer ... &Opacity: intOpacity 0 0 10 15 Set the master opacity for the layer Adjust the transparency of the layer Qt::Vertical 20 151 KisColorButton QPushButton
kis_color_button.h
KisSliderSpinBox QWidget
kis_slider_spin_box.h
1
- KisCompositeOpComboBox + KisLayerStyleCompositeOpComboBox QComboBox
kis_cmb_composite.h
diff --git a/libs/ui/layerstyles/WdgGradientOverlay.ui b/libs/ui/layerstyles/WdgGradientOverlay.ui index 0bf1218d5a..d6ee60a257 100644 --- a/libs/ui/layerstyles/WdgGradientOverlay.ui +++ b/libs/ui/layerstyles/WdgGradientOverlay.ui @@ -1,284 +1,284 @@ WdgGradientOverlay 0 0 473 358 Gradient Overlay Gradient Ble&nd Mode: cmbCompositeOp - + 0 0 Set the blend mode for the layer Opac&ity: intOpacity 0 0 10 15 Set the master opacity for the layer Adjust the transparency of the layer &Gradient: cmbGradient 11 0 &Reverse St&yle: cmbStyle 0 0 Linear Radial Angle Reflected Diamond Ali&gn with Layer &Angle: angleSelector S&cale: intScale 0 0 Set size of gradation 0 0 Qt::Horizontal 40 20 label_6 intScale label_9 label_7 label_8 cmbCompositeOp intOpacity label_13 label_14 Qt::Vertical 20 40 KisSliderSpinBox QWidget
kis_slider_spin_box.h
1
- KisCompositeOpComboBox + KisLayerStyleCompositeOpComboBox QComboBox
kis_cmb_composite.h
KisLayerStyleAngleSelector QWidget
KisLayerStyleAngleSelector.h
1
KisCmbGradient QToolButton
kis_cmb_gradient.h
diff --git a/libs/ui/layerstyles/WdgPatternOverlay.ui b/libs/ui/layerstyles/WdgPatternOverlay.ui index b8de5af6fe..51d974dcde 100644 --- a/libs/ui/layerstyles/WdgPatternOverlay.ui +++ b/libs/ui/layerstyles/WdgPatternOverlay.ui @@ -1,190 +1,190 @@ WdgPatternOverlay 0 0 - 400 - 461 + 408 + 487 Pattern Overlay Pattern QFormLayout::AllNonFixedFieldsGrow Ble&nd Mode: cmbCompositeOp - + 0 0 Set the blend mode for the layer &Opacity: intOpacity 0 0 10 15 Set the master opacity for the layer Adjust the transparency of the layer Pattern: 0 0 250 250 QFrame::StyledPanel QFrame::Raised Sn&ap to Origin S&cale: intScale Set size of gradation Li&nk with layer Qt::Vertical 20 40 - - KisCompositeOpComboBox - QComboBox -
kis_cmb_composite.h
-
KisSliderSpinBox QWidget
kis_slider_spin_box.h
1
+ + KisLayerStyleCompositeOpComboBox + QComboBox +
kis_cmb_composite.h
+
KisPatternChooser QFrame
kis_pattern_chooser.h
1
diff --git a/libs/ui/layerstyles/WdgSatin.ui b/libs/ui/layerstyles/WdgSatin.ui index 1f40d07151..c935ff8243 100644 --- a/libs/ui/layerstyles/WdgSatin.ui +++ b/libs/ui/layerstyles/WdgSatin.ui @@ -1,260 +1,260 @@ WdgSatin 0 0 400 429 Satin Structure Ble&nd Mode: cmbCompositeOp - + 0 0 Set the blend mode for the layer ... Opaci&ty: intOpacity 0 0 10 15 Set the master opacity for the layer Adjust the transparency of the layer An&gle: angleSelector &Distance: intDistance 0 0 S&ize: intSize 0 0 Contour: Smooth the contour Anti-aliased &Invert 0 0 Qt::Horizontal 40 20 cmbContour chkAntiAliased intOpacity label_13 label_14 label_4 intDistance intSize label label_2 label_6 chkInvert Qt::Vertical 20 40 KisColorButton QPushButton
kis_color_button.h
KisSliderSpinBox QWidget
kis_slider_spin_box.h
1
- KisCompositeOpComboBox + KisLayerStyleCompositeOpComboBox QComboBox
kis_cmb_composite.h
KisLayerStyleAngleSelector QWidget
KisLayerStyleAngleSelector.h
1
KisCmbContour QWidget
kis_cmb_contour.h
diff --git a/libs/ui/layerstyles/WdgStroke.ui b/libs/ui/layerstyles/WdgStroke.ui index 761fc4d488..1bdd958021 100644 --- a/libs/ui/layerstyles/WdgStroke.ui +++ b/libs/ui/layerstyles/WdgStroke.ui @@ -1,451 +1,451 @@ WdgStroke 0 0 457 781 Stroke Structure S&ize: intSize 0 0 Positio&n: cmbPosition 0 0 Outside Inside Center &Blend Mode: cmbCompositeOp - + 0 0 Opacit&y: intOpacity 0 0 Fill Color Gradient Pattern 1 Color: ... &Gradient: cmbGradient 0 0 &Reverse St&yle: cmbStyle 0 0 Linear Radial Angle Reflected Diamond Ali&gn with Layer &Angle: angleSelector S&cale: intScale 0 0 Set size of gradation 0 0 Qt::Horizontal 40 20 Pattern: 0 0 250 250 QFrame::StyledPanel QFrame::Raised Sn&ap to Origin S&cale: intScale Set size of gradation Lin&k with Layer Qt::Vertical 20 40 KisColorButton QPushButton
kis_color_button.h
KisSliderSpinBox QWidget
kis_slider_spin_box.h
1
- KisCompositeOpComboBox + KisLayerStyleCompositeOpComboBox QComboBox
kis_cmb_composite.h
KisLayerStyleAngleSelector QWidget
KisLayerStyleAngleSelector.h
1
KisCmbGradient QToolButton
kis_cmb_gradient.h
KisPatternChooser QFrame
kis_pattern_chooser.h
1
diff --git a/libs/ui/layerstyles/wdgBevelAndEmboss.ui b/libs/ui/layerstyles/wdgBevelAndEmboss.ui index 39cb1071c0..4298ffa2bf 100644 --- a/libs/ui/layerstyles/wdgBevelAndEmboss.ui +++ b/libs/ui/layerstyles/wdgBevelAndEmboss.ui @@ -1,437 +1,437 @@ WdgBevelAndEmboss 0 0 453 669 Bevel and Emboss Structure St&yle: cmbStyle 0 0 Outer Bevel Inner Bevel Emboss Pillow Emboss Stroke Emboss &Technique: cmbTechnique 0 0 Smooth Chisel Hard Chisel Soft &Depth: intDepth 0 0 Direction: cmbDirection 0 0 Up Down Si&ze: intSize 0 0 So&ften: intSoften 0 0 Shading &Angle: angleSelector A&ltitude: intAltitude Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter -179 180 &Gloss Contour: cmbContour Anti-aliased H&ighlight Mode: cmbHighlightMode - + Set the blend mode for the layer ... &Opacity: intOpacity 0 0 10 15 Set the master opacity for the layer Adjust the transparency of the layer Sh&adow Mode: cmbShadowMode - + Set the blend mode for the layer ... &Opacity: intOpacity2 0 0 10 15 Set the master opacity for the layer Adjust the transparency of the layer Qt::Vertical 20 40 - - KisIntParseSpinBox - QSpinBox -
kis_int_parse_spin_box.h
-
KisColorButton QPushButton
kis_color_button.h
KisSliderSpinBox QWidget
kis_slider_spin_box.h
1
- KisCompositeOpComboBox + KisLayerStyleCompositeOpComboBox QComboBox
kis_cmb_composite.h
KisLayerStyleAngleSelector QWidget
KisLayerStyleAngleSelector.h
1
+ + KisIntParseSpinBox + QSpinBox +
kis_int_parse_spin_box.h
+
diff --git a/libs/ui/layerstyles/wdgInnerGlow.ui b/libs/ui/layerstyles/wdgInnerGlow.ui index ce23fc693f..79b7a90f04 100644 --- a/libs/ui/layerstyles/wdgInnerGlow.ui +++ b/libs/ui/layerstyles/wdgInnerGlow.ui @@ -1,405 +1,405 @@ WdgInnerGlow 0 0 298 476 Inner Glow Structure Ble&nd Mode: cmbCompositeOp - + 0 0 Set the blend mode for the layer &Opacity: intOpacity 0 0 10 15 Set the master opacity for the layer Adjust the transparency of the layer &Noise: intNoise 0 0 10 15 Set the master opacity for the layer Adjust the transparency of the layer ... 0 0 Elements &Technique: cmbTechnique 0 0 Softer Precise &Source: cmbSource &Choke: intChoke 0 0 % 100 &Size: intSize 0 0 px 250 0 0 Center Edge Quality Contour: Anti-aliased &Range: intRange 0 0 % 1 100 &Jitter: intJitter 0 0 % 100 Qt::Vertical 20 40 KisColorButton QPushButton
kis_color_button.h
KisSliderSpinBox QWidget
kis_slider_spin_box.h
1
- KisCompositeOpComboBox + KisLayerStyleCompositeOpComboBox QComboBox
kis_cmb_composite.h
- - KisCmbContour - QWidget -
kis_cmb_contour.h
-
KisCmbGradient QToolButton
kis_cmb_gradient.h
+ + KisCmbContour + QWidget +
kis_cmb_contour.h
+
diff --git a/libs/ui/layerstyles/wdgdropshadow.ui b/libs/ui/layerstyles/wdgdropshadow.ui index 5572b503a6..b3c07dd271 100644 --- a/libs/ui/layerstyles/wdgdropshadow.ui +++ b/libs/ui/layerstyles/wdgdropshadow.ui @@ -1,282 +1,282 @@ WdgDropShadow 0 0 491 495 Drop Shadow Qt::Vertical 20 40 Use to obscure the shadow when fill is transparent Layer knocks O&ut Drop Shadow Quality Contour: 0 0 Smooth the contour Anti-aliased &Noise: intNoise 0 0 Add noise to shadow Structure - + 0 0 Set the blend mode for the layer 0 0 0 0 10 15 Set the master opacity for the layer Adjust the transparency of the layer &Distance: intDistance 0 0 Opaci&ty: intOpacity An&gle: angleSelector Sp&read: intSpread 0 0 S&ize: intSize &Blend Mode: cmbCompositeOp KisColorButton QPushButton
kis_color_button.h
KisSliderSpinBox QWidget
kis_slider_spin_box.h
1
- KisCompositeOpComboBox + KisLayerStyleCompositeOpComboBox QComboBox
kis_cmb_composite.h
KisLayerStyleAngleSelector QWidget
KisLayerStyleAngleSelector.h
1
KisCmbContour QWidget
kis_cmb_contour.h
diff --git a/libs/ui/opengl/KisOpenGLUpdateInfoBuilder.cpp b/libs/ui/opengl/KisOpenGLUpdateInfoBuilder.cpp index c34367cbd0..07b1a4081e 100644 --- a/libs/ui/opengl/KisOpenGLUpdateInfoBuilder.cpp +++ b/libs/ui/opengl/KisOpenGLUpdateInfoBuilder.cpp @@ -1,272 +1,272 @@ /* * 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 "KisOpenGLUpdateInfoBuilder.h" // TODO: conversion options into a separate file! #include "kis_update_info.h" #include "opengl/kis_texture_tile_info_pool.h" #include "KisProofingConfiguration.h" #include #include #include struct KRITAUI_NO_EXPORT KisOpenGLUpdateInfoBuilder::Private { ConversionOptions conversionOptions; QBitArray channelFlags; bool onlyOneChannelSelected = false; int selectedChannelIndex = -1; int textureBorder = 0; QSize effectiveTextureSize; KisProofingConfigurationSP proofingConfig; QScopedPointer proofingTransform; KisTextureTileInfoPoolSP pool; QReadWriteLock lock; }; KisOpenGLUpdateInfoBuilder::KisOpenGLUpdateInfoBuilder() : m_d(new Private) { } KisOpenGLUpdateInfoBuilder::~KisOpenGLUpdateInfoBuilder() { } KisOpenGLUpdateInfoSP KisOpenGLUpdateInfoBuilder::buildUpdateInfo(const QRect &rect, KisImageSP srcImage, bool convertColorSpace) { return buildUpdateInfo(rect, srcImage->projection(), srcImage->bounds(), srcImage->currentLevelOfDetail(), convertColorSpace); } KisOpenGLUpdateInfoSP KisOpenGLUpdateInfoBuilder::buildUpdateInfo(const QRect &rect, KisPaintDeviceSP projection, const QRect &bounds, int levelOfDetail, bool convertColorSpace) { KisOpenGLUpdateInfoSP info = new KisOpenGLUpdateInfo(); QRect updateRect = rect & bounds; if (updateRect.isEmpty()) return info; KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(m_d->pool, info); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(m_d->conversionOptions.m_destinationColorSpace, info); auto needCreateProofingTransform = [this] () { return !m_d->proofingTransform && m_d->proofingConfig && m_d->proofingConfig->conversionFlags.testFlag(KoColorConversionTransformation::SoftProofing); }; // lazily create transform if (convertColorSpace && needCreateProofingTransform()) { QWriteLocker locker(&m_d->lock); if (needCreateProofingTransform()) { const KoColorSpace *proofingSpace = KoColorSpaceRegistry::instance()->colorSpace(m_d->proofingConfig->proofingModel, m_d->proofingConfig->proofingDepth, m_d->proofingConfig->proofingProfile); m_d->proofingTransform.reset(KisTextureTileUpdateInfo::generateProofingTransform( projection->colorSpace(), m_d->conversionOptions.m_destinationColorSpace, proofingSpace, m_d->conversionOptions.m_renderingIntent, m_d->proofingConfig->intent, m_d->proofingConfig->conversionFlags, m_d->proofingConfig->warningColor, m_d->proofingConfig->adaptationState)); } } QReadLocker locker(&m_d->lock); /** * Why the rect is artificial? That's easy! * It does not represent any real piece of the image. It is - * intentionally stretched to get through the overlappping + * intentionally stretched to get through the overlapping * stripes of neutrality and poke neighbouring tiles. * Thanks to the rect we get the coordinates of all the tiles * involved into update process */ QRect artificialRect = kisGrowRect(updateRect, m_d->textureBorder); artificialRect &= bounds; int firstColumn = xToCol(artificialRect.left()); int lastColumn = xToCol(artificialRect.right()); int firstRow = yToRow(artificialRect.top()); int lastRow = yToRow(artificialRect.bottom()); QBitArray channelFlags; // empty by default if (!m_d->channelFlags.isEmpty() && m_d->channelFlags.size() == projection->colorSpace()->channels().size()) { channelFlags = m_d->channelFlags; } qint32 numItems = (lastColumn - firstColumn + 1) * (lastRow - firstRow + 1); info->tileList.reserve(numItems); QRect alignedUpdateRect = updateRect; QRect alignedBounds = bounds; if (levelOfDetail) { alignedUpdateRect = KisLodTransform::alignedRect(alignedUpdateRect, levelOfDetail); alignedBounds = KisLodTransform::alignedRect(alignedBounds, levelOfDetail); } for (int col = firstColumn; col <= lastColumn; col++) { for (int row = firstRow; row <= lastRow; row++) { const QRect alignedTileTextureRect = calculatePhysicalTileRect(col, row, bounds, levelOfDetail); KisTextureTileUpdateInfoSP tileInfo( new KisTextureTileUpdateInfo(col, row, alignedTileTextureRect, alignedUpdateRect, alignedBounds, levelOfDetail, m_d->pool)); // Don't update empty tiles if (tileInfo->valid()) { tileInfo->retrieveData(projection, channelFlags, m_d->onlyOneChannelSelected, m_d->selectedChannelIndex); if (convertColorSpace) { if (m_d->proofingTransform) { tileInfo->proofTo(m_d->conversionOptions.m_destinationColorSpace, m_d->proofingConfig->conversionFlags, m_d->proofingTransform.data()); } else { tileInfo->convertTo(m_d->conversionOptions.m_destinationColorSpace, m_d->conversionOptions.m_renderingIntent, m_d->conversionOptions.m_conversionFlags); } } info->tileList.append(tileInfo); } else { dbgUI << "Trying to create an empty tileinfo record" << col << row << alignedTileTextureRect << updateRect << bounds; } } } info->assignDirtyImageRect(rect); info->assignLevelOfDetail(levelOfDetail); return info; } QRect KisOpenGLUpdateInfoBuilder::calculateEffectiveTileRect(int col, int row, const QRect &imageBounds) const { return imageBounds & QRect(col * m_d->effectiveTextureSize.width(), row * m_d->effectiveTextureSize.height(), m_d->effectiveTextureSize.width(), m_d->effectiveTextureSize.height()); } QRect KisOpenGLUpdateInfoBuilder::calculatePhysicalTileRect(int col, int row, const QRect &imageBounds, int levelOfDetail) const { const QRect tileRect = calculateEffectiveTileRect(col, row, imageBounds); const QRect tileTextureRect = kisGrowRect(tileRect, m_d->textureBorder); const QRect alignedTileTextureRect = levelOfDetail ? KisLodTransform::alignedRect(tileTextureRect, levelOfDetail) : tileTextureRect; return alignedTileTextureRect; } int KisOpenGLUpdateInfoBuilder::xToCol(int x) const { return x / m_d->effectiveTextureSize.width(); } int KisOpenGLUpdateInfoBuilder::yToRow(int y) const { return y / m_d->effectiveTextureSize.height(); } const KoColorSpace *KisOpenGLUpdateInfoBuilder::destinationColorSpace() const { QReadLocker lock(&m_d->lock); return m_d->conversionOptions.m_destinationColorSpace; } void KisOpenGLUpdateInfoBuilder::setConversionOptions(const ConversionOptions &options) { QWriteLocker lock(&m_d->lock); m_d->conversionOptions = options; } void KisOpenGLUpdateInfoBuilder::setChannelFlags(const QBitArray &channelFrags, bool onlyOneChannelSelected, int selectedChannelIndex) { QWriteLocker lock(&m_d->lock); m_d->channelFlags = channelFrags; m_d->onlyOneChannelSelected = onlyOneChannelSelected; m_d->selectedChannelIndex = selectedChannelIndex; } void KisOpenGLUpdateInfoBuilder::setTextureBorder(int value) { QWriteLocker lock(&m_d->lock); m_d->textureBorder = value; } void KisOpenGLUpdateInfoBuilder::setEffectiveTextureSize(const QSize &size) { QWriteLocker lock(&m_d->lock); m_d->effectiveTextureSize = size; } void KisOpenGLUpdateInfoBuilder::setTextureInfoPool(KisTextureTileInfoPoolSP pool) { QWriteLocker lock(&m_d->lock); m_d->pool = pool; } KisTextureTileInfoPoolSP KisOpenGLUpdateInfoBuilder::textureInfoPool() const { QReadLocker lock(&m_d->lock); return m_d->pool; } void KisOpenGLUpdateInfoBuilder::setProofingConfig(KisProofingConfigurationSP config) { QWriteLocker lock(&m_d->lock); m_d->proofingConfig = config; m_d->proofingTransform.reset(); } KisProofingConfigurationSP KisOpenGLUpdateInfoBuilder::proofingConfig() const { QReadLocker lock(&m_d->lock); return m_d->proofingConfig; } diff --git a/libs/ui/opengl/kis_opengl.cpp b/libs/ui/opengl/kis_opengl.cpp index 793853dacf..bcfa3a7c9c 100644 --- a/libs/ui/opengl/kis_opengl.cpp +++ b/libs/ui/opengl/kis_opengl.cpp @@ -1,913 +1,913 @@ /* * Copyright (c) 2007 Adrian Page * * 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 "opengl/kis_opengl.h" #include "opengl/kis_opengl_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KisOpenGLModeProber.h" #include #include #include "kis_assert.h" #include #include #include #ifndef GL_RENDERER # define GL_RENDERER 0x1F01 #endif using namespace KisOpenGLPrivate; namespace { // config option, set manually by main() bool g_isDebugSynchronous = false; bool g_sanityDefaultFormatIsSet = false; boost::optional openGLCheckResult; bool g_needsFenceWorkaround = false; bool g_needsPixmapCacheWorkaround = false; QString g_surfaceFormatDetectionLog; QString g_debugText("OpenGL Info\n **OpenGL not initialized**"); QVector g_openglWarningStrings; KisOpenGL::OpenGLRenderers g_supportedRenderers; KisOpenGL::OpenGLRenderer g_rendererPreferredByQt; void overrideSupportedRenderers(KisOpenGL::OpenGLRenderers supportedRenderers, KisOpenGL::OpenGLRenderer preferredByQt) { g_supportedRenderers = supportedRenderers; g_rendererPreferredByQt = preferredByQt; } void openglOnMessageLogged(const QOpenGLDebugMessage& debugMessage) { qDebug() << "OpenGL:" << debugMessage; } KisOpenGL::OpenGLRenderer getRendererFromProbeResult(KisOpenGLModeProber::Result info) { KisOpenGL::OpenGLRenderer result = KisOpenGL::RendererDesktopGL; if (info.isOpenGLES()) { const QString rendererString = info.rendererString().toLower(); if (rendererString.contains("basic render driver") || rendererString.contains("software")) { result = KisOpenGL::RendererSoftware; } else { result = KisOpenGL::RendererOpenGLES; } } return result; } } KisOpenGLPrivate::OpenGLCheckResult::OpenGLCheckResult(QOpenGLContext &context) { if (!context.isValid()) { return; } QOpenGLFunctions *funcs = context.functions(); // funcs is ready to be used m_rendererString = QString(reinterpret_cast(funcs->glGetString(GL_RENDERER))); m_driverVersionString = QString(reinterpret_cast(funcs->glGetString(GL_VERSION))); m_glMajorVersion = context.format().majorVersion(); m_glMinorVersion = context.format().minorVersion(); m_supportsDeprecatedFunctions = (context.format().options() & QSurfaceFormat::DeprecatedFunctions); m_isOpenGLES = context.isOpenGLES(); } void KisOpenGLPrivate::appendOpenGLWarningString(KLocalizedString warning) { g_openglWarningStrings << warning; } void KisOpenGLPrivate::overrideOpenGLWarningString(QVector warnings) { g_openglWarningStrings = warnings; } void KisOpenGL::initialize() { if (openGLCheckResult) return; KIS_SAFE_ASSERT_RECOVER_NOOP(g_sanityDefaultFormatIsSet); KisOpenGL::RendererConfig config; config.format = QSurfaceFormat::defaultFormat(); openGLCheckResult = KisOpenGLModeProber::instance()->probeFormat(config, false); g_debugText.clear(); QDebug debugOut(&g_debugText); debugOut << "OpenGL Info\n"; if (openGLCheckResult) { debugOut << "\n Vendor: " << openGLCheckResult->vendorString(); debugOut << "\n Renderer: " << openGLCheckResult->rendererString(); debugOut << "\n Version: " << openGLCheckResult->driverVersionString(); debugOut << "\n Shading language: " << openGLCheckResult->shadingLanguageString(); debugOut << "\n Requested format: " << QSurfaceFormat::defaultFormat(); debugOut << "\n Current format: " << openGLCheckResult->format(); debugOut.nospace(); debugOut << "\n Version: " << openGLCheckResult->glMajorVersion() << "." << openGLCheckResult->glMinorVersion(); debugOut.resetFormat(); debugOut << "\n Supports deprecated functions" << openGLCheckResult->supportsDeprecatedFunctions(); debugOut << "\n is OpenGL ES:" << openGLCheckResult->isOpenGLES(); } debugOut << "\n\nQPA OpenGL Detection Info"; debugOut << "\n supportsDesktopGL:" << bool(g_supportedRenderers & RendererDesktopGL); #ifdef Q_OS_WIN debugOut << "\n supportsAngleD3D11:" << bool(g_supportedRenderers & RendererOpenGLES); debugOut << "\n isQtPreferAngle:" << bool(g_rendererPreferredByQt == RendererOpenGLES); #else debugOut << "\n supportsOpenGLES:" << bool(g_supportedRenderers & RendererOpenGLES); debugOut << "\n isQtPreferOpenGLES:" << bool(g_rendererPreferredByQt == RendererOpenGLES); #endif - debugOut << "\n== log ==\n"; - debugOut.noquote(); - debugOut << g_surfaceFormatDetectionLog; - debugOut.resetFormat(); - debugOut << "\n== end log =="; +// debugOut << "\n== log ==\n"; +// debugOut.noquote(); +// debugOut << g_surfaceFormatDetectionLog; +// debugOut.resetFormat(); +// debugOut << "\n== end log =="; dbgOpenGL.noquote().nospace() << g_debugText; KisUsageLogger::write(g_debugText); if (!openGLCheckResult) { return; } // Check if we have a bugged driver that needs fence workaround bool isOnX11 = false; #ifdef HAVE_X11 isOnX11 = true; #endif KisConfig cfg(true); if ((isOnX11 && openGLCheckResult->rendererString().startsWith("AMD")) || cfg.forceOpenGLFenceWorkaround()) { g_needsFenceWorkaround = true; } /** * NVidia + Qt's openGL don't play well together and one cannot * draw a pixmap on a widget more than once in one rendering cycle. * * It can be workarounded by drawing strictly via QPixmapCache and * only when the pixmap size in bigger than doubled size of the * display framebuffer. That is for 8-bit HD display, you should have * a cache bigger than 16 MiB. Don't ask me why. (DK) * * See bug: https://bugs.kde.org/show_bug.cgi?id=361709 * * TODO: check if this workaround is still needed after merging * Qt5+openGL3 branch. */ if (openGLCheckResult->vendorString().toUpper().contains("NVIDIA")) { g_needsPixmapCacheWorkaround = true; const QRect screenSize = QGuiApplication::primaryScreen()->availableGeometry(); const int minCacheSize = 20 * 1024; const int cacheSize = 2048 + 2 * 4 * screenSize.width() * screenSize.height() / 1024; //KiB QPixmapCache::setCacheLimit(qMax(minCacheSize, cacheSize)); } } void KisOpenGL::initializeContext(QOpenGLContext *ctx) { KisConfig cfg(true); initialize(); const bool isDebugEnabled = ctx->format().testOption(QSurfaceFormat::DebugContext); dbgUI << "OpenGL: Opening new context"; if (isDebugEnabled) { // Passing ctx for ownership management only, not specifying context. // QOpenGLDebugLogger only function on the current active context. // FIXME: Do we need to make sure ctx is the active context? QOpenGLDebugLogger* openglLogger = new QOpenGLDebugLogger(ctx); if (openglLogger->initialize()) { qDebug() << "QOpenGLDebugLogger is initialized. Check whether you get a message below."; QObject::connect(openglLogger, &QOpenGLDebugLogger::messageLogged, &openglOnMessageLogged); openglLogger->startLogging(g_isDebugSynchronous ? QOpenGLDebugLogger::SynchronousLogging : QOpenGLDebugLogger::AsynchronousLogging); openglLogger->logMessage(QOpenGLDebugMessage::createApplicationMessage(QStringLiteral("QOpenGLDebugLogger is logging."))); } else { qDebug() << "QOpenGLDebugLogger cannot be initialized."; delete openglLogger; } } // Double check we were given the version we requested QSurfaceFormat format = ctx->format(); QOpenGLFunctions *f = ctx->functions(); f->initializeOpenGLFunctions(); QFile log(QStandardPaths::writableLocation(QStandardPaths::TempLocation) + "/krita-opengl.txt"); log.open(QFile::WriteOnly); QString vendor((const char*)f->glGetString(GL_VENDOR)); log.write(vendor.toLatin1()); log.write(", "); log.write(openGLCheckResult->rendererString().toLatin1()); log.write(", "); QString version((const char*)f->glGetString(GL_VERSION)); log.write(version.toLatin1()); log.close(); } const QString &KisOpenGL::getDebugText() { initialize(); return g_debugText; } QStringList KisOpenGL::getOpenGLWarnings() { QStringList strings; Q_FOREACH (const KLocalizedString &item, g_openglWarningStrings) { strings << item.toString(); } return strings; } // XXX Temporary function to allow LoD on OpenGL3 without triggering // all of the other 3.2 functionality, can be removed once we move to Qt5.7 bool KisOpenGL::supportsLoD() { initialize(); return openGLCheckResult && openGLCheckResult->supportsLoD(); } bool KisOpenGL::hasOpenGL3() { initialize(); return openGLCheckResult && openGLCheckResult->hasOpenGL3(); } bool KisOpenGL::hasOpenGLES() { initialize(); return openGLCheckResult && openGLCheckResult->isOpenGLES(); } bool KisOpenGL::supportsFenceSync() { initialize(); return openGLCheckResult && openGLCheckResult->supportsFenceSync(); } bool KisOpenGL::needsFenceWorkaround() { initialize(); return g_needsFenceWorkaround; } bool KisOpenGL::needsPixmapCacheWorkaround() { initialize(); return g_needsPixmapCacheWorkaround; } void KisOpenGL::testingInitializeDefaultSurfaceFormat() { setDefaultSurfaceConfig(selectSurfaceConfig(KisOpenGL::RendererAuto, KisConfig::BT709_G22, false)); } void KisOpenGL::setDebugSynchronous(bool value) { g_isDebugSynchronous = value; } KisOpenGL::OpenGLRenderer KisOpenGL::getCurrentOpenGLRenderer() { if (!openGLCheckResult) return RendererAuto; return getRendererFromProbeResult(*openGLCheckResult); } KisOpenGL::OpenGLRenderer KisOpenGL::getQtPreferredOpenGLRenderer() { return g_rendererPreferredByQt; } KisOpenGL::OpenGLRenderers KisOpenGL::getSupportedOpenGLRenderers() { return g_supportedRenderers; } KisOpenGL::OpenGLRenderer KisOpenGL::getUserPreferredOpenGLRendererConfig() { const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); return convertConfigToOpenGLRenderer(kritarc.value("OpenGLRenderer", "auto").toString()); } void KisOpenGL::setUserPreferredOpenGLRendererConfig(KisOpenGL::OpenGLRenderer renderer) { const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); kritarc.setValue("OpenGLRenderer", KisOpenGL::convertOpenGLRendererToConfig(renderer)); } QString KisOpenGL::convertOpenGLRendererToConfig(KisOpenGL::OpenGLRenderer renderer) { switch (renderer) { case RendererNone: return QStringLiteral("none"); case RendererSoftware: return QStringLiteral("software"); case RendererDesktopGL: return QStringLiteral("desktop"); case RendererOpenGLES: return QStringLiteral("angle"); default: return QStringLiteral("auto"); } } KisOpenGL::OpenGLRenderer KisOpenGL::convertConfigToOpenGLRenderer(QString renderer) { if (renderer == "desktop") { return RendererDesktopGL; } else if (renderer == "angle") { return RendererOpenGLES; } else if (renderer == "software") { return RendererSoftware; } else if (renderer == "none") { return RendererNone; } else { return RendererAuto; } } KisOpenGL::OpenGLRenderer KisOpenGL::RendererConfig::rendererId() const { KisOpenGL::OpenGLRenderer result = RendererAuto; if (format.renderableType() == QSurfaceFormat::OpenGLES && angleRenderer == AngleRendererD3d11Warp) { result = RendererSoftware; } else if (format.renderableType() == QSurfaceFormat::OpenGLES && angleRenderer == AngleRendererD3d11) { result = RendererOpenGLES; } else if (format.renderableType() == QSurfaceFormat::OpenGL) { result = RendererDesktopGL; } else if (format.renderableType() == QSurfaceFormat::DefaultRenderableType && angleRenderer == AngleRendererD3d11) { // noop } else { qWarning() << "WARNING: unsupported combination of OpenGL renderer" << ppVar(format.renderableType()) << ppVar(angleRenderer); } return result; } namespace { typedef std::pair RendererInfo; RendererInfo getRendererInfo(KisOpenGL::OpenGLRenderer renderer) { RendererInfo info = {QSurfaceFormat::DefaultRenderableType, KisOpenGL::AngleRendererD3d11}; switch (renderer) { case KisOpenGL::RendererNone: info = {QSurfaceFormat::DefaultRenderableType, KisOpenGL::AngleRendererDefault}; break; case KisOpenGL::RendererAuto: break; case KisOpenGL::RendererDesktopGL: info = {QSurfaceFormat::OpenGL, KisOpenGL::AngleRendererD3d11}; break; case KisOpenGL::RendererOpenGLES: info = {QSurfaceFormat::OpenGLES, KisOpenGL::AngleRendererD3d11}; break; case KisOpenGL::RendererSoftware: info = {QSurfaceFormat::OpenGLES, KisOpenGL::AngleRendererD3d11Warp}; break; } return info; } KisOpenGL::RendererConfig generateSurfaceConfig(KisOpenGL::OpenGLRenderer renderer, KisConfig::RootSurfaceFormat rootSurfaceFormat, bool debugContext) { RendererInfo info = getRendererInfo(renderer); KisOpenGL::RendererConfig config; config.angleRenderer = info.second; QSurfaceFormat &format = config.format; #ifdef Q_OS_MACOS format.setVersion(3, 2); format.setProfile(QSurfaceFormat::CoreProfile); #elif !defined(Q_OS_ANDROID) // XXX This can be removed once we move to Qt5.7 format.setVersion(3, 0); format.setProfile(QSurfaceFormat::CompatibilityProfile); format.setOptions(QSurfaceFormat::DeprecatedFunctions); #endif format.setDepthBufferSize(24); format.setStencilBufferSize(8); KisOpenGLModeProber::initSurfaceFormatFromConfig(rootSurfaceFormat, &format); format.setRenderableType(info.first); format.setSwapBehavior(QSurfaceFormat::DoubleBuffer); format.setSwapInterval(0); // Disable vertical refresh syncing if (debugContext) { format.setOption(QSurfaceFormat::DebugContext, true); } return config; } bool isOpenGLRendererBlacklisted(const QString &rendererString, const QString &driverVersionString, QVector *warningMessage) { bool isBlacklisted = false; #ifndef Q_OS_WIN Q_UNUSED(rendererString); Q_UNUSED(driverVersionString); Q_UNUSED(warningMessage); #else // Special blacklisting of OpenGL/ANGLE is tracked on: // https://phabricator.kde.org/T7411 // HACK: Specifically detect for Intel driver build number // See https://www.intel.com/content/www/us/en/support/articles/000005654/graphics-drivers.html if (rendererString.startsWith("Intel")) { KLocalizedString knownBadIntelWarning = ki18n("The Intel graphics driver in use is known to have issues with OpenGL."); KLocalizedString grossIntelWarning = ki18n( "Intel graphics drivers tend to have issues with OpenGL so ANGLE will be used by default. " "You may manually switch to OpenGL but it is not guaranteed to work properly." ); QRegularExpression regex("\\b\\d{1,2}\\.\\d{2}\\.\\d{1,3}\\.(\\d{4})\\b"); QRegularExpressionMatch match = regex.match(driverVersionString); if (match.hasMatch()) { int driverBuild = match.captured(1).toInt(); if (driverBuild > 4636 && driverBuild < 4729) { // Make ANGLE the preferred renderer for Intel driver versions // between build 4636 and 4729 (exclusive) due to an UI offset bug. // See https://communities.intel.com/thread/116003 // (Build 4636 is known to work from some test results) qDebug() << "Detected Intel driver build between 4636 and 4729, making ANGLE the preferred renderer"; isBlacklisted = true; *warningMessage << knownBadIntelWarning; } else if (driverBuild == 4358) { // There are several reports on a bug where the canvas is not being // updated properly which has debug info pointing to this build. qDebug() << "Detected Intel driver build 4358, making ANGLE the preferred renderer"; isBlacklisted = true; *warningMessage << knownBadIntelWarning; } else { // Intel tends to randomly break OpenGL in some of their new driver // builds, therefore we just shouldn't use OpenGL by default to // reduce bug report noises. qDebug() << "Detected Intel driver, making ANGLE the preferred renderer"; isBlacklisted = true; *warningMessage << grossIntelWarning; } } else { // In case Intel changed the driver version format to something that // we don't understand, we still select ANGLE. qDebug() << "Detected Intel driver with unknown version format, making ANGLE the preferred renderer"; isBlacklisted = true; *warningMessage << grossIntelWarning; } } #endif return isBlacklisted; } boost::optional orderPreference(bool lhs, bool rhs) { if (lhs == rhs) return boost::none; if (lhs && !rhs) return true; if (!lhs && rhs) return false; return false; } #define ORDER_BY(lhs, rhs) if (auto res = orderPreference((lhs), (rhs))) { return *res; } class FormatPositionLess { public: FormatPositionLess() { } bool operator()(const KisOpenGL::RendererConfig &lhs, const KisOpenGL::RendererConfig &rhs) const { KIS_SAFE_ASSERT_RECOVER_NOOP(m_preferredColorSpace != KisSurfaceColorSpace::DefaultColorSpace); if (m_preferredRendererByUser != KisOpenGL::RendererSoftware) { ORDER_BY(!isFallbackOnly(lhs.rendererId()), !isFallbackOnly(rhs.rendererId())); } #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) ORDER_BY(isPreferredColorSpace(lhs.format.colorSpace()), isPreferredColorSpace(rhs.format.colorSpace())); #endif if (doPreferHDR()) { ORDER_BY(isHDRFormat(lhs.format), isHDRFormat(rhs.format)); } else { ORDER_BY(!isHDRFormat(lhs.format), !isHDRFormat(rhs.format)); } if (m_preferredRendererByUser != KisOpenGL::RendererAuto) { ORDER_BY(lhs.rendererId() == m_preferredRendererByUser, rhs.rendererId() == m_preferredRendererByUser); } ORDER_BY(!isBlacklisted(lhs.rendererId()), !isBlacklisted(rhs.rendererId())); if (doPreferHDR() && m_preferredRendererByHDR != KisOpenGL::RendererAuto) { ORDER_BY(lhs.rendererId() == m_preferredRendererByHDR, rhs.rendererId() == m_preferredRendererByHDR); } KIS_SAFE_ASSERT_RECOVER_NOOP(m_preferredRendererByQt != KisOpenGL::RendererAuto); ORDER_BY(lhs.rendererId() == m_preferredRendererByQt, rhs.rendererId() == m_preferredRendererByQt); return false; } public: void setPreferredColorSpace(const KisSurfaceColorSpace &preferredColorSpace) { m_preferredColorSpace = preferredColorSpace; } void setPreferredRendererByQt(const KisOpenGL::OpenGLRenderer &preferredRendererByQt) { m_preferredRendererByQt = preferredRendererByQt; } void setPreferredRendererByUser(const KisOpenGL::OpenGLRenderer &preferredRendererByUser) { m_preferredRendererByUser = preferredRendererByUser; } void setPreferredRendererByHDR(const KisOpenGL::OpenGLRenderer &preferredRendererByHDR) { m_preferredRendererByHDR = preferredRendererByHDR; } void setOpenGLBlacklisted(bool openGLBlacklisted) { m_openGLBlacklisted = openGLBlacklisted; } void setOpenGLESBlacklisted(bool openGLESBlacklisted) { m_openGLESBlacklisted = openGLESBlacklisted; } bool isOpenGLBlacklisted() const { return m_openGLBlacklisted; } bool isOpenGLESBlacklisted() const { return m_openGLESBlacklisted; } KisSurfaceColorSpace preferredColorSpace() const { return m_preferredColorSpace; } KisOpenGL::OpenGLRenderer preferredRendererByUser() const { return m_preferredRendererByUser; } private: bool isHDRFormat(const QSurfaceFormat &f) const { #ifdef HAVE_HDR return f.colorSpace() == KisSurfaceColorSpace::bt2020PQColorSpace || f.colorSpace() == KisSurfaceColorSpace::scRGBColorSpace; #else Q_UNUSED(f); return false; #endif } bool isFallbackOnly(KisOpenGL::OpenGLRenderer r) const { return r == KisOpenGL::RendererSoftware; } bool isBlacklisted(KisOpenGL::OpenGLRenderer r) const { KIS_SAFE_ASSERT_RECOVER_NOOP(r == KisOpenGL::RendererAuto || r == KisOpenGL::RendererDesktopGL || r == KisOpenGL::RendererOpenGLES || r == KisOpenGL::RendererSoftware || r == KisOpenGL::RendererNone); return (r == KisOpenGL::RendererDesktopGL && m_openGLBlacklisted) || (r == KisOpenGL::RendererOpenGLES && m_openGLESBlacklisted) || (r == KisOpenGL::RendererSoftware && m_openGLESBlacklisted); } bool doPreferHDR() const { #ifdef HAVE_HDR return m_preferredColorSpace == KisSurfaceColorSpace::bt2020PQColorSpace || m_preferredColorSpace == KisSurfaceColorSpace::scRGBColorSpace; #else return false; #endif } bool isPreferredColorSpace(const KisSurfaceColorSpace cs) const { return KisOpenGLModeProber::fuzzyCompareColorSpaces(m_preferredColorSpace, cs); return false; } private: KisSurfaceColorSpace m_preferredColorSpace = KisSurfaceColorSpace::DefaultColorSpace; KisOpenGL::OpenGLRenderer m_preferredRendererByQt = KisOpenGL::RendererDesktopGL; KisOpenGL::OpenGLRenderer m_preferredRendererByUser = KisOpenGL::RendererAuto; KisOpenGL::OpenGLRenderer m_preferredRendererByHDR = KisOpenGL::RendererAuto; bool m_openGLBlacklisted = false; bool m_openGLESBlacklisted = false; }; struct DetectionDebug : public QDebug { DetectionDebug(QString *string) : QDebug(string), m_string(string), m_originalSize(string->size()) {} ~DetectionDebug() { dbgOpenGL << m_string->right(m_string->size() - m_originalSize); *this << endl; } QString *m_string; int m_originalSize; }; } #define dbgDetection() DetectionDebug(&g_surfaceFormatDetectionLog) KisOpenGL::RendererConfig KisOpenGL::selectSurfaceConfig(KisOpenGL::OpenGLRenderer preferredRenderer, KisConfig::RootSurfaceFormat preferredRootSurfaceFormat, bool enableDebug) { QVector warningMessages; using Info = boost::optional; QHash renderersToTest; renderersToTest.insert(RendererDesktopGL, Info()); renderersToTest.insert(RendererOpenGLES, Info()); #ifdef Q_OS_WIN renderersToTest.insert(RendererSoftware, Info()); #endif #ifdef HAVE_HDR QVector formatSymbols({KisConfig::BT709_G22, KisConfig::BT709_G10, KisConfig::BT2020_PQ}); #else QVector formatSymbols({KisConfig::BT709_G22}); #endif KisOpenGL::RendererConfig defaultConfig = generateSurfaceConfig(KisOpenGL::RendererAuto, KisConfig::BT709_G22, false); Info info = KisOpenGLModeProber::instance()->probeFormat(defaultConfig); #ifdef Q_OS_WIN if (!info) { // try software rasterizer (WARP) defaultConfig = generateSurfaceConfig(KisOpenGL::RendererSoftware, KisConfig::BT709_G22, false); info = KisOpenGLModeProber::instance()->probeFormat(defaultConfig); if (!info) { renderersToTest.remove(RendererSoftware); } } #endif if (!info) return KisOpenGL::RendererConfig(); const OpenGLRenderer defaultRenderer = getRendererFromProbeResult(*info); OpenGLRenderers supportedRenderers = RendererNone; FormatPositionLess compareOp; compareOp.setPreferredRendererByQt(defaultRenderer); #ifdef HAVE_HDR compareOp.setPreferredColorSpace( preferredRootSurfaceFormat == KisConfig::BT709_G22 ? KisSurfaceColorSpace::sRGBColorSpace : preferredRootSurfaceFormat == KisConfig::BT709_G10 ? KisSurfaceColorSpace::scRGBColorSpace : KisSurfaceColorSpace::bt2020PQColorSpace); #else Q_UNUSED(preferredRootSurfaceFormat); compareOp.setPreferredColorSpace(KisSurfaceColorSpace::sRGBColorSpace); #endif #ifdef Q_OS_WIN compareOp.setPreferredRendererByHDR(KisOpenGL::RendererOpenGLES); #endif compareOp.setPreferredRendererByUser(preferredRenderer); compareOp.setOpenGLESBlacklisted(false); // We cannot blacklist ES drivers atm renderersToTest[defaultRenderer] = info; for (auto it = renderersToTest.begin(); it != renderersToTest.end(); ++it) { Info info = it.value(); if (!info) { info = KisOpenGLModeProber::instance()-> probeFormat(generateSurfaceConfig(it.key(), KisConfig::BT709_G22, false)); *it = info; } compareOp.setOpenGLBlacklisted( !info || isOpenGLRendererBlacklisted(info->rendererString(), info->driverVersionString(), &warningMessages)); if (info && info->isSupportedVersion()) { supportedRenderers |= it.key(); } } OpenGLRenderer preferredByQt = defaultRenderer; if (preferredByQt == RendererDesktopGL && supportedRenderers & RendererDesktopGL && compareOp.isOpenGLBlacklisted()) { preferredByQt = RendererOpenGLES; } else if (preferredByQt == RendererOpenGLES && supportedRenderers & RendererOpenGLES && compareOp.isOpenGLESBlacklisted()) { preferredByQt = RendererDesktopGL; } QVector preferredConfigs; for (auto it = renderersToTest.begin(); it != renderersToTest.end(); ++it) { // if default mode of the renderer doesn't work, then custom won't either if (!it.value()) continue; Q_FOREACH (const KisConfig::RootSurfaceFormat formatSymbol, formatSymbols) { preferredConfigs << generateSurfaceConfig(it.key(), formatSymbol, enableDebug); } } std::stable_sort(preferredConfigs.begin(), preferredConfigs.end(), compareOp); dbgDetection() << "Supported renderers:" << supportedRenderers; dbgDetection() << "Surface format preference list:"; Q_FOREACH (const KisOpenGL::RendererConfig &config, preferredConfigs) { dbgDetection() << "*" << config.format; dbgDetection() << " " << config.rendererId(); } KisOpenGL::RendererConfig resultConfig = defaultConfig; if (preferredRenderer != RendererNone) { Q_FOREACH (const KisOpenGL::RendererConfig &config, preferredConfigs) { #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) dbgDetection() <<"Probing format..." << config.format.colorSpace() << config.rendererId(); #else dbgDetection() <<"Probing format..." << config.rendererId(); #endif Info info = KisOpenGLModeProber::instance()->probeFormat(config); if (info && info->isSupportedVersion()) { #ifdef Q_OS_WIN // HACK: Block ANGLE with Direct3D9 // Direct3D9 does not give OpenGL ES 3.0 // Some versions of ANGLE returns OpenGL version 3.0 incorrectly if (info->isUsingAngle() && info->rendererString().contains("Direct3D9", Qt::CaseInsensitive)) { dbgDetection() << "Skipping Direct3D 9 Angle implementation, it shouldn't have happened."; continue; } #endif dbgDetection() << "Found format:" << config.format; dbgDetection() << " " << config.rendererId(); resultConfig = config; break; } } { const bool colorSpaceIsCorrect = #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) KisOpenGLModeProber::fuzzyCompareColorSpaces(compareOp.preferredColorSpace(), resultConfig.format.colorSpace()); #else true; #endif const bool rendererIsCorrect = compareOp.preferredRendererByUser() == KisOpenGL::RendererAuto || compareOp.preferredRendererByUser() == resultConfig.rendererId(); if (!rendererIsCorrect && colorSpaceIsCorrect) { warningMessages << ki18n("Preferred renderer doesn't support requested surface format. Another renderer has been selected."); } else if (!colorSpaceIsCorrect) { warningMessages << ki18n("Preferred output format is not supported by available renderers"); } } } else { resultConfig.format = QSurfaceFormat(); resultConfig.angleRenderer = AngleRendererDefault; } overrideSupportedRenderers(supportedRenderers, preferredByQt); overrideOpenGLWarningString(warningMessages); return resultConfig; } void KisOpenGL::setDefaultSurfaceConfig(const KisOpenGL::RendererConfig &config) { KIS_SAFE_ASSERT_RECOVER_NOOP(!g_sanityDefaultFormatIsSet); g_sanityDefaultFormatIsSet = true; QSurfaceFormat::setDefaultFormat(config.format); #ifdef Q_OS_WIN // Force ANGLE to use Direct3D11. D3D9 doesn't support OpenGL ES 3 and WARP // might get weird crashes atm. qputenv("QT_ANGLE_PLATFORM", KisOpenGLModeProber::angleRendererToString(config.angleRenderer).toLatin1()); #endif if (config.format.renderableType() == QSurfaceFormat::OpenGLES) { QCoreApplication::setAttribute(Qt::AA_UseOpenGLES, true); } else if (config.format.renderableType() == QSurfaceFormat::OpenGL) { QCoreApplication::setAttribute(Qt::AA_UseDesktopOpenGL, true); } } bool KisOpenGL::hasOpenGL() { return openGLCheckResult->isSupportedVersion(); } diff --git a/libs/ui/opengl/kis_opengl_canvas2.cpp b/libs/ui/opengl/kis_opengl_canvas2.cpp index 2dcc4a0f7b..b9d090d11c 100644 --- a/libs/ui/opengl/kis_opengl_canvas2.cpp +++ b/libs/ui/opengl/kis_opengl_canvas2.cpp @@ -1,1055 +1,1061 @@ /* This file is part of the KDE project * Copyright (C) Boudewijn Rempt , (C) 2006-2013 * Copyright (C) 2015 Michael Abrahams * * 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. */ #define GL_GLEXT_PROTOTYPES #include "opengl/kis_opengl_canvas2.h" #include "opengl/kis_opengl_canvas2_p.h" #include "opengl/kis_opengl_shader_loader.h" #include "opengl/kis_opengl_canvas_debugger.h" #include "canvas/kis_canvas2.h" #include "canvas/kis_coordinates_converter.h" #include "canvas/kis_display_filter.h" #include "canvas/kis_display_color_converter.h" #include "kis_config.h" #include "kis_config_notifier.h" #include "kis_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include "KisOpenGLModeProber.h" #include #if !defined(Q_OS_MACOS) && !defined(HAS_ONLY_OPENGL_ES) #include #endif #define NEAR_VAL -1000.0 #define FAR_VAL 1000.0 #ifndef GL_CLAMP_TO_EDGE #define GL_CLAMP_TO_EDGE 0x812F #endif #define PROGRAM_VERTEX_ATTRIBUTE 0 #define PROGRAM_TEXCOORD_ATTRIBUTE 1 static bool OPENGL_SUCCESS = false; struct KisOpenGLCanvas2::Private { public: ~Private() { delete displayShader; delete checkerShader; delete solidColorShader; Sync::deleteSync(glSyncObject); } bool canvasInitialized{false}; KisOpenGLImageTexturesSP openGLImageTextures; KisOpenGLShaderLoader shaderLoader; KisShaderProgram *displayShader{0}; KisShaderProgram *checkerShader{0}; KisShaderProgram *solidColorShader{0}; bool displayShaderCompiledWithDisplayFilterSupport{false}; GLfloat checkSizeScale; bool scrollCheckers; QSharedPointer displayFilter; KisOpenGL::FilterMode filterMode; bool proofingConfigIsUpdated=false; GLsync glSyncObject{0}; bool wrapAroundMode{false}; // Stores a quad for drawing the canvas QOpenGLVertexArrayObject quadVAO; QOpenGLBuffer quadBuffers[2]; // Stores data for drawing tool outlines QOpenGLVertexArrayObject outlineVAO; QOpenGLBuffer lineBuffer; QVector3D vertices[6]; QVector2D texCoords[6]; #if !defined(Q_OS_MACOS) && !defined(HAS_ONLY_OPENGL_ES) QOpenGLFunctions_2_1 *glFn201; #endif qreal pixelGridDrawingThreshold; bool pixelGridEnabled; QColor gridColor; QColor cursorColor; bool lodSwitchInProgress = false; int xToColWithWrapCompensation(int x, const QRect &imageRect) { int firstImageColumn = openGLImageTextures->xToCol(imageRect.left()); int lastImageColumn = openGLImageTextures->xToCol(imageRect.right()); int colsPerImage = lastImageColumn - firstImageColumn + 1; int numWraps = floor(qreal(x) / imageRect.width()); int remainder = x - imageRect.width() * numWraps; return colsPerImage * numWraps + openGLImageTextures->xToCol(remainder); } int yToRowWithWrapCompensation(int y, const QRect &imageRect) { int firstImageRow = openGLImageTextures->yToRow(imageRect.top()); int lastImageRow = openGLImageTextures->yToRow(imageRect.bottom()); int rowsPerImage = lastImageRow - firstImageRow + 1; int numWraps = floor(qreal(y) / imageRect.height()); int remainder = y - imageRect.height() * numWraps; return rowsPerImage * numWraps + openGLImageTextures->yToRow(remainder); } }; KisOpenGLCanvas2::KisOpenGLCanvas2(KisCanvas2 *canvas, KisCoordinatesConverter *coordinatesConverter, QWidget *parent, KisImageWSP image, KisDisplayColorConverter *colorConverter) : QOpenGLWidget(parent) , KisCanvasWidgetBase(canvas, coordinatesConverter) , d(new Private()) { KisConfig cfg(false); cfg.setCanvasState("OPENGL_STARTED"); d->openGLImageTextures = KisOpenGLImageTextures::getImageTextures(image, colorConverter->openGLCanvasSurfaceProfile(), colorConverter->renderingIntent(), colorConverter->conversionFlags()); connect(d->openGLImageTextures.data(), SIGNAL(sigShowFloatingMessage(QString, int, bool)), SLOT(slotShowFloatingMessage(QString, int, bool))); setAcceptDrops(true); setAutoFillBackground(false); setFocusPolicy(Qt::StrongFocus); setAttribute(Qt::WA_NoSystemBackground, true); #ifdef Q_OS_MACOS setAttribute(Qt::WA_AcceptTouchEvents, false); #else setAttribute(Qt::WA_AcceptTouchEvents, true); #endif setAttribute(Qt::WA_InputMethodEnabled, false); setAttribute(Qt::WA_DontCreateNativeAncestors, true); #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) // we should make sure the texture doesn't have alpha channel, // otherwise blending will not work correctly. if (KisOpenGLModeProber::instance()->useHDRMode()) { setTextureFormat(GL_RGBA16F); } else { /** * When in pure OpenGL mode, the canvas surface will have alpha * channel. Therefore, if our canvas blending algorithm produces * semi-transparent pixels (and it does), then Krita window itself * will become transparent. Which is not good. * * In Angle mode, GL_RGB8 is not available (and the transparence effect * doesn't exist at all). */ if (!KisOpenGL::hasOpenGLES()) { setTextureFormat(GL_RGB8); } } #endif setDisplayFilterImpl(colorConverter->displayFilter(), true); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); connect(KisConfigNotifier::instance(), SIGNAL(pixelGridModeChanged()), SLOT(slotPixelGridModeChanged())); slotConfigChanged(); slotPixelGridModeChanged(); cfg.writeEntry("canvasState", "OPENGL_SUCCESS"); } KisOpenGLCanvas2::~KisOpenGLCanvas2() { delete d; } void KisOpenGLCanvas2::setDisplayFilter(QSharedPointer displayFilter) { setDisplayFilterImpl(displayFilter, false); } void KisOpenGLCanvas2::setDisplayFilterImpl(QSharedPointer displayFilter, bool initializing) { bool needsInternalColorManagement = !displayFilter || displayFilter->useInternalColorManagement(); bool needsFullRefresh = d->openGLImageTextures->setInternalColorManagementActive(needsInternalColorManagement); d->displayFilter = displayFilter; if (!initializing && needsFullRefresh) { canvas()->startUpdateInPatches(canvas()->image()->bounds()); } else if (!initializing) { canvas()->updateCanvas(); } } void KisOpenGLCanvas2::notifyImageColorSpaceChanged(const KoColorSpace *cs) { // FIXME: on color space change the data is refetched multiple // times by different actors! if (d->openGLImageTextures->setImageColorSpace(cs)) { canvas()->startUpdateInPatches(canvas()->image()->bounds()); } } void KisOpenGLCanvas2::setWrapAroundViewingMode(bool value) { d->wrapAroundMode = value; update(); } inline void rectToVertices(QVector3D* vertices, const QRectF &rc) { vertices[0] = QVector3D(rc.left(), rc.bottom(), 0.f); vertices[1] = QVector3D(rc.left(), rc.top(), 0.f); vertices[2] = QVector3D(rc.right(), rc.bottom(), 0.f); vertices[3] = QVector3D(rc.left(), rc.top(), 0.f); vertices[4] = QVector3D(rc.right(), rc.top(), 0.f); vertices[5] = QVector3D(rc.right(), rc.bottom(), 0.f); } inline void rectToTexCoords(QVector2D* texCoords, const QRectF &rc) { texCoords[0] = QVector2D(rc.left(), rc.bottom()); texCoords[1] = QVector2D(rc.left(), rc.top()); texCoords[2] = QVector2D(rc.right(), rc.bottom()); texCoords[3] = QVector2D(rc.left(), rc.top()); texCoords[4] = QVector2D(rc.right(), rc.top()); texCoords[5] = QVector2D(rc.right(), rc.bottom()); } void KisOpenGLCanvas2::initializeGL() { KisOpenGL::initializeContext(context()); initializeOpenGLFunctions(); #if !defined(Q_OS_MACOS) && !defined(HAS_ONLY_OPENGL_ES) if (!KisOpenGL::hasOpenGLES()) { d->glFn201 = context()->versionFunctions(); if (!d->glFn201) { warnUI << "Cannot obtain QOpenGLFunctions_2_1, glLogicOp cannot be used"; } } else { d->glFn201 = nullptr; } #endif KisConfig cfg(true); d->openGLImageTextures->setProofingConfig(canvas()->proofingConfiguration()); d->openGLImageTextures->initGL(context()->functions()); d->openGLImageTextures->generateCheckerTexture(createCheckersImage(cfg.checkSize())); initializeShaders(); // If we support OpenGL 3.2, then prepare our VAOs and VBOs for drawing if (KisOpenGL::hasOpenGL3()) { d->quadVAO.create(); d->quadVAO.bind(); glEnableVertexAttribArray(PROGRAM_VERTEX_ATTRIBUTE); glEnableVertexAttribArray(PROGRAM_TEXCOORD_ATTRIBUTE); // Create the vertex buffer object, it has 6 vertices with 3 components d->quadBuffers[0].create(); d->quadBuffers[0].setUsagePattern(QOpenGLBuffer::StaticDraw); d->quadBuffers[0].bind(); d->quadBuffers[0].allocate(d->vertices, 6 * 3 * sizeof(float)); glVertexAttribPointer(PROGRAM_VERTEX_ATTRIBUTE, 3, GL_FLOAT, GL_FALSE, 0, 0); // Create the texture buffer object, it has 6 texture coordinates with 2 components d->quadBuffers[1].create(); d->quadBuffers[1].setUsagePattern(QOpenGLBuffer::StaticDraw); d->quadBuffers[1].bind(); d->quadBuffers[1].allocate(d->texCoords, 6 * 2 * sizeof(float)); glVertexAttribPointer(PROGRAM_TEXCOORD_ATTRIBUTE, 2, GL_FLOAT, GL_FALSE, 0, 0); // Create the outline buffer, this buffer will store the outlines of // tools and will frequently change data d->outlineVAO.create(); d->outlineVAO.bind(); glEnableVertexAttribArray(PROGRAM_VERTEX_ATTRIBUTE); // The outline buffer has a StreamDraw usage pattern, because it changes constantly d->lineBuffer.create(); d->lineBuffer.setUsagePattern(QOpenGLBuffer::StreamDraw); d->lineBuffer.bind(); glVertexAttribPointer(PROGRAM_VERTEX_ATTRIBUTE, 3, GL_FLOAT, GL_FALSE, 0, 0); } Sync::init(context()); d->canvasInitialized = true; } /** * Loads all shaders and reports compilation problems */ void KisOpenGLCanvas2::initializeShaders() { KIS_SAFE_ASSERT_RECOVER_RETURN(!d->canvasInitialized); delete d->checkerShader; delete d->solidColorShader; d->checkerShader = 0; d->solidColorShader = 0; try { d->checkerShader = d->shaderLoader.loadCheckerShader(); d->solidColorShader = d->shaderLoader.loadSolidColorShader(); } catch (const ShaderLoaderException &e) { reportFailedShaderCompilation(e.what()); } initializeDisplayShader(); } void KisOpenGLCanvas2::initializeDisplayShader() { KIS_SAFE_ASSERT_RECOVER_RETURN(!d->canvasInitialized); bool useHiQualityFiltering = d->filterMode == KisOpenGL::HighQualityFiltering; delete d->displayShader; d->displayShader = 0; try { d->displayShader = d->shaderLoader.loadDisplayShader(d->displayFilter, useHiQualityFiltering); d->displayShaderCompiledWithDisplayFilterSupport = d->displayFilter; } catch (const ShaderLoaderException &e) { reportFailedShaderCompilation(e.what()); } } /** * Displays a message box telling the user that * shader compilation failed and turns off OpenGL. */ void KisOpenGLCanvas2::reportFailedShaderCompilation(const QString &context) { KisConfig cfg(false); qDebug() << "Shader Compilation Failure: " << context; QMessageBox::critical(this, i18nc("@title:window", "Krita"), i18n("Krita could not initialize the OpenGL canvas:\n\n%1\n\n Krita will disable OpenGL and close now.", context), QMessageBox::Close); cfg.disableOpenGL(); cfg.setCanvasState("OPENGL_FAILED"); } void KisOpenGLCanvas2::resizeGL(int /*width*/, int /*height*/) { // The given size is the widget size but here we actually want to give // KisCoordinatesConverter the viewport size aligned to device pixels. coordinatesConverter()->setCanvasWidgetSize(widgetSizeAlignedToDevicePixel()); paintGL(); } void KisOpenGLCanvas2::paintGL() { if (!OPENGL_SUCCESS) { KisConfig cfg(false); cfg.writeEntry("canvasState", "OPENGL_PAINT_STARTED"); } KisOpenglCanvasDebugger::instance()->nofityPaintRequested(); renderCanvasGL(); if (d->glSyncObject) { Sync::deleteSync(d->glSyncObject); } d->glSyncObject = Sync::getSync(); QPainter gc(this); renderDecorations(&gc); gc.end(); if (!OPENGL_SUCCESS) { KisConfig cfg(false); cfg.writeEntry("canvasState", "OPENGL_SUCCESS"); OPENGL_SUCCESS = true; } } void KisOpenGLCanvas2::paintToolOutline(const QPainterPath &path) { if (!d->solidColorShader->bind()) { return; } QSizeF widgetSize = widgetSizeAlignedToDevicePixel(); // setup the mvp transformation QMatrix4x4 projectionMatrix; projectionMatrix.setToIdentity(); // FIXME: It may be better to have the projection in device pixel, but // this requires introducing a new coordinate system. projectionMatrix.ortho(0, widgetSize.width(), widgetSize.height(), 0, NEAR_VAL, FAR_VAL); // Set view/projection matrices QMatrix4x4 modelMatrix(coordinatesConverter()->flakeToWidgetTransform()); modelMatrix.optimize(); modelMatrix = projectionMatrix * modelMatrix; d->solidColorShader->setUniformValue(d->solidColorShader->location(Uniform::ModelViewProjection), modelMatrix); if (!KisOpenGL::hasOpenGLES()) { #ifndef HAS_ONLY_OPENGL_ES glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); glEnable(GL_COLOR_LOGIC_OP); #ifndef Q_OS_MACOS if (d->glFn201) { d->glFn201->glLogicOp(GL_XOR); } #else glLogicOp(GL_XOR); #endif // Q_OS_OSX #else // HAS_ONLY_OPENGL_ES KIS_ASSERT_X(false, "KisOpenGLCanvas2::paintToolOutline", "Unexpected KisOpenGL::hasOpenGLES returned false"); #endif // HAS_ONLY_OPENGL_ES } else { glEnable(GL_BLEND); glBlendFuncSeparate(GL_ONE_MINUS_DST_COLOR, GL_ZERO, GL_ONE, GL_ONE); } d->solidColorShader->setUniformValue( d->solidColorShader->location(Uniform::FragmentColor), QVector4D(d->cursorColor.redF(), d->cursorColor.greenF(), d->cursorColor.blueF(), 1.0f)); // Paint the tool outline if (KisOpenGL::hasOpenGL3()) { d->outlineVAO.bind(); d->lineBuffer.bind(); } // Convert every disjointed subpath to a polygon and draw that polygon QList subPathPolygons = path.toSubpathPolygons(); for (int i = 0; i < subPathPolygons.size(); i++) { const QPolygonF& polygon = subPathPolygons.at(i); QVector vertices; vertices.resize(polygon.count()); for (int j = 0; j < polygon.count(); j++) { QPointF p = polygon.at(j); vertices[j].setX(p.x()); vertices[j].setY(p.y()); } if (KisOpenGL::hasOpenGL3()) { d->lineBuffer.allocate(vertices.constData(), 3 * vertices.size() * sizeof(float)); } else { d->solidColorShader->enableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE); d->solidColorShader->setAttributeArray(PROGRAM_VERTEX_ATTRIBUTE, vertices.constData()); } glDrawArrays(GL_LINE_STRIP, 0, vertices.size()); } if (KisOpenGL::hasOpenGL3()) { d->lineBuffer.release(); d->outlineVAO.release(); } if (!KisOpenGL::hasOpenGLES()) { #ifndef HAS_ONLY_OPENGL_ES glDisable(GL_COLOR_LOGIC_OP); #else KIS_ASSERT_X(false, "KisOpenGLCanvas2::paintToolOutline", "Unexpected KisOpenGL::hasOpenGLES returned false"); #endif } else { glDisable(GL_BLEND); } d->solidColorShader->release(); } bool KisOpenGLCanvas2::isBusy() const { const bool isBusyStatus = Sync::syncStatus(d->glSyncObject) == Sync::Unsignaled; KisOpenglCanvasDebugger::instance()->nofitySyncStatus(isBusyStatus); return isBusyStatus; } void KisOpenGLCanvas2::setLodResetInProgress(bool value) { d->lodSwitchInProgress = value; } void KisOpenGLCanvas2::drawCheckers() { if (!d->checkerShader) { return; } KisCoordinatesConverter *converter = coordinatesConverter(); QTransform textureTransform; QTransform modelTransform; QRectF textureRect; QRectF modelRect; QSizeF widgetSize = widgetSizeAlignedToDevicePixel(); QRectF viewportRect = !d->wrapAroundMode ? converter->imageRectInViewportPixels() : converter->widgetToViewport(QRectF(0, 0, widgetSize.width(), widgetSize.height())); if (!canvas()->renderingLimit().isEmpty()) { const QRect vrect = converter->imageToViewport(canvas()->renderingLimit()).toAlignedRect(); viewportRect &= vrect; } converter->getOpenGLCheckersInfo(viewportRect, &textureTransform, &modelTransform, &textureRect, &modelRect, d->scrollCheckers); textureTransform *= QTransform::fromScale(d->checkSizeScale / KisOpenGLImageTextures::BACKGROUND_TEXTURE_SIZE, d->checkSizeScale / KisOpenGLImageTextures::BACKGROUND_TEXTURE_SIZE); if (!d->checkerShader->bind()) { qWarning() << "Could not bind checker shader"; return; } QMatrix4x4 projectionMatrix; projectionMatrix.setToIdentity(); // FIXME: It may be better to have the projection in device pixel, but // this requires introducing a new coordinate system. projectionMatrix.ortho(0, widgetSize.width(), widgetSize.height(), 0, NEAR_VAL, FAR_VAL); // Set view/projection matrices QMatrix4x4 modelMatrix(modelTransform); modelMatrix.optimize(); modelMatrix = projectionMatrix * modelMatrix; d->checkerShader->setUniformValue(d->checkerShader->location(Uniform::ModelViewProjection), modelMatrix); QMatrix4x4 textureMatrix(textureTransform); d->checkerShader->setUniformValue(d->checkerShader->location(Uniform::TextureMatrix), textureMatrix); //Setup the geometry for rendering if (KisOpenGL::hasOpenGL3()) { rectToVertices(d->vertices, modelRect); d->quadBuffers[0].bind(); d->quadBuffers[0].write(0, d->vertices, 3 * 6 * sizeof(float)); rectToTexCoords(d->texCoords, textureRect); d->quadBuffers[1].bind(); d->quadBuffers[1].write(0, d->texCoords, 2 * 6 * sizeof(float)); } else { rectToVertices(d->vertices, modelRect); d->checkerShader->enableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE); d->checkerShader->setAttributeArray(PROGRAM_VERTEX_ATTRIBUTE, d->vertices); rectToTexCoords(d->texCoords, textureRect); d->checkerShader->enableAttributeArray(PROGRAM_TEXCOORD_ATTRIBUTE); d->checkerShader->setAttributeArray(PROGRAM_TEXCOORD_ATTRIBUTE, d->texCoords); } // render checkers glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, d->openGLImageTextures->checkerTexture()); glDrawArrays(GL_TRIANGLES, 0, 6); glBindTexture(GL_TEXTURE_2D, 0); d->checkerShader->release(); glBindBuffer(GL_ARRAY_BUFFER, 0); } void KisOpenGLCanvas2::drawGrid() { if (!d->solidColorShader->bind()) { return; } QSizeF widgetSize = widgetSizeAlignedToDevicePixel(); QMatrix4x4 projectionMatrix; projectionMatrix.setToIdentity(); // FIXME: It may be better to have the projection in device pixel, but // this requires introducing a new coordinate system. projectionMatrix.ortho(0, widgetSize.width(), widgetSize.height(), 0, NEAR_VAL, FAR_VAL); // Set view/projection matrices QMatrix4x4 modelMatrix(coordinatesConverter()->imageToWidgetTransform()); modelMatrix.optimize(); modelMatrix = projectionMatrix * modelMatrix; d->solidColorShader->setUniformValue(d->solidColorShader->location(Uniform::ModelViewProjection), modelMatrix); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); d->solidColorShader->setUniformValue( d->solidColorShader->location(Uniform::FragmentColor), QVector4D(d->gridColor.redF(), d->gridColor.greenF(), d->gridColor.blueF(), 0.5f)); if (KisOpenGL::hasOpenGL3()) { d->outlineVAO.bind(); d->lineBuffer.bind(); } QRectF widgetRect(0,0, widgetSize.width(), widgetSize.height()); QRectF widgetRectInImagePixels = coordinatesConverter()->documentToImage(coordinatesConverter()->widgetToDocument(widgetRect)); QRect wr = widgetRectInImagePixels.toAlignedRect(); if (!d->wrapAroundMode) { wr &= d->openGLImageTextures->storedImageBounds(); } QPoint topLeftCorner = wr.topLeft(); QPoint bottomRightCorner = wr.bottomRight() + QPoint(1, 1); QVector grid; for (int i = topLeftCorner.x(); i <= bottomRightCorner.x(); ++i) { grid.append(QVector3D(i, topLeftCorner.y(), 0)); grid.append(QVector3D(i, bottomRightCorner.y(), 0)); } for (int i = topLeftCorner.y(); i <= bottomRightCorner.y(); ++i) { grid.append(QVector3D(topLeftCorner.x(), i, 0)); grid.append(QVector3D(bottomRightCorner.x(), i, 0)); } if (KisOpenGL::hasOpenGL3()) { d->lineBuffer.allocate(grid.constData(), 3 * grid.size() * sizeof(float)); } else { d->solidColorShader->enableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE); d->solidColorShader->setAttributeArray(PROGRAM_VERTEX_ATTRIBUTE, grid.constData()); } glDrawArrays(GL_LINES, 0, grid.size()); if (KisOpenGL::hasOpenGL3()) { d->lineBuffer.release(); d->outlineVAO.release(); } d->solidColorShader->release(); glDisable(GL_BLEND); } void KisOpenGLCanvas2::drawImage() { if (!d->displayShader) { return; } glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); KisCoordinatesConverter *converter = coordinatesConverter(); d->displayShader->bind(); QSizeF widgetSize = widgetSizeAlignedToDevicePixel(); QMatrix4x4 projectionMatrix; projectionMatrix.setToIdentity(); // FIXME: It may be better to have the projection in device pixel, but // this requires introducing a new coordinate system. projectionMatrix.ortho(0, widgetSize.width(), widgetSize.height(), 0, NEAR_VAL, FAR_VAL); // Set view/projection matrices QMatrix4x4 modelMatrix(converter->imageToWidgetTransform()); modelMatrix.optimize(); modelMatrix = projectionMatrix * modelMatrix; d->displayShader->setUniformValue(d->displayShader->location(Uniform::ModelViewProjection), modelMatrix); QMatrix4x4 textureMatrix; textureMatrix.setToIdentity(); d->displayShader->setUniformValue(d->displayShader->location(Uniform::TextureMatrix), textureMatrix); QRectF widgetRect(0,0, widgetSize.width(), widgetSize.height()); QRectF widgetRectInImagePixels = converter->documentToImage(converter->widgetToDocument(widgetRect)); const QRect renderingLimit = canvas()->renderingLimit(); if (!renderingLimit.isEmpty()) { widgetRectInImagePixels &= renderingLimit; } qreal scaleX, scaleY; converter->imagePhysicalScale(&scaleX, &scaleY); d->displayShader->setUniformValue(d->displayShader->location(Uniform::ViewportScale), (GLfloat) scaleX); d->displayShader->setUniformValue(d->displayShader->location(Uniform::TexelSize), (GLfloat) d->openGLImageTextures->texelSize()); QRect ir = d->openGLImageTextures->storedImageBounds(); QRect wr = widgetRectInImagePixels.toAlignedRect(); if (!d->wrapAroundMode) { // if we don't want to paint wrapping images, just limit the // processing area, and the code will handle all the rest wr &= ir; } int firstColumn = d->xToColWithWrapCompensation(wr.left(), ir); int lastColumn = d->xToColWithWrapCompensation(wr.right(), ir); int firstRow = d->yToRowWithWrapCompensation(wr.top(), ir); int lastRow = d->yToRowWithWrapCompensation(wr.bottom(), ir); int minColumn = d->openGLImageTextures->xToCol(ir.left()); int maxColumn = d->openGLImageTextures->xToCol(ir.right()); int minRow = d->openGLImageTextures->yToRow(ir.top()); int maxRow = d->openGLImageTextures->yToRow(ir.bottom()); int imageColumns = maxColumn - minColumn + 1; int imageRows = maxRow - minRow + 1; for (int col = firstColumn; col <= lastColumn; col++) { for (int row = firstRow; row <= lastRow; row++) { int effectiveCol = col; int effectiveRow = row; QPointF tileWrappingTranslation; if (effectiveCol > maxColumn || effectiveCol < minColumn) { int translationStep = floor(qreal(col) / imageColumns); int originCol = translationStep * imageColumns; effectiveCol = col - originCol; tileWrappingTranslation.rx() = translationStep * ir.width(); } if (effectiveRow > maxRow || effectiveRow < minRow) { int translationStep = floor(qreal(row) / imageRows); int originRow = translationStep * imageRows; effectiveRow = row - originRow; tileWrappingTranslation.ry() = translationStep * ir.height(); } KisTextureTile *tile = d->openGLImageTextures->getTextureTileCR(effectiveCol, effectiveRow); if (!tile) { warnUI << "OpenGL: Trying to paint texture tile but it has not been created yet."; continue; } /* * We create a float rect here to workaround Qt's * "history reasons" in calculation of right() * and bottom() coordinates of integer rects. */ QRectF textureRect; QRectF modelRect; if (renderingLimit.isEmpty()) { textureRect = tile->tileRectInTexturePixels(); modelRect = tile->tileRectInImagePixels().translated(tileWrappingTranslation.x(), tileWrappingTranslation.y()); } else { const QRect limitedTileRect = tile->tileRectInImagePixels() & renderingLimit; textureRect = tile->imageRectInTexturePixels(limitedTileRect); modelRect = limitedTileRect.translated(tileWrappingTranslation.x(), tileWrappingTranslation.y()); } //Setup the geometry for rendering if (KisOpenGL::hasOpenGL3()) { rectToVertices(d->vertices, modelRect); d->quadBuffers[0].bind(); d->quadBuffers[0].write(0, d->vertices, 3 * 6 * sizeof(float)); rectToTexCoords(d->texCoords, textureRect); d->quadBuffers[1].bind(); d->quadBuffers[1].write(0, d->texCoords, 2 * 6 * sizeof(float)); } else { rectToVertices(d->vertices, modelRect); d->displayShader->enableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE); d->displayShader->setAttributeArray(PROGRAM_VERTEX_ATTRIBUTE, d->vertices); rectToTexCoords(d->texCoords, textureRect); d->displayShader->enableAttributeArray(PROGRAM_TEXCOORD_ATTRIBUTE); d->displayShader->setAttributeArray(PROGRAM_TEXCOORD_ATTRIBUTE, d->texCoords); } if (d->displayFilter) { glActiveTexture(GL_TEXTURE0 + 1); glBindTexture(GL_TEXTURE_3D, d->displayFilter->lutTexture()); d->displayShader->setUniformValue(d->displayShader->location(Uniform::Texture1), 1); } glActiveTexture(GL_TEXTURE0); const int currentLodPlane = tile->bindToActiveTexture(d->lodSwitchInProgress); if (d->displayShader->location(Uniform::FixedLodLevel) >= 0) { d->displayShader->setUniformValue(d->displayShader->location(Uniform::FixedLodLevel), (GLfloat) currentLodPlane); } if (currentLodPlane > 0) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST); } else if (SCALE_MORE_OR_EQUAL_TO(scaleX, scaleY, 2.0)) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); } else { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); switch(d->filterMode) { case KisOpenGL::NearestFilterMode: glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); break; case KisOpenGL::BilinearFilterMode: glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); break; case KisOpenGL::TrilinearFilterMode: glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); break; case KisOpenGL::HighQualityFiltering: if (SCALE_LESS_THAN(scaleX, scaleY, 0.5)) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST); } else { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); } break; } } glDrawArrays(GL_TRIANGLES, 0, 6); } } glBindTexture(GL_TEXTURE_2D, 0); d->displayShader->release(); glBindBuffer(GL_ARRAY_BUFFER, 0); glDisable(GL_BLEND); } QSize KisOpenGLCanvas2::viewportDevicePixelSize() const { // This is how QOpenGLCanvas sets the FBO and the viewport size. If // devicePixelRatioF() is non-integral, the result is truncated. int viewportWidth = static_cast(width() * devicePixelRatioF()); int viewportHeight = static_cast(height() * devicePixelRatioF()); return QSize(viewportWidth, viewportHeight); } QSizeF KisOpenGLCanvas2::widgetSizeAlignedToDevicePixel() const { QSize viewportSize = viewportDevicePixelSize(); qreal scaledWidth = viewportSize.width() / devicePixelRatioF(); qreal scaledHeight = viewportSize.height() / devicePixelRatioF(); return QSizeF(scaledWidth, scaledHeight); } void KisOpenGLCanvas2::slotConfigChanged() { KisConfig cfg(true); d->checkSizeScale = KisOpenGLImageTextures::BACKGROUND_TEXTURE_CHECK_SIZE / static_cast(cfg.checkSize()); d->scrollCheckers = cfg.scrollCheckers(); d->openGLImageTextures->generateCheckerTexture(createCheckersImage(cfg.checkSize())); d->openGLImageTextures->updateConfig(cfg.useOpenGLTextureBuffer(), cfg.numMipmapLevels()); d->filterMode = (KisOpenGL::FilterMode) cfg.openGLFilteringMode(); d->cursorColor = cfg.getCursorMainColor(); notifyConfigChanged(); } void KisOpenGLCanvas2::slotPixelGridModeChanged() { KisConfig cfg(true); d->pixelGridDrawingThreshold = cfg.getPixelGridDrawingThreshold(); d->pixelGridEnabled = cfg.pixelGridEnabled(); d->gridColor = cfg.getPixelGridColor(); update(); } void KisOpenGLCanvas2::slotShowFloatingMessage(const QString &message, int timeout, bool priority) { canvas()->imageView()->showFloatingMessage(message, QIcon(), timeout, priority ? KisFloatingMessage::High : KisFloatingMessage::Medium); } QVariant KisOpenGLCanvas2::inputMethodQuery(Qt::InputMethodQuery query) const { return processInputMethodQuery(query); } void KisOpenGLCanvas2::inputMethodEvent(QInputMethodEvent *event) { processInputMethodEvent(event); } void KisOpenGLCanvas2::renderCanvasGL() { { // Draw the border (that is, clear the whole widget to the border color) QColor widgetBackgroundColor = borderColor(); - KoColor convertedBackgroudColor = - canvas()->displayColorConverter()->applyDisplayFiltering( - KoColor(widgetBackgroundColor, KoColorSpaceRegistry::instance()->rgb8()), - Float32BitsColorDepthID); - const float *pixel = reinterpret_cast(convertedBackgroudColor.data()); - glClearColor(pixel[0], pixel[1], pixel[2], 1.0); + + const KoColorSpace *finalColorSpace = + KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), + d->openGLImageTextures->updateInfoBuilder().destinationColorSpace()->colorDepthId().id(), + d->openGLImageTextures->monitorProfile()); + + KoColor convertedBackgroudColor = KoColor(widgetBackgroundColor, KoColorSpaceRegistry::instance()->rgb8()); + convertedBackgroudColor.convertTo(finalColorSpace); + + QVector channels = QVector(4); + convertedBackgroudColor.colorSpace()->normalisedChannelsValue(convertedBackgroudColor.data(), channels); + glClearColor(channels[0], channels[1], channels[2], 1.0); } glClear(GL_COLOR_BUFFER_BIT); if ((d->displayFilter && d->displayFilter->updateShader()) || (bool(d->displayFilter) != d->displayShaderCompiledWithDisplayFilterSupport)) { KIS_SAFE_ASSERT_RECOVER_NOOP(d->canvasInitialized); d->canvasInitialized = false; // TODO: check if actually needed? initializeDisplayShader(); d->canvasInitialized = true; } if (KisOpenGL::hasOpenGL3()) { d->quadVAO.bind(); } drawCheckers(); drawImage(); if ((coordinatesConverter()->effectiveZoom() > d->pixelGridDrawingThreshold - 0.00001) && d->pixelGridEnabled) { drawGrid(); } if (KisOpenGL::hasOpenGL3()) { d->quadVAO.release(); } } void KisOpenGLCanvas2::renderDecorations(QPainter *painter) { QRect boundingRect = coordinatesConverter()->imageRectInWidgetPixels().toAlignedRect(); drawDecorations(*painter, boundingRect); } void KisOpenGLCanvas2::setDisplayColorConverter(KisDisplayColorConverter *colorConverter) { d->openGLImageTextures->setMonitorProfile(colorConverter->openGLCanvasSurfaceProfile(), colorConverter->renderingIntent(), colorConverter->conversionFlags()); } void KisOpenGLCanvas2::channelSelectionChanged(const QBitArray &channelFlags) { d->openGLImageTextures->setChannelFlags(channelFlags); } void KisOpenGLCanvas2::finishResizingImage(qint32 w, qint32 h) { if (d->canvasInitialized) { d->openGLImageTextures->slotImageSizeChanged(w, h); } } KisUpdateInfoSP KisOpenGLCanvas2::startUpdateCanvasProjection(const QRect & rc, const QBitArray &channelFlags) { d->openGLImageTextures->setChannelFlags(channelFlags); if (canvas()->proofingConfigUpdated()) { d->openGLImageTextures->setProofingConfig(canvas()->proofingConfiguration()); canvas()->setProofingConfigUpdated(false); } return d->openGLImageTextures->updateCache(rc, d->openGLImageTextures->image()); } QRect KisOpenGLCanvas2::updateCanvasProjection(KisUpdateInfoSP info) { // See KisQPainterCanvas::updateCanvasProjection for more info bool isOpenGLUpdateInfo = dynamic_cast(info.data()); if (isOpenGLUpdateInfo) { d->openGLImageTextures->recalculateCache(info, d->lodSwitchInProgress); } return QRect(); // FIXME: Implement dirty rect for OpenGL } QVector KisOpenGLCanvas2::updateCanvasProjection(const QVector &infoObjects) { #ifdef Q_OS_MACOS /** - * On OSX openGL defferent (shared) contexts have different execution queues. + * On OSX openGL different (shared) contexts have different execution queues. * It means that the textures uploading and their painting can be easily reordered. * To overcome the issue, we should ensure that the textures are uploaded in the * same openGL context as the painting is done. */ QOpenGLContext *oldContext = QOpenGLContext::currentContext(); QSurface *oldSurface = oldContext ? oldContext->surface() : 0; this->makeCurrent(); #endif QVector result = KisCanvasWidgetBase::updateCanvasProjection(infoObjects); #ifdef Q_OS_MACOS if (oldContext) { oldContext->makeCurrent(oldSurface); } else { this->doneCurrent(); } #endif return result; } bool KisOpenGLCanvas2::callFocusNextPrevChild(bool next) { return focusNextPrevChild(next); } KisOpenGLImageTexturesSP KisOpenGLCanvas2::openGLImageTextures() const { return d->openGLImageTextures; } diff --git a/libs/ui/opengl/kis_opengl_image_textures.cpp b/libs/ui/opengl/kis_opengl_image_textures.cpp index d4a68e78c2..528a19d41e 100644 --- a/libs/ui/opengl/kis_opengl_image_textures.cpp +++ b/libs/ui/opengl/kis_opengl_image_textures.cpp @@ -1,655 +1,670 @@ /* * Copyright (c) 2005-2007 Adrian Page * * 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 "opengl/kis_opengl_image_textures.h" #ifdef HAS_ONLY_OPENGL_ES #include #endif #ifndef HAS_ONLY_OPENGL_ES #include #endif #include #include #include #include #include #include #include #include "kis_image.h" #include "kis_config.h" #include "KisPart.h" #include "KisOpenGLModeProber.h" +#include "kis_fixed_paint_device.h" #ifdef HAVE_OPENEXR #include #endif #ifndef GL_CLAMP_TO_EDGE #define GL_CLAMP_TO_EDGE 0x812F #endif #ifndef GL_BGRA #define GL_BGRA 0x80E1 #endif // GL_EXT_texture_format_BGRA8888 #ifndef GL_BGRA_EXT #define GL_BGRA_EXT 0x80E1 #endif #ifndef GL_BGRA8_EXT #define GL_BGRA8_EXT 0x93A1 #endif KisOpenGLImageTextures::ImageTexturesMap KisOpenGLImageTextures::imageTexturesMap; KisOpenGLImageTextures::KisOpenGLImageTextures() : m_image(0) , m_monitorProfile(0) , m_internalColorManagementActive(true) , m_checkerTexture(0) , m_glFuncs(0) , m_useOcio(false) , m_initialized(false) { KisConfig cfg(true); m_renderingIntent = (KoColorConversionTransformation::Intent)cfg.monitorRenderIntent(); m_conversionFlags = KoColorConversionTransformation::HighQuality; if (cfg.useBlackPointCompensation()) m_conversionFlags |= KoColorConversionTransformation::BlackpointCompensation; if (!cfg.allowLCMSOptimization()) m_conversionFlags |= KoColorConversionTransformation::NoOptimization; m_useOcio = cfg.useOcio(); } KisOpenGLImageTextures::KisOpenGLImageTextures(KisImageWSP image, const KoColorProfile *monitorProfile, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) : m_image(image) , m_monitorProfile(monitorProfile) , m_renderingIntent(renderingIntent) , m_conversionFlags(conversionFlags) , m_internalColorManagementActive(true) , m_checkerTexture(0) , m_glFuncs(0) , m_useOcio(false) , m_initialized(false) { Q_ASSERT(renderingIntent < 4); } void KisOpenGLImageTextures::initGL(QOpenGLFunctions *f) { if (f) { m_glFuncs = f; } else { errUI << "Tried to create OpenGLImageTextures with uninitialized QOpenGLFunctions"; } getTextureSize(&m_texturesInfo); // we use local static object for creating pools shared among // different images static KisTextureTileInfoPoolRegistry s_poolRegistry; m_updateInfoBuilder.setTextureInfoPool(s_poolRegistry.getPool(m_texturesInfo.width, m_texturesInfo.height)); m_glFuncs->glGenTextures(1, &m_checkerTexture); recreateImageTextureTiles(); KisOpenGLUpdateInfoSP info = updateCache(m_image->bounds(), m_image); recalculateCache(info, false); } KisOpenGLImageTextures::~KisOpenGLImageTextures() { ImageTexturesMap::iterator it = imageTexturesMap.find(m_image); if (it != imageTexturesMap.end()) { KisOpenGLImageTextures *textures = it.value(); if (textures == this) { dbgUI << "Removing shared image context from map"; imageTexturesMap.erase(it); } } destroyImageTextureTiles(); m_glFuncs->glDeleteTextures(1, &m_checkerTexture); } KisImageSP KisOpenGLImageTextures::image() const { return m_image; } bool KisOpenGLImageTextures::imageCanShareTextures() { KisConfig cfg(true); if (cfg.useOcio()) return false; if (KisPart::instance()->mainwindowCount() == 1) return true; if (QGuiApplication::screens().count() == 1) return true; for (int i = 1; i < QGuiApplication::screens().count(); i++) { if (cfg.displayProfile(i) != cfg.displayProfile(i - 1)) { return false; } } return true; } KisOpenGLImageTexturesSP KisOpenGLImageTextures::getImageTextures(KisImageWSP image, const KoColorProfile *monitorProfile, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { // Disabled until we figure out why we're deleting the shared textures on closing the second view on a single image if (false && imageCanShareTextures()) { ImageTexturesMap::iterator it = imageTexturesMap.find(image); if (it != imageTexturesMap.end()) { KisOpenGLImageTexturesSP textures = it.value(); textures->setMonitorProfile(monitorProfile, renderingIntent, conversionFlags); return textures; } else { KisOpenGLImageTextures *imageTextures = new KisOpenGLImageTextures(image, monitorProfile, renderingIntent, conversionFlags); imageTexturesMap[image] = imageTextures; dbgUI << "Added shareable textures to map"; return imageTextures; } } else { return new KisOpenGLImageTextures(image, monitorProfile, renderingIntent, conversionFlags); } } void KisOpenGLImageTextures::recreateImageTextureTiles() { destroyImageTextureTiles(); updateTextureFormat(); const KoColorSpace *tilesDestinationColorSpace = m_updateInfoBuilder.destinationColorSpace(); if (!tilesDestinationColorSpace) { qDebug() << "No destination colorspace!!!!"; return; } m_storedImageBounds = m_image->bounds(); const int lastCol = xToCol(m_image->width()); const int lastRow = yToRow(m_image->height()); m_numCols = lastCol + 1; // Default color is transparent black const int pixelSize = tilesDestinationColorSpace->pixelSize(); QByteArray emptyTileData((m_texturesInfo.width) * (m_texturesInfo.height) * pixelSize, 0); KisConfig config(true); KisOpenGL::FilterMode mode = (KisOpenGL::FilterMode)config.openGLFilteringMode(); QOpenGLContext *ctx = QOpenGLContext::currentContext(); if (ctx) { QOpenGLFunctions *f = ctx->functions(); m_initialized = true; dbgUI << "OpenGL: creating texture tiles of size" << m_texturesInfo.height << "x" << m_texturesInfo.width; m_textureTiles.reserve((lastRow+1)*m_numCols); for (int row = 0; row <= lastRow; row++) { for (int col = 0; col <= lastCol; col++) { QRect tileRect = m_updateInfoBuilder.calculateEffectiveTileRect(col, row, m_image->bounds()); KisTextureTile *tile = new KisTextureTile(tileRect, &m_texturesInfo, emptyTileData, mode, config.useOpenGLTextureBuffer(), config.numMipmapLevels(), f); m_textureTiles.append(tile); } } } else { dbgUI << "Tried to init texture tiles without a current OpenGL Context."; } } void KisOpenGLImageTextures::destroyImageTextureTiles() { if (m_textureTiles.isEmpty()) return; Q_FOREACH (KisTextureTile *tile, m_textureTiles) { delete tile; } m_textureTiles.clear(); m_storedImageBounds = QRect(); } KisOpenGLUpdateInfoSP KisOpenGLImageTextures::updateCache(const QRect& rect, KisImageSP srcImage) { return updateCacheImpl(rect, srcImage, true); } KisOpenGLUpdateInfoSP KisOpenGLImageTextures::updateCacheNoConversion(const QRect& rect) { return updateCacheImpl(rect, m_image, false); } // TODO: add sanity checks about the conformance of the passed srcImage! KisOpenGLUpdateInfoSP KisOpenGLImageTextures::updateCacheImpl(const QRect& rect, KisImageSP srcImage, bool convertColorSpace) { if (!m_initialized) return new KisOpenGLUpdateInfo(); return m_updateInfoBuilder.buildUpdateInfo(rect, srcImage, convertColorSpace); } void KisOpenGLImageTextures::recalculateCache(KisUpdateInfoSP info, bool blockMipmapRegeneration) { if (!m_initialized) { dbgUI << "OpenGL: Tried to edit image texture cache before it was initialized."; return; } KisOpenGLUpdateInfoSP glInfo = dynamic_cast(info.data()); if(!glInfo) return; KisTextureTileUpdateInfoSP tileInfo; Q_FOREACH (tileInfo, glInfo->tileList) { KisTextureTile *tile = getTextureTileCR(tileInfo->tileCol(), tileInfo->tileRow()); KIS_ASSERT_RECOVER_RETURN(tile); tile->update(*tileInfo, blockMipmapRegeneration); } } void KisOpenGLImageTextures::generateCheckerTexture(const QImage &checkImage) { + if (!m_initialized) { + return; + } QOpenGLContext *ctx = QOpenGLContext::currentContext(); if (ctx) { QOpenGLFunctions *f = ctx->functions(); dbgUI << "Attaching checker texture" << checkerTexture(); f->glBindTexture(GL_TEXTURE_2D, checkerTexture()); f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); f->glPixelStorei(GL_UNPACK_ALIGNMENT, 1); QImage img = checkImage; if (checkImage.width() != BACKGROUND_TEXTURE_SIZE || checkImage.height() != BACKGROUND_TEXTURE_SIZE) { img = checkImage.scaled(BACKGROUND_TEXTURE_SIZE, BACKGROUND_TEXTURE_SIZE); } -#ifdef HAS_ONLY_OPENGL_ES - GLint format = GL_RGBA, internalFormat = GL_RGBA8; -#else - GLint format = GL_BGRA, internalFormat = GL_RGBA8; - if (KisOpenGL::hasOpenGLES()) { - if (ctx->hasExtension(QByteArrayLiteral("GL_EXT_texture_format_BGRA8888"))) { - format = GL_BGRA_EXT; - internalFormat = GL_BGRA8_EXT; - } else { - format = GL_RGBA; - } - } -#endif + + // convert from sRGB to display format, potentially HDR + const KoColorSpace *temporaryColorSpace = KoColorSpaceRegistry::instance()->rgb8(); + const KoColorSpace *finalColorSpace = + KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), + m_updateInfoBuilder.destinationColorSpace()->colorDepthId().id(), + m_monitorProfile); + + KisFixedPaintDevice checkers(temporaryColorSpace); + checkers.convertFromQImage(img, temporaryColorSpace->profile()->name()); + checkers.convertTo(finalColorSpace); + + KIS_ASSERT(checkers.bounds().width() == BACKGROUND_TEXTURE_SIZE); + KIS_ASSERT(checkers.bounds().height() == BACKGROUND_TEXTURE_SIZE); + + GLint format = m_texturesInfo.format; + GLint internalFormat = m_texturesInfo.internalFormat; + GLint type = m_texturesInfo.type; + f->glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, BACKGROUND_TEXTURE_SIZE, BACKGROUND_TEXTURE_SIZE, - 0, format, GL_UNSIGNED_BYTE, img.constBits()); + 0, format, type, checkers.data()); } else { dbgUI << "OpenGL: Tried to generate checker texture before OpenGL was initialized."; } } GLuint KisOpenGLImageTextures::checkerTexture() { if (m_glFuncs) { if (m_checkerTexture == 0) { m_glFuncs->glGenTextures(1, &m_checkerTexture); } return m_checkerTexture; } else { dbgUI << "Tried to access checker texture before OpenGL was initialized"; return 0; } } void KisOpenGLImageTextures::updateConfig(bool useBuffer, int NumMipmapLevels) { if(m_textureTiles.isEmpty()) return; Q_FOREACH (KisTextureTile *tile, m_textureTiles) { tile->setUseBuffer(useBuffer); tile->setNumMipmapLevels(NumMipmapLevels); } } void KisOpenGLImageTextures::slotImageSizeChanged(qint32 /*w*/, qint32 /*h*/) { recreateImageTextureTiles(); } KisOpenGLUpdateInfoBuilder &KisOpenGLImageTextures::updateInfoBuilder() { return m_updateInfoBuilder; } +const KoColorProfile *KisOpenGLImageTextures::monitorProfile() +{ + return m_monitorProfile; +} + void KisOpenGLImageTextures::setMonitorProfile(const KoColorProfile *monitorProfile, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { //dbgUI << "Setting monitor profile to" << monitorProfile->name() << renderingIntent << conversionFlags; m_monitorProfile = monitorProfile; m_renderingIntent = renderingIntent; m_conversionFlags = conversionFlags; recreateImageTextureTiles(); } bool KisOpenGLImageTextures::setImageColorSpace(const KoColorSpace *cs) { Q_UNUSED(cs); // TODO: implement lazy update: do not re-upload textures when not needed recreateImageTextureTiles(); return true; } void KisOpenGLImageTextures::setChannelFlags(const QBitArray &channelFlags) { QBitArray targetChannelFlags = channelFlags; int selectedChannels = 0; const KoColorSpace *projectionCs = m_image->projection()->colorSpace(); QList channelInfo = projectionCs->channels(); if (targetChannelFlags.size() != channelInfo.size()) { targetChannelFlags = QBitArray(); } int selectedChannelIndex = -1; for (int i = 0; i < targetChannelFlags.size(); ++i) { if (targetChannelFlags.testBit(i) && channelInfo[i]->channelType() == KoChannelInfo::COLOR) { selectedChannels++; selectedChannelIndex = i; } } const bool allChannelsSelected = (selectedChannels == targetChannelFlags.size()); const bool onlyOneChannelSelected = (selectedChannels == 1); // OCIO has its own channel swizzling if (allChannelsSelected || m_useOcio) { m_updateInfoBuilder.setChannelFlags(QBitArray(), false, -1); } else { m_updateInfoBuilder.setChannelFlags(targetChannelFlags, onlyOneChannelSelected, selectedChannelIndex); } } void KisOpenGLImageTextures::setProofingConfig(KisProofingConfigurationSP proofingConfig) { m_updateInfoBuilder.setProofingConfig(proofingConfig); } void KisOpenGLImageTextures::getTextureSize(KisGLTexturesInfo *texturesInfo) { KisConfig cfg(true); const GLint preferredTextureSize = cfg.openGLTextureSize(); GLint maxTextureSize; if (m_glFuncs) { m_glFuncs->glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize); } else { dbgUI << "OpenGL: Tried to read texture size before OpenGL was initialized."; maxTextureSize = GL_MAX_TEXTURE_SIZE; } texturesInfo->width = qMin(preferredTextureSize, maxTextureSize); texturesInfo->height = qMin(preferredTextureSize, maxTextureSize); texturesInfo->border = cfg.textureOverlapBorder(); texturesInfo->effectiveWidth = texturesInfo->width - 2 * texturesInfo->border; texturesInfo->effectiveHeight = texturesInfo->height - 2 * texturesInfo->border; m_updateInfoBuilder.setTextureBorder(texturesInfo->border); m_updateInfoBuilder.setEffectiveTextureSize( QSize(texturesInfo->effectiveWidth, texturesInfo->effectiveHeight)); } bool KisOpenGLImageTextures::internalColorManagementActive() const { return m_internalColorManagementActive; } bool KisOpenGLImageTextures::setInternalColorManagementActive(bool value) { bool needsFinalRegeneration = m_internalColorManagementActive != value; if (needsFinalRegeneration) { m_internalColorManagementActive = value; recreateImageTextureTiles(); // at this point the value of m_internalColorManagementActive might // have been forcely reverted to 'false' in case of some problems } return needsFinalRegeneration; } namespace { void initializeRGBA16FTextures(QOpenGLContext *ctx, KisGLTexturesInfo &texturesInfo, KoID &destinationColorDepthId) { if (KisOpenGL::hasOpenGLES() || KisOpenGL::hasOpenGL3()) { texturesInfo.internalFormat = GL_RGBA16F; dbgUI << "Using half (GLES or GL3)"; } else if (ctx->hasExtension("GL_ARB_texture_float")) { #ifndef HAS_ONLY_OPENGL_ES texturesInfo.internalFormat = GL_RGBA16F_ARB; dbgUI << "Using ARB half"; } else if (ctx->hasExtension("GL_ATI_texture_float")) { texturesInfo.internalFormat = GL_RGBA_FLOAT16_ATI; dbgUI << "Using ATI half"; #else KIS_ASSERT_X(false, "initializeRGBA16FTextures", "Unexpected KisOpenGL::hasOpenGLES and \ KisOpenGL::hasOpenGL3 returned false"); #endif } bool haveBuiltInOpenExr = false; #ifdef HAVE_OPENEXR haveBuiltInOpenExr = true; #endif if (haveBuiltInOpenExr && (KisOpenGL::hasOpenGLES() || KisOpenGL::hasOpenGL3())) { texturesInfo.type = GL_HALF_FLOAT; destinationColorDepthId = Float16BitsColorDepthID; dbgUI << "Pixel type half (GLES or GL3)"; } else if (haveBuiltInOpenExr && ctx->hasExtension("GL_ARB_half_float_pixel")) { #ifndef HAS_ONLY_OPENGL_ES texturesInfo.type = GL_HALF_FLOAT_ARB; destinationColorDepthId = Float16BitsColorDepthID; dbgUI << "Pixel type half"; } else { texturesInfo.type = GL_FLOAT; destinationColorDepthId = Float32BitsColorDepthID; dbgUI << "Pixel type float"; #else KIS_ASSERT_X(false, "KisOpenGLCanvas2::paintToolOutline", "Unexpected KisOpenGL::hasOpenGLES and \ KisOpenGL::hasOpenGL3 returned false"); #endif } texturesInfo.format = GL_RGBA; } } void KisOpenGLImageTextures::updateTextureFormat() { QOpenGLContext *ctx = QOpenGLContext::currentContext(); if (!(m_image && ctx)) return; if (!KisOpenGL::hasOpenGLES()) { #ifndef HAS_ONLY_OPENGL_ES m_texturesInfo.internalFormat = GL_RGBA8; m_texturesInfo.type = GL_UNSIGNED_BYTE; m_texturesInfo.format = GL_BGRA; #else KIS_ASSERT_X(false, "KisOpenGLImageTextures::updateTextureFormat", "Unexpected KisOpenGL::hasOpenGLES returned false"); #endif } else { #ifdef HAS_ONLY_OPENGL_ES m_texturesInfo.internalFormat = GL_RGBA8; m_texturesInfo.type = GL_UNSIGNED_BYTE; m_texturesInfo.format = GL_RGBA; #else m_texturesInfo.internalFormat = GL_BGRA8_EXT; m_texturesInfo.type = GL_UNSIGNED_BYTE; m_texturesInfo.format = GL_BGRA_EXT; if(!ctx->hasExtension(QByteArrayLiteral("GL_EXT_texture_format_BGRA8888"))) { // The red and blue channels are swapped, but it will be re-swapped // by texture swizzle mask set in KisTextureTile::setTextureParameters m_texturesInfo.internalFormat = GL_RGBA8; m_texturesInfo.type = GL_UNSIGNED_BYTE; m_texturesInfo.format = GL_RGBA; } #endif } const bool useHDRMode = KisOpenGLModeProber::instance()->useHDRMode(); const KoID colorModelId = m_image->colorSpace()->colorModelId(); const KoID colorDepthId = useHDRMode ? Float16BitsColorDepthID : m_image->colorSpace()->colorDepthId(); KoID destinationColorModelId = RGBAColorModelID; KoID destinationColorDepthId = Integer8BitsColorDepthID; dbgUI << "Choosing texture format:"; if (colorModelId == RGBAColorModelID) { if (colorDepthId == Float16BitsColorDepthID) { initializeRGBA16FTextures(ctx, m_texturesInfo, destinationColorDepthId); } else if (colorDepthId == Float32BitsColorDepthID) { if (KisOpenGL::hasOpenGLES() || KisOpenGL::hasOpenGL3()) { m_texturesInfo.internalFormat = GL_RGBA32F; dbgUI << "Using float (GLES or GL3)"; } else if (ctx->hasExtension("GL_ARB_texture_float")) { #ifndef HAS_ONLY_OPENGL_ES m_texturesInfo.internalFormat = GL_RGBA32F_ARB; dbgUI << "Using ARB float"; } else if (ctx->hasExtension("GL_ATI_texture_float")) { m_texturesInfo.internalFormat = GL_RGBA_FLOAT32_ATI; dbgUI << "Using ATI float"; #else KIS_ASSERT_X(false, "KisOpenGLCanvas2::updateTextureFormat", "Unexpected KisOpenGL::hasOpenGLES and \ KisOpenGL::hasOpenGL3 returned false"); #endif } m_texturesInfo.type = GL_FLOAT; m_texturesInfo.format = GL_RGBA; destinationColorDepthId = Float32BitsColorDepthID; } else if (colorDepthId == Integer16BitsColorDepthID) { #ifndef HAS_ONLY_OPENGL_ES if (!KisOpenGL::hasOpenGLES()) { m_texturesInfo.internalFormat = GL_RGBA16; m_texturesInfo.type = GL_UNSIGNED_SHORT; m_texturesInfo.format = GL_BGRA; destinationColorDepthId = Integer16BitsColorDepthID; dbgUI << "Using 16 bits rgba"; } #endif // TODO: for ANGLE, see if we can convert to 16f to support 10-bit display } } else { // We will convert the colorspace to 16 bits rgba, instead of 8 bits if (colorDepthId == Integer16BitsColorDepthID && !KisOpenGL::hasOpenGLES()) { #ifndef HAS_ONLY_OPENGL_ES m_texturesInfo.internalFormat = GL_RGBA16; m_texturesInfo.type = GL_UNSIGNED_SHORT; m_texturesInfo.format = GL_BGRA; destinationColorDepthId = Integer16BitsColorDepthID; dbgUI << "Using conversion to 16 bits rgba"; #else KIS_ASSERT_X(false, "KisOpenGLCanvas2::updateTextureFormat", "Unexpected KisOpenGL::hasOpenGLES returned false"); #endif } else if (colorDepthId == Float16BitsColorDepthID && KisOpenGL::hasOpenGLES()) { // TODO: try removing opengl es limit initializeRGBA16FTextures(ctx, m_texturesInfo, destinationColorDepthId); } // TODO: for ANGLE, see if we can convert to 16f to support 10-bit display } if (!m_internalColorManagementActive && colorModelId != destinationColorModelId) { KisConfig cfg(false); KisConfig::OcioColorManagementMode cm = cfg.ocioColorManagementMode(); if (cm != KisConfig::INTERNAL) { emit sigShowFloatingMessage( i18n("OpenColorIO is disabled: image color space is not supported"), 5000, true); } warnUI << "WARNING: Internal color management was forcibly enabled"; warnUI << "Color Management Mode: " << cm; warnUI << ppVar(m_image->colorSpace()); warnUI << ppVar(destinationColorModelId); warnUI << ppVar(destinationColorDepthId); cfg.setOcioColorManagementMode(KisConfig::INTERNAL); m_internalColorManagementActive = true; } const KoColorProfile *profile = m_internalColorManagementActive || colorModelId != destinationColorModelId ? m_monitorProfile : m_image->colorSpace()->profile(); /** * TODO: add an optimization so that the tile->convertTo() method * would not be called when not needed (DK) */ const KoColorSpace *tilesDestinationColorSpace = KoColorSpaceRegistry::instance()->colorSpace(destinationColorModelId.id(), destinationColorDepthId.id(), profile); m_updateInfoBuilder.setConversionOptions( ConversionOptions(tilesDestinationColorSpace, m_renderingIntent, m_conversionFlags)); } diff --git a/libs/ui/opengl/kis_opengl_image_textures.h b/libs/ui/opengl/kis_opengl_image_textures.h index 3ce622caba..42f04f2ad2 100644 --- a/libs/ui/opengl/kis_opengl_image_textures.h +++ b/libs/ui/opengl/kis_opengl_image_textures.h @@ -1,212 +1,213 @@ /* * Copyright (c) 2005-2007 Adrian Page * * 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_OPENGL_IMAGE_TEXTURES_H_ #define KIS_OPENGL_IMAGE_TEXTURES_H_ #include #include #include #include "kritaui_export.h" #include "kis_shared.h" #include "canvas/kis_update_info.h" #include "opengl/kis_texture_tile.h" #include "KisOpenGLUpdateInfoBuilder.h" class KisOpenGLImageTextures; typedef KisSharedPtr KisOpenGLImageTexturesSP; class KoColorProfile; class KisTextureTileUpdateInfoPoolCollection; typedef QSharedPointer KisTextureTileInfoPoolSP; class KisProofingConfiguration; typedef QSharedPointer KisProofingConfigurationSP; /** * A set of OpenGL textures that contains the projection of a KisImage. */ class KRITAUI_EXPORT KisOpenGLImageTextures : public QObject, public KisShared { Q_OBJECT public: /** * Obtain a KisOpenGLImageTextures object for the given image. * @param image The image * @param monitorProfile The profile of the display device * @param renderingIntent The rendering intent * @param conversionFlags The color conversion flags */ static KisOpenGLImageTexturesSP getImageTextures(KisImageWSP image, const KoColorProfile *monitorProfile, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags); /** * Default constructor. */ KisOpenGLImageTextures(); /** * Destructor. */ virtual ~KisOpenGLImageTextures(); /** * \return the image associated with the textures */ KisImageSP image() const; /** * Set the color profile of the display device. * @param monitorProfile The color profile of the display device * @param renderingIntent The rendering intent * @param conversionFlags The color conversion flags */ void setMonitorProfile(const KoColorProfile *monitorProfile, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags); /** * Update the textures when the color space of the image changes. * @return true when a full data refetch should be initiated by the caller */ bool setImageColorSpace(const KoColorSpace *cs); /** * Complete initialization can only happen once an OpenGL context has been created. * @param f Pointer to OpenGL functions. They must already be initialized. */ void initGL(QOpenGLFunctions *f); void setChannelFlags(const QBitArray &channelFlags); void setProofingConfig(KisProofingConfigurationSP); bool internalColorManagementActive() const; bool setInternalColorManagementActive(bool value); /** * The background checkers texture. */ static const int BACKGROUND_TEXTURE_CHECK_SIZE = 32; static const int BACKGROUND_TEXTURE_SIZE = BACKGROUND_TEXTURE_CHECK_SIZE * 2; /** * Generate a background texture from the given QImage. This is used for the checker * pattern on which the image is rendered. */ void generateCheckerTexture(const QImage & checkImage); GLuint checkerTexture(); void updateConfig(bool useBuffer, int NumMipmapLevels); public: inline QRect storedImageBounds() { return m_storedImageBounds; } inline int xToCol(int x) { return x / m_texturesInfo.effectiveWidth; } inline int yToRow(int y) { return y / m_texturesInfo.effectiveHeight; } inline KisTextureTile* getTextureTileCR(int col, int row) { if (m_initialized) { int tile = row * m_numCols + col; KIS_ASSERT_RECOVER_RETURN_VALUE(m_textureTiles.size() > tile, 0); return m_textureTiles[tile]; } return 0; } inline qreal texelSize() const { Q_ASSERT(m_texturesInfo.width == m_texturesInfo.height); return 1.0 / m_texturesInfo.width; } KisOpenGLUpdateInfoSP updateCache(const QRect& rect, KisImageSP srcImage); KisOpenGLUpdateInfoSP updateCacheNoConversion(const QRect& rect); void recalculateCache(KisUpdateInfoSP info, bool blockMipmapRegeneration); void slotImageSizeChanged(qint32 w, qint32 h); KisOpenGLUpdateInfoBuilder& updateInfoBuilder(); + const KoColorProfile* monitorProfile(); Q_SIGNALS: void sigShowFloatingMessage(const QString &message, int timeout, bool priority); protected: KisOpenGLImageTextures(KisImageWSP image, const KoColorProfile *monitorProfile, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags); void recreateImageTextureTiles(); void destroyImageTextureTiles(); static bool imageCanShareTextures(); private: void getTextureSize(KisGLTexturesInfo *texturesInfo); void updateTextureFormat(); KisOpenGLUpdateInfoSP updateCacheImpl(const QRect& rect, KisImageSP srcImage, bool convertColorSpace); private: KisImageWSP m_image; QRect m_storedImageBounds; const KoColorProfile *m_monitorProfile; KoColorConversionTransformation::Intent m_renderingIntent; KoColorConversionTransformation::ConversionFlags m_conversionFlags; /** * Shows whether the internal color management should be enabled or not. * Please note that if you disable color management, *but* your image color * space will not be supported (non-RGB), then it will be enabled anyway. * And this valiable will hold the real state of affairs! */ bool m_internalColorManagementActive; GLuint m_checkerTexture; KisGLTexturesInfo m_texturesInfo; int m_numCols; QVector m_textureTiles; QOpenGLFunctions *m_glFuncs; bool m_useOcio; bool m_initialized; KisOpenGLUpdateInfoBuilder m_updateInfoBuilder; private: typedef QMap ImageTexturesMap; static ImageTexturesMap imageTexturesMap; }; #endif // KIS_OPENGL_IMAGE_TEXTURES_H_ diff --git a/libs/ui/tool/kis_tool.h b/libs/ui/tool/kis_tool.h index fdbd5e67f9..00d15c6796 100644 --- a/libs/ui/tool/kis_tool.h +++ b/libs/ui/tool/kis_tool.h @@ -1,329 +1,329 @@ /* * 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_TOOL_H_ #define KIS_TOOL_H_ #include #include #include #include #include #include #include #ifdef __GNUC__ #define WARN_WRONG_MODE(_mode) warnKrita << "Unexpected tool event has come to" << __func__ << "while being mode" << _mode << "!" #else #define WARN_WRONG_MODE(_mode) warnKrita << "Unexpected tool event has come while being mode" << _mode << "!" #endif #define CHECK_MODE_SANITY_OR_RETURN(_mode) if (mode() != _mode) { WARN_WRONG_MODE(mode()); return; } class KoCanvasBase; class KoPattern; class KoAbstractGradient; class KisFilterConfiguration; class QPainter; class QPainterPath; class QPolygonF; /// Definitions of the toolgroups of Krita static const QString TOOL_TYPE_SHAPE = "0 Krita/Shape"; // Geometric shapes like ellipses and lines static const QString TOOL_TYPE_TRANSFORM = "2 Krita/Transform"; // Tools that transform the layer; static const QString TOOL_TYPE_FILL = "3 Krita/Fill"; // Tools that fill parts of the canvas static const QString TOOL_TYPE_VIEW = "4 Krita/View"; // Tools that affect the canvas: pan, zoom, etc. static const QString TOOL_TYPE_SELECTION = "5 Krita/Select"; // Tools that select pixels //activation id for Krita tools, Krita tools are always active and handle locked and invisible layers by themself static const QString KRITA_TOOL_ACTIVATION_ID = "flake/always"; class KRITAUI_EXPORT KisTool : public KoToolBase { Q_OBJECT Q_PROPERTY(bool isActive READ isActive NOTIFY isActiveChanged) public: enum { FLAG_USES_CUSTOM_PRESET=0x01, FLAG_USES_CUSTOM_COMPOSITEOP=0x02, FLAG_USES_CUSTOM_SIZE=0x04 }; KisTool(KoCanvasBase * canvas, const QCursor & cursor); ~KisTool() override; virtual int flags() const { return 0; } void deleteSelection() override; // KoToolBase Implementation. public: /** * Called by KisToolProxy when the primary action of the tool is * going to be started now, that is when all the modifiers are * pressed and the only thing left is just to press the mouse * button. On coming of this callback the tool is supposed to * prepare the cursor and/or the outline to show the user shat is * going to happen next */ virtual void activatePrimaryAction(); /** * Called by KisToolProxy when the primary is no longer possible * to be started now, e.g. when its modifiers and released. The - * tool is supposed revert all the preparetions it has doen in + * tool is supposed to revert all the preparations it has done in * activatePrimaryAction(). */ virtual void deactivatePrimaryAction(); /** * Called by KisToolProxy when a primary action for the tool is * started. The \p event stores the original event that * started the stroke. The \p event is _accepted_ by default. If * the tool decides to ignore this particular action (e.g. when * the node is not editable), it should call event->ignore(). Then * no further continuePrimaryAction() or endPrimaryAction() will * be called until the next user action. */ virtual void beginPrimaryAction(KoPointerEvent *event); /** * Called by KisToolProxy when the primary action is in progress * of pointer movement. If the tool has ignored the event in * beginPrimaryAction(), this method will not be called. */ virtual void continuePrimaryAction(KoPointerEvent *event); /** * Called by KisToolProxy when the primary action is being * finished, that is while mouseRelease or tabletRelease event. * If the tool has ignored the event in beginPrimaryAction(), this * method will not be called. */ virtual void endPrimaryAction(KoPointerEvent *event); /** * The same as beginPrimaryAction(), but called when the stroke is * started by a double-click * * \see beginPrimaryAction() */ virtual void beginPrimaryDoubleClickAction(KoPointerEvent *event); /** * Returns true if the tool can handle (and wants to handle) a * very tight flow of input events from the tablet */ virtual bool primaryActionSupportsHiResEvents() const; enum ToolAction { Primary, AlternateChangeSize, AlternatePickFgNode, AlternatePickBgNode, AlternatePickFgImage, AlternatePickBgImage, AlternateSecondary, AlternateThird, AlternateFourth, AlternateFifth, Alternate_NONE = 10000 }; // Technically users are allowed to configure this, but nobody ever would do that. // So these can basically be thought of as aliases to ctrl+click, etc. enum AlternateAction { ChangeSize = AlternateChangeSize, // Default: Shift+Left click PickFgNode = AlternatePickFgNode, // Default: Ctrl+Alt+Left click PickBgNode = AlternatePickBgNode, // Default: Ctrl+Alt+Right click PickFgImage = AlternatePickFgImage, // Default: Ctrl+Left click PickBgImage = AlternatePickBgImage, // Default: Ctrl+Right click Secondary = AlternateSecondary, Third = AlternateThird, Fourth = AlternateFourth, Fifth = AlternateFifth, NONE = 10000 }; enum NodePaintAbility { VECTOR, CLONE, PAINT, UNPAINTABLE }; Q_ENUMS(NodePaintAbility) static AlternateAction actionToAlternateAction(ToolAction action); virtual void activateAlternateAction(AlternateAction action); virtual void deactivateAlternateAction(AlternateAction action); virtual void beginAlternateAction(KoPointerEvent *event, AlternateAction action); virtual void continueAlternateAction(KoPointerEvent *event, AlternateAction action); virtual void endAlternateAction(KoPointerEvent *event, AlternateAction action); virtual void beginAlternateDoubleClickAction(KoPointerEvent *event, AlternateAction action); void mousePressEvent(KoPointerEvent *event) override; void mouseDoubleClickEvent(KoPointerEvent *event) override; void mouseTripleClickEvent(KoPointerEvent *event) override; void mouseReleaseEvent(KoPointerEvent *event) override; void mouseMoveEvent(KoPointerEvent *event) override; bool isActive() const; KisTool::NodePaintAbility nodePaintAbility(); public Q_SLOTS: void activate(ToolActivation activation, const QSet &shapes) override; void deactivate() override; void canvasResourceChanged(int key, const QVariant & res) override; // Implement this slot in case there are any widgets or properties which need // to be updated after certain operations, to reflect the inner state correctly. // At the moment this is used for smoothing options in the freehand brush, but // this will likely be expanded. virtual void updateSettingsViews(); Q_SIGNALS: void isActiveChanged(bool isActivated); protected: // conversion methods are also needed by the paint information builder friend class KisToolPaintingInformationBuilder; /// Convert from native (postscript points) to image pixel /// coordinates. QPointF convertToPixelCoord(KoPointerEvent *e); QPointF convertToPixelCoord(const QPointF& pt); QPointF convertToPixelCoordAndSnap(KoPointerEvent *e, const QPointF &offset = QPointF(), bool useModifiers = true); QPointF convertToPixelCoordAndSnap(const QPointF& pt, const QPointF &offset = QPointF()); protected: QPointF widgetCenterInWidgetPixels(); QPointF convertDocumentToWidget(const QPointF& pt); /// Convert from native (postscript points) to integer image pixel /// coordinates. This rounds down (not truncate) the pixel coordinates and /// should be used in preference to QPointF::toPoint(), which rounds, /// to ensure the cursor acts on the pixel it is visually over. QPoint convertToImagePixelCoordFloored(KoPointerEvent *e); QRectF convertToPt(const QRectF &rect); qreal convertToPt(qreal value); QPointF viewToPixel(const QPointF &viewCoord) const; /// Convert an integer pixel coordinate into a view coordinate. /// The view coordinate is at the centre of the pixel. QPointF pixelToView(const QPoint &pixelCoord) const; /// Convert a floating point pixel coordinate into a view coordinate. QPointF pixelToView(const QPointF &pixelCoord) const; /// Convert a pixel rectangle into a view rectangle. QRectF pixelToView(const QRectF &pixelRect) const; /// Convert a pixel path into a view path QPainterPath pixelToView(const QPainterPath &pixelPath) const; /// Convert a pixel polygon into a view path QPolygonF pixelToView(const QPolygonF &pixelPolygon) const; /// Update the canvas for the given rectangle in image pixel coordinates. void updateCanvasPixelRect(const QRectF &pixelRect); /// Update the canvas for the given rectangle in view coordinates. void updateCanvasViewRect(const QRectF &viewRect); QWidget* createOptionWidget() override; /** * To determine whether this tool will change its behavior when * modifier keys are pressed */ virtual bool listeningToModifiers(); /** * Request that this tool no longer listen to modifier keys * (Responding to the request is optional) */ virtual void listenToModifiers(bool listen); protected: KisImageWSP image() const; QCursor cursor() const; /// Call this to set the document modified void notifyModified() const; KisImageWSP currentImage(); KoPattern* currentPattern(); KoAbstractGradient *currentGradient(); KisNodeSP currentNode() const; KisNodeList selectedNodes() const; KoColor currentFgColor(); KoColor currentBgColor(); KisPaintOpPresetSP currentPaintOpPreset(); KisFilterConfigurationSP currentGenerator(); /// paint the path which is in view coordinates, default paint mode is XOR_MODE, BW_MODE is also possible /// never apply transformations to the painter, they would be useless, if drawing in OpenGL mode. The coordinates in the path should be in view coordinates. void paintToolOutline(QPainter * painter, const QPainterPath &path); /// Checks checks if the current node is editable bool nodeEditable(); /// Checks checks if the selection is editable, only applies to local selection as global selection is always editable bool selectionEditable(); /// Override the cursor appropriately if current node is not editable bool overrideCursorIfNotEditable(); bool blockUntilOperationsFinished(); void blockUntilOperationsFinishedForced(); protected: enum ToolMode: int { HOVER_MODE, PAINT_MODE, SECONDARY_PAINT_MODE, MIRROR_AXIS_SETUP_MODE, GESTURE_MODE, PAN_MODE, OTHER, // tool-specific modes, like multibrush's symmetry axis setup OTHER_1 }; virtual void setMode(ToolMode mode); virtual ToolMode mode() const; void setCursor(const QCursor &cursor); protected Q_SLOTS: /** * Called whenever the configuration settings change. */ virtual void resetCursorStyle(); private: struct Private; Private* const d; }; #endif // KIS_TOOL_H_ diff --git a/libs/ui/widgets/KisNewsWidget.cpp b/libs/ui/widgets/KisNewsWidget.cpp index 273a05f973..bf9207aa84 100644 --- a/libs/ui/widgets/KisNewsWidget.cpp +++ b/libs/ui/widgets/KisNewsWidget.cpp @@ -1,223 +1,229 @@ /* * Copyright (c) 2018 boud * * 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 "KisNewsWidget.h" #include #include #include #include #include #include #include #include "kis_config.h" #include "KisMultiFeedRSSModel.h" #include "QRegularExpression" KisNewsDelegate::KisNewsDelegate(QObject *parent) : QStyledItemDelegate(parent) { } void KisNewsDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { painter->save(); QStyleOptionViewItem optionCopy = option; initStyleOption(&optionCopy, index); QStyle *style = optionCopy.widget? optionCopy.widget->style() : QApplication::style(); QTextDocument doc; doc.setHtml(optionCopy.text); doc.setDocumentMargin(10); /// Painting item without text optionCopy.text = QString(); style->drawControl(QStyle::CE_ItemViewItem, &optionCopy, painter); QAbstractTextDocumentLayout::PaintContext ctx; // Highlighting text if item is selected if (optionCopy.state & QStyle::State_Selected) { ctx.palette.setColor(QPalette::Text, optionCopy.palette.color(QPalette::Active, QPalette::HighlightedText)); } painter->translate(optionCopy.rect.left(), optionCopy.rect.top()); QRect clip(0, 0, optionCopy.rect.width(), optionCopy.rect.height()); doc.setPageSize(clip.size()); doc.drawContents(painter, clip); painter->restore(); } QSize KisNewsDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { QStyleOptionViewItem optionCopy = option; initStyleOption(&optionCopy, index); QTextDocument doc; doc.setHtml(optionCopy.text); doc.setTextWidth(optionCopy.rect.width()); return QSize(doc.idealWidth(), doc.size().height()); } KisNewsWidget::KisNewsWidget(QWidget *parent) : QWidget(parent) + , m_getNews(false) + , m_rssModel(0) + , m_needsVersionUpdate(false) { setupUi(this); m_rssModel = new MultiFeedRssModel(this); connect(m_rssModel, SIGNAL(feedDataChanged()), this, SLOT(rssDataChanged())); setCursor(Qt::PointingHandCursor); listNews->setModel(m_rssModel); listNews->setItemDelegate(new KisNewsDelegate(listNews)); connect(listNews, SIGNAL(clicked(QModelIndex)), this, SLOT(itemSelected(QModelIndex))); } void KisNewsWidget::setAnalyticsTracking(QString text) { - analyticsTrackingParameters = text; + m_analyticsTrackingParameters = text; } bool KisNewsWidget::hasUpdateAvailable() { - return needsVersionUpdate; + return m_needsVersionUpdate; } QString KisNewsWidget::versionNumber() { - return newVersionNumber; + return m_newVersionNumber; } QString KisNewsWidget::versionLink() { - return newVersionLink; + return m_newVersionLink; } void KisNewsWidget::toggleNews(bool toggle) { KisConfig cfg(false); cfg.writeEntry("FetchNews", toggle); if (toggle) { m_rssModel->addFeed(QLatin1String("https://krita.org/en/feed/")); } else { m_rssModel->removeFeed(QLatin1String("https://krita.org/en/feed/")); } } void KisNewsWidget::itemSelected(const QModelIndex &idx) { if (idx.isValid()) { QString link = idx.data(RssRoles::LinkRole).toString(); // append query string for analytics tracking if we set it - if (analyticsTrackingParameters != "") { + if (m_analyticsTrackingParameters != "") { // use title in analytics query string QString linkTitle = idx.data(RssRoles::TitleRole).toString(); linkTitle = linkTitle.simplified(); // trims and makes 1 white space linkTitle = linkTitle.replace(" ", ""); - analyticsTrackingParameters = analyticsTrackingParameters.append(linkTitle); - QDesktopServices::openUrl(QUrl(link.append(analyticsTrackingParameters))); + m_analyticsTrackingParameters = m_analyticsTrackingParameters.append(linkTitle); + QDesktopServices::openUrl(QUrl(link.append(m_analyticsTrackingParameters))); } else { QDesktopServices::openUrl(QUrl(link)); } } } void KisNewsWidget::rssDataChanged() { // grab the latest release post and URL for reference later // if we need to update for (int i = 0; i < m_rssModel->rowCount(); i++) { const QModelIndex &idx = m_rssModel->index(i); if (idx.isValid()) { // only use official release announcements to get version number if ( idx.data(RssRoles::CategoryRole).toString() != "Official Release") { continue; } QString linkTitle = idx.data(RssRoles::TitleRole).toString(); // regex to capture version number QRegularExpression versionRegex("\\d\\.\\d\\.?\\d?\\.?\\d"); QRegularExpressionMatch matched = versionRegex.match(linkTitle); // only take the top match for release version since that is the newest if (matched.hasMatch()) { - newVersionNumber = matched.captured(0); - newVersionLink = idx.data(RssRoles::LinkRole).toString(); + m_newVersionNumber = matched.captured(0); + m_newVersionLink = idx.data(RssRoles::LinkRole).toString(); break; } - } } // see if we need to update our version, or we are on a dev version calculateVersionUpdateStatus(); emit newsDataChanged(); } void KisNewsWidget::calculateVersionUpdateStatus() { - // do version compare to see if there is a new version available - // also check to see if we are on a dev version (newer than newest release) - QStringList currentVersionParts = qApp->applicationVersion().split("."); - QStringList onlineReleaseAnnouncement = newVersionNumber.split("."); - - // is the major version different? - if (onlineReleaseAnnouncement[0] > currentVersionParts[0] ) { - needsVersionUpdate = true; // we are a major version behind + // do nothing if we are in dev version. + QString currentVersionString = qApp->applicationVersion(); + if (currentVersionString.contains("git")) { return; } - // major versions are the same, so check minor versions - if (onlineReleaseAnnouncement[1] > currentVersionParts[1] ) { - needsVersionUpdate = true; // we are a minor version behind - return; - } - - // minor versions are the same, so maybe bugfix version is different - // sometimes we don't communicate this, implictly make 0 if it doesn't exist - if (onlineReleaseAnnouncement[2].isNull()) { - onlineReleaseAnnouncement[2] = "0"; - } - if (currentVersionParts[2].isNull()) { - currentVersionParts[2] = "0"; - } - - if (onlineReleaseAnnouncement[2] > currentVersionParts[2] ) { - needsVersionUpdate = true; // we are a bugfix version behind - return; - } + QList currentVersionParts; + Q_FOREACH (QString number, currentVersionString.split(".")) { + currentVersionParts.append(number.toInt()); + } + + QList onlineReleaseAnnouncement; + Q_FOREACH (QString number, m_newVersionNumber.split(".")) { + onlineReleaseAnnouncement.append(number.toInt()); + } + + while (onlineReleaseAnnouncement.size() < 4) { + onlineReleaseAnnouncement.append(0); + } + + while (currentVersionParts.size() < 4) { + currentVersionParts.append(0); + } + // Check versions from mayor to minor + // We don't assume onlineRelease version is always equal or higher. + bool makeUpdate = true; + for (int i = 0; i <= 3; i++) { + if (onlineReleaseAnnouncement.at(i) > currentVersionParts.at(i)) { + m_needsVersionUpdate = (true & makeUpdate); + return; + } else if (onlineReleaseAnnouncement.at(i) < currentVersionParts.at(i)) { + makeUpdate &= false; + } + } } diff --git a/libs/ui/widgets/KisNewsWidget.h b/libs/ui/widgets/KisNewsWidget.h index 7acfa7239d..71dba10506 100644 --- a/libs/ui/widgets/KisNewsWidget.h +++ b/libs/ui/widgets/KisNewsWidget.h @@ -1,75 +1,79 @@ /* * Copyright (c) 2018 boud * * 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 KISNEWSWIDGET_H #define KISNEWSWIDGET_H #include #include #include #include class MultiFeedRssModel; class KisNewsDelegate : public QStyledItemDelegate { Q_OBJECT public: KisNewsDelegate(QObject *parent = 0); void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; }; /** * @brief The KisNewsWidget class shows the latest news from Krita.org */ class KisNewsWidget : public QWidget, public Ui::KisNewsPage { Q_OBJECT public: explicit KisNewsWidget(QWidget *parent = nullptr); void setAnalyticsTracking(QString text); bool hasUpdateAvailable(); QString versionNumber(); QString versionLink(); Q_SIGNALS: void newsDataChanged(); private Q_SLOTS: void toggleNews(bool toggle); void itemSelected(const QModelIndex &idx); void rssDataChanged(); + private: - bool m_getNews {false}; - MultiFeedRssModel *m_rssModel {0}; - QString analyticsTrackingParameters; + // do version compare to see if there is a new version available + void calculateVersionUpdateStatus(); + +private: + bool m_getNews; + MultiFeedRssModel *m_rssModel; + QString m_analyticsTrackingParameters; /// for new Krita version notification - QString newVersionNumber; - QString newVersionLink; + QString m_newVersionNumber; + QString m_newVersionLink; // version checking logic tells us we need to update our Krita - void calculateVersionUpdateStatus(); - bool needsVersionUpdate = false; + bool m_needsVersionUpdate; }; #endif // KISNEWSWIDGET_H diff --git a/libs/ui/widgets/kis_advanced_color_space_selector.cc b/libs/ui/widgets/kis_advanced_color_space_selector.cc index c23a15bd56..2550e89840 100644 --- a/libs/ui/widgets/kis_advanced_color_space_selector.cc +++ b/libs/ui/widgets/kis_advanced_color_space_selector.cc @@ -1,795 +1,795 @@ /* * Copyright (C) 2007 Cyrille Berger * Copyright (C) 2011 Boudewijn Rempt * Copyright (C) 2011 Srikanth Tiyyagura * Copyright (C) 2015 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. */ #include "kis_advanced_color_space_selector.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ui_wdgcolorspaceselectoradvanced.h" #include struct KisAdvancedColorSpaceSelector::Private { Ui_WdgColorSpaceSelectorAdvanced* colorSpaceSelector; QString knsrcFile; }; KisAdvancedColorSpaceSelector::KisAdvancedColorSpaceSelector(QWidget* parent, const QString &caption) : QDialog(parent) , d(new Private) { setWindowTitle(caption); d->colorSpaceSelector = new Ui_WdgColorSpaceSelectorAdvanced; d->colorSpaceSelector->setupUi(this); d->colorSpaceSelector->cmbColorModels->setIDList(KoColorSpaceRegistry::instance()->colorModelsList(KoColorSpaceRegistry::OnlyUserVisible)); fillCmbDepths(d->colorSpaceSelector->cmbColorModels->currentItem()); d->colorSpaceSelector->bnInstallProfile->setIcon(KisIconUtils::loadIcon("document-open")); d->colorSpaceSelector->bnInstallProfile->setToolTip( i18n("Open Color Profile") ); connect(d->colorSpaceSelector->cmbColorModels, SIGNAL(activated(KoID)), this, SLOT(fillCmbDepths(KoID))); connect(d->colorSpaceSelector->cmbColorDepth, SIGNAL(activated(KoID)), this, SLOT(fillLstProfiles())); connect(d->colorSpaceSelector->cmbColorModels, SIGNAL(activated(KoID)), this, SLOT(fillLstProfiles())); connect(d->colorSpaceSelector->lstProfile, SIGNAL(currentItemChanged(QListWidgetItem*,QListWidgetItem*)), this, SLOT(colorSpaceChanged())); connect(this, SIGNAL(selectionChanged(bool)), this, SLOT(fillDescription())); connect(this, SIGNAL(selectionChanged(bool)), d->colorSpaceSelector->TongueWidget, SLOT(repaint())); connect(this, SIGNAL(selectionChanged(bool)), d->colorSpaceSelector->TRCwidget, SLOT(repaint())); connect(d->colorSpaceSelector->bnInstallProfile, SIGNAL(clicked()), this, SLOT(installProfile())); connect(d->colorSpaceSelector->bnOK, SIGNAL(accepted()), this, SLOT(accept())); connect(d->colorSpaceSelector->bnOK, SIGNAL(rejected()), this, SLOT(reject())); fillLstProfiles(); } KisAdvancedColorSpaceSelector::~KisAdvancedColorSpaceSelector() { delete d->colorSpaceSelector; delete d; } void KisAdvancedColorSpaceSelector::fillLstProfiles() { d->colorSpaceSelector->lstProfile->blockSignals(true); const QString colorSpaceId = KoColorSpaceRegistry::instance()->colorSpaceId(d->colorSpaceSelector->cmbColorModels->currentItem(), d->colorSpaceSelector->cmbColorDepth->currentItem()); const QString defaultProfileName = KoColorSpaceRegistry::instance()->defaultProfileForColorSpace(colorSpaceId); d->colorSpaceSelector->lstProfile->clear(); QList profileList = KoColorSpaceRegistry::instance()->profilesFor(colorSpaceId); QStringList profileNames; Q_FOREACH (const KoColorProfile *profile, profileList) { profileNames.append(profile->name()); } std::sort(profileNames.begin(), profileNames.end()); QListWidgetItem *defaultProfile = new QListWidgetItem; defaultProfile->setText(defaultProfileName + " " + i18nc("This is appended to the color profile which is the default for the given colorspace and bit-depth","(Default)")); Q_FOREACH (QString stringName, profileNames) { if (stringName == defaultProfileName) { d->colorSpaceSelector->lstProfile->addItem(defaultProfile); } else { d->colorSpaceSelector->lstProfile->addItem(stringName); } } d->colorSpaceSelector->lstProfile->setCurrentItem(defaultProfile); d->colorSpaceSelector->lstProfile->blockSignals(false); colorSpaceChanged(); } void KisAdvancedColorSpaceSelector::fillCmbDepths(const KoID& id) { KoID activeDepth = d->colorSpaceSelector->cmbColorDepth->currentItem(); d->colorSpaceSelector->cmbColorDepth->clear(); QList depths = KoColorSpaceRegistry::instance()->colorDepthList(id, KoColorSpaceRegistry::OnlyUserVisible); QList sortedDepths; if (depths.contains(Integer8BitsColorDepthID)) { sortedDepths << Integer8BitsColorDepthID; } if (depths.contains(Integer16BitsColorDepthID)) { sortedDepths << Integer16BitsColorDepthID; } if (depths.contains(Float16BitsColorDepthID)) { sortedDepths << Float16BitsColorDepthID; } if (depths.contains(Float32BitsColorDepthID)) { sortedDepths << Float32BitsColorDepthID; } if (depths.contains(Float64BitsColorDepthID)) { sortedDepths << Float64BitsColorDepthID; } d->colorSpaceSelector->cmbColorDepth->setIDList(sortedDepths); if (sortedDepths.contains(activeDepth)) { d->colorSpaceSelector->cmbColorDepth->setCurrent(activeDepth); } } void KisAdvancedColorSpaceSelector::fillDescription() { QString notApplicable = i18nc("Not Applicable, used where there's no colorants or gamma curve found","N/A"); QString notApplicableTooltip = i18nc("@info:tooltip","This profile has no colorants."); QString profileName = i18nc("Shows up instead of the name when there's no profile","No Profile Found"); QString whatIsColorant = i18n("Colorant in d50-adapted xyY."); //set colorants const QString colorSpaceId = KoColorSpaceRegistry::instance()->colorSpaceId(d->colorSpaceSelector->cmbColorModels->currentItem(), d->colorSpaceSelector->cmbColorDepth->currentItem()); QList profileList = KoColorSpaceRegistry::instance()->profilesFor(colorSpaceId); if (!profileList.isEmpty()) { profileName = currentColorSpace()->profile()->name(); if (currentColorSpace()->profile()->hasColorants()){ QVector colorants = currentColorSpace()->profile()->getColorantsxyY(); QVector whitepoint = currentColorSpace()->profile()->getWhitePointxyY(); //QString text = currentColorSpace()->profile()->info() + " =" + d->colorSpaceSelector->lblXYZ_W->setText(nameWhitePoint(whitepoint)); d->colorSpaceSelector->lblXYZ_W->setToolTip(QString::number(whitepoint[0], 'f', 4) + ", " + QString::number(whitepoint[1], 'f', 4) + ", " + QString::number(whitepoint[2], 'f', 4)); d->colorSpaceSelector->TongueWidget->setToolTip("
"+i18nc("@info:tooltip","This profile has the following xyY colorants:")+"
"+ i18n("Red:") +""+QString::number(colorants[0], 'f', 4) + "" + QString::number(colorants[1], 'f', 4) + "" + QString::number(colorants[2], 'f', 4)+"
"+ i18n("Green:")+""+QString::number(colorants[3], 'f', 4) + "" + QString::number(colorants[4], 'f', 4) + "" + QString::number(colorants[5], 'f', 4)+"
"+ i18n("Blue:") +""+QString::number(colorants[6], 'f', 4) + "" + QString::number(colorants[7], 'f', 4) + "" + QString::number(colorants[8], 'f', 4)+"
"); } else { QVector whitepoint2 = currentColorSpace()->profile()->getWhitePointxyY(); d->colorSpaceSelector->lblXYZ_W->setText(nameWhitePoint(whitepoint2)); d->colorSpaceSelector->lblXYZ_W->setToolTip(QString::number(whitepoint2[0], 'f', 4) + ", " + QString::number(whitepoint2[1], 'f', 4) + ", " + QString::number(whitepoint2[2], 'f', 4)); d->colorSpaceSelector->TongueWidget->setToolTip(notApplicableTooltip); } } else { d->colorSpaceSelector->lblXYZ_W->setText(notApplicable); d->colorSpaceSelector->lblXYZ_W->setToolTip(notApplicableTooltip); d->colorSpaceSelector->TongueWidget->setToolTip(notApplicableTooltip); } //set TRC QVector estimatedTRC(3); QString estimatedGamma = i18nc("Estimated Gamma indicates how the TRC (Tone Response Curve or Tone Reproduction Curve) is bent. A Gamma of 1.0 means linear.", "Estimated Gamma: "); QString estimatedsRGB = i18nc("This is for special Gamma types that LCMS cannot differentiate between", "Estimated Gamma: sRGB, L* or rec709 TRC"); QString whatissRGB = i18nc("@info:tooltip","The Tone Response Curve of this color space is either sRGB, L* or rec709 TRC."); QString currentModelStr = d->colorSpaceSelector->cmbColorModels->currentItem().id(); if (profileList.isEmpty()) { d->colorSpaceSelector->TongueWidget->setProfileDataAvailable(false); d->colorSpaceSelector->TRCwidget->setProfileDataAvailable(false); } else if (currentModelStr == "RGBA") { QVector colorants = currentColorSpace()->profile()->getColorantsxyY(); QVector whitepoint = currentColorSpace()->profile()->getWhitePointxyY(); if (currentColorSpace()->profile()->hasColorants()){ d->colorSpaceSelector->TongueWidget->setRGBData(whitepoint, colorants); } else { colorants.fill(0.0); d->colorSpaceSelector->TongueWidget->setRGBData(whitepoint, colorants); } d->colorSpaceSelector->TongueWidget->setGamut(currentColorSpace()->gamutXYY()); estimatedTRC = currentColorSpace()->profile()->getEstimatedTRC(); QString estimatedCurve = " Estimated curve: "; QPolygonF redcurve; QPolygonF greencurve; QPolygonF bluecurve; if (currentColorSpace()->profile()->hasTRC()){ for (int i=0; i<=10; i++) { QVector linear(3); linear.fill(i*0.1); currentColorSpace()->profile()->linearizeFloatValue(linear); estimatedCurve = estimatedCurve + ", " + QString::number(linear[0]); QPointF tonepoint(linear[0],i*0.1); redcurve<colorSpaceSelector->TRCwidget->setRGBCurve(redcurve, greencurve, bluecurve); } else { QPolygonF curve = currentColorSpace()->estimatedTRCXYY(); redcurve << curve.at(0) << curve.at(1) << curve.at(2) << curve.at(3) << curve.at(4); greencurve << curve.at(5) << curve.at(6) << curve.at(7) << curve.at(8) << curve.at(9); bluecurve << curve.at(10) << curve.at(11) << curve.at(12) << curve.at(13) << curve.at(14); d->colorSpaceSelector->TRCwidget->setRGBCurve(redcurve, greencurve, bluecurve); } if (estimatedTRC[0] == -1) { d->colorSpaceSelector->TRCwidget->setToolTip(""+whatissRGB+"
"+estimatedCurve+""); } else { d->colorSpaceSelector->TRCwidget->setToolTip(""+estimatedGamma + QString::number(estimatedTRC[0]) + "," + QString::number(estimatedTRC[1]) + "," + QString::number(estimatedTRC[2])+"
"+estimatedCurve+""); } } else if (currentModelStr == "GRAYA") { QVector whitepoint = currentColorSpace()->profile()->getWhitePointxyY(); d->colorSpaceSelector->TongueWidget->setGrayData(whitepoint); d->colorSpaceSelector->TongueWidget->setGamut(currentColorSpace()->gamutXYY()); estimatedTRC = currentColorSpace()->profile()->getEstimatedTRC(); QString estimatedCurve = " Estimated curve: "; QPolygonF tonecurve; if (currentColorSpace()->profile()->hasTRC()){ for (int i=0; i<=10; i++) { QVector linear(3); linear.fill(i*0.1); currentColorSpace()->profile()->linearizeFloatValue(linear); estimatedCurve = estimatedCurve + ", " + QString::number(linear[0]); QPointF tonepoint(linear[0],i*0.1); tonecurve<colorSpaceSelector->TRCwidget->setProfileDataAvailable(false); } d->colorSpaceSelector->TRCwidget->setGreyscaleCurve(tonecurve); if (estimatedTRC[0] == -1) { d->colorSpaceSelector->TRCwidget->setToolTip(""+whatissRGB+"
"+estimatedCurve+""); } else { d->colorSpaceSelector->TRCwidget->setToolTip(""+estimatedGamma + QString::number(estimatedTRC[0])+"
"+estimatedCurve+""); } } else if (currentModelStr == "CMYKA") { QVector whitepoint = currentColorSpace()->profile()->getWhitePointxyY(); d->colorSpaceSelector->TongueWidget->setCMYKData(whitepoint); d->colorSpaceSelector->TongueWidget->setGamut(currentColorSpace()->gamutXYY()); QString estimatedCurve = " Estimated curve: "; QPolygonF tonecurve; QPolygonF cyancurve; QPolygonF magentacurve; QPolygonF yellowcurve; if (currentColorSpace()->profile()->hasTRC()){ for (int i=0; i<=10; i++) { QVector linear(3); linear.fill(i*0.1); currentColorSpace()->profile()->linearizeFloatValue(linear); estimatedCurve = estimatedCurve + ", " + QString::number(linear[0]); QPointF tonepoint(linear[0],i*0.1); tonecurve<colorSpaceSelector->TRCwidget->setGreyscaleCurve(tonecurve); } else { QPolygonF curve = currentColorSpace()->estimatedTRCXYY(); cyancurve << curve.at(0) << curve.at(1) << curve.at(2) << curve.at(3) << curve.at(4); magentacurve << curve.at(5) << curve.at(6) << curve.at(7) << curve.at(8) << curve.at(9); yellowcurve << curve.at(10) << curve.at(11) << curve.at(12) << curve.at(13) << curve.at(14); tonecurve << curve.at(15) << curve.at(16) << curve.at(17) << curve.at(18) << curve.at(19); d->colorSpaceSelector->TRCwidget->setCMYKCurve(cyancurve, magentacurve, yellowcurve, tonecurve); } d->colorSpaceSelector->TRCwidget->setToolTip(i18nc("@info:tooltip","Estimated Gamma cannot be retrieved for CMYK.")); } else if (currentModelStr == "XYZA") { QString estimatedCurve = " Estimated curve: "; estimatedTRC = currentColorSpace()->profile()->getEstimatedTRC(); QPolygonF tonecurve; if (currentColorSpace()->profile()->hasTRC()){ for (int i=0; i<=10; i++) { QVector linear(3); linear.fill(i*0.1); currentColorSpace()->profile()->linearizeFloatValue(linear); estimatedCurve = estimatedCurve + ", " + QString::number(linear[0]); QPointF tonepoint(linear[0],i*0.1); tonecurve<colorSpaceSelector->TRCwidget->setGreyscaleCurve(tonecurve); } else { d->colorSpaceSelector->TRCwidget->setProfileDataAvailable(false); } QVector whitepoint = currentColorSpace()->profile()->getWhitePointxyY(); d->colorSpaceSelector->TongueWidget->setXYZData(whitepoint); d->colorSpaceSelector->TongueWidget->setGamut(currentColorSpace()->gamutXYY()); d->colorSpaceSelector->TRCwidget->setToolTip(""+estimatedGamma + QString::number(estimatedTRC[0])+"< br />"+estimatedCurve+""); } else if (currentModelStr == "LABA") { estimatedTRC = currentColorSpace()->profile()->getEstimatedTRC(); QString estimatedCurve = " Estimated curve: "; QPolygonF tonecurve; if (currentColorSpace()->profile()->hasTRC()){ for (int i=0; i<=10; i++) { QVector linear(3); linear.fill(i*0.1); currentColorSpace()->profile()->linearizeFloatValue(linear); estimatedCurve = estimatedCurve + ", " + QString::number(linear[0]); QPointF tonepoint(linear[0],i*0.1); tonecurve<colorSpaceSelector->TRCwidget->setGreyscaleCurve(tonecurve); } else { d->colorSpaceSelector->TRCwidget->setProfileDataAvailable(false); } QVector whitepoint = currentColorSpace()->profile()->getWhitePointxyY(); d->colorSpaceSelector->TongueWidget->setLABData(whitepoint); d->colorSpaceSelector->TongueWidget->setGamut(currentColorSpace()->gamutXYY()); d->colorSpaceSelector->TRCwidget->setToolTip(""+i18nc("@info:tooltip","This is assumed to be the L * TRC. ")+"
"+estimatedCurve+""); } else if (currentModelStr == "YCbCrA") { QVector whitepoint = currentColorSpace()->profile()->getWhitePointxyY(); d->colorSpaceSelector->TongueWidget->setYCbCrData(whitepoint); QString estimatedCurve = " Estimated curve: "; QPolygonF tonecurve; if (currentColorSpace()->profile()->hasTRC()){ for (int i=0; i<=10; i++) { QVector linear(3); linear.fill(i*0.1); currentColorSpace()->profile()->linearizeFloatValue(linear); estimatedCurve = estimatedCurve + ", " + QString::number(linear[0]); QPointF tonepoint(linear[0],i*0.1); tonecurve<colorSpaceSelector->TRCwidget->setGreyscaleCurve(tonecurve); } else { d->colorSpaceSelector->TRCwidget->setProfileDataAvailable(false); } d->colorSpaceSelector->TongueWidget->setGamut(currentColorSpace()->gamutXYY()); d->colorSpaceSelector->TRCwidget->setToolTip(i18nc("@info:tooltip","Estimated Gamma cannot be retrieved for YCrCb.")); } d->colorSpaceSelector->textProfileDescription->clear(); if (profileList.isEmpty()==false) { d->colorSpaceSelector->textProfileDescription->append("

"+i18nc("About ","About ") + currentColorSpace()->name() + "/" + profileName + "

"); d->colorSpaceSelector->textProfileDescription->append("

"+ i18nc("ICC profile version","ICC Version: ") + QString::number(currentColorSpace()->profile()->version()) + "

"); //d->colorSpaceSelector->textProfileDescription->append("

"+ i18nc("Who made the profile?","Manufacturer: ") + currentColorSpace()->profile()->manufacturer() + "

"); //This would work if people actually wrote the manufacturer into the manufacturer fiedl... d->colorSpaceSelector->textProfileDescription->append("

"+ i18nc("What is the copyright? These are from embedded strings from the icc profile, so they default to english.","Copyright: ") + currentColorSpace()->profile()->copyright() + "

"); } else { d->colorSpaceSelector->textProfileDescription->append("

" + profileName + "

"); } if (currentModelStr == "RGBA") { d->colorSpaceSelector->textProfileDescription->append("

"+i18nc("If the selected model is RGB", "RGB (Red, Green, Blue), is the color model used by screens and other light-based media.
" "RGB is an additive color model: adding colors together makes them brighter. This color " "model is the most extensive of all color models, and is recommended as a model for painting," "that you can later convert to other spaces. RGB is also the recommended colorspace for HDR editing.")+"

"); } else if (currentModelStr == "CMYKA") { d->colorSpaceSelector->textProfileDescription->append("

"+i18nc("If the selected model is CMYK", "CMYK (Cyan, Magenta, Yellow, Key), " "is the model used by printers and other ink-based media.
" "CMYK is a subtractive model, meaning that adding colors together will turn them darker. Because of CMYK " "profiles being very specific per printer, it is recommended to work in RGB space, and then later convert " "to a CMYK profile, preferably one delivered by your printer.
" "CMYK is not recommended for painting." "Unfortunately, Krita cannot retrieve colorants or the TRC for this space.")+"

"); } else if (currentModelStr == "XYZA") { d->colorSpaceSelector->textProfileDescription->append("

"+i18nc("If the selected model is XYZ", "CIE XYZ" "is the space determined by the CIE as the space that encompasses all other colors, and used to " "convert colors between profiles. XYZ is an additive color model, meaning that adding colors together " "makes them brighter. XYZ is not recommended for painting, but can be useful to encode in. The Tone Response " "Curve is assumed to be linear.")+"

"); } else if (currentModelStr == "GRAYA") { d->colorSpaceSelector->textProfileDescription->append("

"+i18nc("If the selected model is Grayscale", "Grayscale only allows for " "gray values and transparent values. Grayscale images use half " "the memory and disk space compared to an RGB image of the same bit-depth.
" "Grayscale is useful for inking and grayscale images. In " "Krita, you can mix Grayscale and RGB layers in the same image.")+"

"); } else if (currentModelStr == "LABA") { d->colorSpaceSelector->textProfileDescription->append("

"+i18nc("If the selected model is LAB", "L*a*b. L stands for Lightness, " "the a and b components represent color channels.
" "L*a*b is a special model for color correction. It is based on human perception, meaning that it " "tries to encode the difference in lightness, red-green balance and yellow-blue balance. " "This makes it useful for color correction, but the vast majority of color maths in the blending " "modes do not work as expected here.
" "Similarly, Krita does not support HDR in LAB, meaning that HDR images converted to LAB lose color " "information. This colorspace is not recommended for painting, nor for export, " "but best as a space to do post-processing in. The TRC is assumed to be the L* TRC.")+"

"); } else if (currentModelStr == "YCbCrA") { d->colorSpaceSelector->textProfileDescription->append("

"+i18nc("If the selected model is YCbCr", "YCbCr (Luma, Blue Chroma, Red Chroma), is a " "model designed for video encoding. It is based on human perception, meaning that it tries to " "encode the difference in lightness, red-green balance and yellow-blue balance. Chroma in " "this case is then a word indicating a special type of saturation, in these cases the saturation " "of Red and Blue, of which the desaturated equivalents are Green and Yellow respectively. It " "is available to open up certain images correctly, but Krita does not currently ship a profile for " "this due to lack of open source ICC profiles for YCrCb.")+"

"); } QString currentDepthStr = d->colorSpaceSelector->cmbColorDepth->currentItem().id(); if (currentDepthStr == "U8") { d->colorSpaceSelector->textProfileDescription->append("

"+i18nc("When the selected Bitdepth is 8", "8 bit integer: The default number of colors per channel. Each channel will have 256 values available, " "leading to a total amount of colors of 256 to the power of the number of channels. Recommended to use for images intended for the web, " "or otherwise simple images.")+"

"); } else if (currentDepthStr == "U16") { d->colorSpaceSelector->textProfileDescription->append("

"+i18nc("When the selected Bitdepth is 16", "16 bit integer: Also known as 'deep color'. 16 bit is ideal for editing images with a linear TRC, large " "color space, or just when you need more precise color blending. This does take twice as much space on " "the RAM and hard-drive than any given 8 bit image of the same properties, and for some devices it " "takes much more processing power. We recommend watching the RAM usage of the file carefully, or " "otherwise use 8 bit if your computer slows down. Take care to disable conversion optimization " "when converting from 16 bit/channel to 8 bit/channel.")+"

"); } else if (currentDepthStr == "F16") { d->colorSpaceSelector->textProfileDescription->append("

"+i18nc("When the selected Bitdepth is 16 bit float", "16 bit floating point: Also known as 'Half Floating Point', and the standard in VFX industry images. " "16 bit float is ideal for editing images with a linear Tone Response Curve, large color space, or just when you need " "more precise color blending. It being floating point is an absolute requirement for Scene Referred " "(HDR) images. This does take twice as much space on the RAM and hard-drive than any given 8 bit image " "of the same properties, and for some devices it takes much more processing power. We recommend watching " "the RAM usage of the file carefully, or otherwise use 8 bit if your computer slows down.")+"

"); } else if (currentDepthStr == "F32") { d->colorSpaceSelector->textProfileDescription->append("

"+i18nc("When the selected Bitdepth is 32bit float", "32 bit float point: Also known as 'Full Floating Point'. 32 bit float is ideal for editing images " "with a linear TRC, large color space, or just when you need more precise color blending. It being " "floating point is an absolute requirement for Scene Referred (HDR) images. This does take four times " "as much space on the RAM and hard-drive than any given 8 bit image of the same properties, and for " "some devices it takes much more processing power. We recommend watching the RAM usage of the file " "carefully, or otherwise use 8 bit if your computer slows down.")+"

"); } else if (currentDepthStr == "F64") { d->colorSpaceSelector->textProfileDescription->append("

"+i18nc("When the selected Bitdepth is 64bit float, but this isn't actually available in Krita at the moment.",\ "64 bit float point: 64 bit float is as precise as it gets in current technology, and this depth is used " "most of the time for images that are generated or used as an input for software. It being floating point " "is an absolute requirement for Scene Referred (HDR) images. This does take eight times as much space on " "the RAM and hard-drive than any given 8 bit image of the same properties, and for some devices it takes " "much more processing power. We recommend watching the RAM usage of the file carefully, or otherwise use " "8 bit if your computer slows down.")+"

"); } if (profileList.isEmpty()==false) { QString possibleConversionIntents = "

"+i18n("The following conversion intents are possible: ")+"

    "; if (currentColorSpace()->profile()->supportsPerceptual()){ possibleConversionIntents += "
  • "+i18n("Perceptual")+"
  • "; } if (currentColorSpace()->profile()->supportsRelative()){ possibleConversionIntents += "
  • "+i18n("Relative Colorimetric")+"
  • "; } if (currentColorSpace()->profile()->supportsAbsolute()){ possibleConversionIntents += "
  • "+i18n("Absolute Colorimetric")+"
  • "; } if (currentColorSpace()->profile()->supportsSaturation()){ possibleConversionIntents += "
  • "+i18n("Saturation")+"
  • "; } possibleConversionIntents += "

"; d->colorSpaceSelector->textProfileDescription->append(possibleConversionIntents); } if (profileName.contains("-elle-")) { d->colorSpaceSelector->textProfileDescription->append("

"+i18nc("These are Elle Stone's notes on her profiles that we ship.", "

Extra notes on profiles by Elle Stone:

" "

Krita comes with a number of high quality profiles created by " "Elle Stone. This is a summary. Please check " "the full documentation as well.

")); if (profileName.contains("ACES-")) { d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.", "

Quoting Wikipedia, 'Academy Color Encoding System (ACES) is a color image " "encoding system proposed by the Academy of Motion Picture Arts and Sciences that will allow for " "a fully encompassing color accurate workflow, with 'seamless interchange of high quality motion " "picture images regardless of source'.

")); } if (profileName.contains("ACEScg-")) { d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.", "

The ACEScg color space is smaller than the ACES color space, but large enough to contain the 'Rec-2020 gamut " "and the DCI-P3 gamut', unlike the ACES color space it has no negative values and contains only few colors " "that fall just barely outside the area of real colors humans can see

")); } if (profileName.contains("ClayRGB-")) { d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.", "

To avoid possible copyright infringement issues, I used 'ClayRGB' (following ArgyllCMS) as the base name " "for these profiles. As used below, 'Compatible with Adobe RGB 1998' is terminology suggested in the preamble " "to the AdobeRGB 1998 color space specifications.

" "The Adobe RGB 1998 color gamut covers a higher " "percentage of real-world cyans, greens, and yellow-greens than sRGB, but still doesn't include all printable " "cyans, greens, yellow-greens, especially when printing using today's high-end, wider gamut, ink jet printers. " "BetaRGB (not included in the profile pack) and Rec.2020 are better matches for the color gamuts of today's " "wide gamut printers.

" "The Adobe RGB 1998 color gamut is a reasonable approximation to some of today's " "high-end wide gamut monitors.

")); } if (profileName.contains("AllColorsRGB-")) { d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.", "

This profile's color gamut is roughly the same size and shape as the ACES color space gamut, " "and like the ACES color space, AllColorsRGB holds all possible real colors. But AllColorsRGB " "actually has a slightly larger color gamut (to capture some fringe colors that barely qualify " "as real when viewed by the standard observer) and uses the D50 white point.

" "Just like the ACES color space, AllColorsRGB holds a high percentage of imaginary colors. See the Completely " "" "Painless Programmer's Guide to XYZ, RGB, ICC, xyY, and TRCs for more information about imaginary " "colors.

" "There is no particular reason why anyone would want to use this profile " "for editing, unless one needs to make sure your color space really does hold all " "possible real colors.

")); } if (profileName.contains("CIERGB-")) { d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.", "

This profile is included mostly for its historical significance. " "It's the color space that was used in the original color matching experiments " "that led to the creation of the XYZ reference color space.

" "The ASTM E white point " "is probably the right E white point to use when making the CIERGB color space profile. " "It's not clear to me what the correct CIERGB primaries really are. " "Lindbloom gives one set. The LCMS version 1 tutorial gives a different set. " "Experts in the field contend that the real primaries " "should be calculated from the spectral wavelengths, so I did.

")); } if (profileName.contains("IdentityRGB-")) { d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.", "

The IdentityRGB working space is included in the profile pack because it's a mathematically " "obvious way to include all possible visible colors, though it has a higher percentage of " "imaginary colors than the ACES and AllColorsRGB color spaces. I cannot think of any reason " "why you'd ever want to actually edit images in the IdentityRGB working space.

")); } if (profileName.contains("LargeRGB-")) { d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.", "

To avoid possible copyright infringement issues, I used 'LargeRGB' (following RawTherapee) " "as the base name for these profiles.

" "Kodak designed the RIMM/ROMM (ProPhotoRGB) color " "gamut to include all printable and most real world colors. It includes some imaginary colors " "and excludes some of the real world blues and violet blues that can be captured by digital " "cameras. It also excludes some very saturated 'camera-captured' yellows as interpreted by " "some (and probably many) camera matrix input profiles.

" "The ProPhotoRGB primaries are " "hard-coded into Adobe products such as Lightroom and the Dng-DCP camera 'profiles'. However, " "other than being large enough to hold a lot of colors, ProPhotoRGB has no particular merit " "as an RGB working space. Personally I recommend the Rec.2020 or ACEScg profiles over " "ProPhotoRGB. But if you have an already well-established workflow using ProPhotoRGB, you " "might find a shift to another RGB working space a little odd, at least at first, and so you " "have to weight the pros and cons of changing your workflow.

")); } if (profileName.contains("Rec2020-")) { d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.", "

Rec.2020 is the up-and-coming replacement for the thoroughly outdated sRGB color space. As of " "June 2015, very few (if any) display devices (and certainly no affordable display devices) can " "display all of Rec.2020. However, display technology is closing in on Rec.2020, movies are " "already being made for Rec.2020, and various cameras offer support for Rec.2020. And in the " "digital darkroom Rec.2020 is much more suitable as a general RGB working space than the " "exceedingly small sRGB color space.

")); } if (profileName.contains("sRGB-")) { d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.", "

Hewlett-Packard and Microsoft designed sRGB to match the color gamut of consumer-grade CRTs " "from the 1990s. sRGB is the standard color space for the world wide web and is still the best " "choice for exporting images to the internet.

" "The sRGB color gamut was a good match to " "calibrated decent quality CRTs. But sRGB is not a good match to many consumer-grade LCD monitors, " "which often cannot display the more saturated sRGB blues and magentas (the good news: as technology " "progresses, wider gamuts are trickling down to consumer grade monitors).

" "Printer color gamuts can easily exceed the sRGB color gamut in cyans, greens, and yellow-greens. Colors from interpolated " "camera raw files also often exceed the sRGB color gamut.

" "As a very relevant aside, using perceptual " "intent when converting to sRGB does not magically makes otherwise out of gamut colors fit inside the " "sRGB color gamut! The standard sRGB color space (along with all the other the RGB profiles provided " "in my profile pack) is a matrix profile, and matrix profiles don't have perceptual intent tables.

")); } if (profileName.contains("WideRGB-")) { d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.", "

To avoid possible copyright infringement issues, I used 'WideRGB' as the base name for these profiles.

" "WideGamutRGB was designed by Adobe to be a wide gamut color space that uses spectral colors " "as its primaries. Pascale's primary values produce a profile that matches old V2 Widegamut profiles " "from Adobe and Canon. It is an interesting color space, but shortly after its introduction, Adobe " "switched their emphasis to the ProPhotoRGB color space.

")); } if (profileName.contains("Gray-")) { d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.", "

These profiles are for use with RGB images that have been converted to monotone gray (black and white). " "The main reason to convert from RGB to Gray is to save the file space needed to encode the image. " "Google places a premium on fast-loading web pages, and images are one of the slower-loading elements " "of a web page. So converting black and white images to Grayscale images does save some kilobytes. " " For grayscale images uploaded to the internet, convert the image to the V2 Gray profile with the sRGB TRC.

")); } if (profileName.contains("-g10")) { d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.", "

The profiles that end in '-g10.icc' are linear gamma (gamma=1.0, 'linear light', etc) profiles and " "should only be used when editing at high bit depths (16-bit floating point, 16-bit integer, 32-bit " "floating point, 32-bit integer). Many editing operations produce better results in linear gamma color " "spaces.

")); } if (profileName.contains("-labl")) { d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.", "

The profiles that end in '-labl.icc' have perceptually uniform TRCs. A few editing operations really " "should be done on perceptually uniform RGB. Make sure you use the V4 versions for editing high bit depth " "images.

")); } if (profileName.contains("-srgbtrc") || profileName.contains("-g22") || profileName.contains("-g18") || profileName.contains("-rec709")) { d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.", "

The profiles that end in '-srgbtrc.icc', '-g22.icc', and '-rec709.icc' have approximately but not exactly " "perceptually uniform TRCs. ProPhotoRGB's gamma=1.8 TRC is not quite as close to being perceptually uniform.

")); } if (d->colorSpaceSelector->cmbColorDepth->currentItem().id()=="U8") { d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.", "

When editing 8-bit images, you should use a profile with a small color gamut and an approximately or " "exactly perceptually uniform TRC. Of the profiles supplied in my profile pack, only the sRGB and AdobeRGB1998 " "(ClayRGB) color spaces are small enough for 8-bit editing. Even with the AdobeRGB1998 color space you need to " "be careful to not cause posterization. And of course you cannot use the linear gamma versions of these profiles " "for 8-bit editing.

")); } if (profileName.contains("-V4-")) { d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.", "

Use V4 profiles for editing images using high bit depth image editors that use LCMS as the Color Management Module. " "This includes Krita, digiKam/showFoto, and GIMP 2.9.

")); } if (profileName.contains("-V2-")) { d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.", "

Use V2 profiles for exporting finished images to be uploaded to the web or for use with imaging software that " "cannot read V4 profiles.

")); } } d->colorSpaceSelector->textProfileDescription->moveCursor(QTextCursor::Start); } QString KisAdvancedColorSpaceSelector::nameWhitePoint(QVector whitePoint) { QString name=(QString::number(whitePoint[0]) + ", " + QString::number(whitePoint[1], 'f', 4)); //A (0.451170, 0.40594) (2856K)(tungsten) if ((whitePoint[0]>0.451170-0.005 && whitePoint[0]<0.451170 + 0.005) && (whitePoint[1]>0.40594-0.005 && whitePoint[1]<0.40594 + 0.005)){ name="A"; return name; } //B (0.34980, 0.35270) (4874K) (Direct Sunlight at noon)(obsolete) - //C (0.31039, 0.31905) (6774K) (avarage/north sky daylight)(obsolete) + //C (0.31039, 0.31905) (6774K) (average/north sky daylight)(obsolete) //D50 (0.34773, 0.35952) (5003K) (Horizon Light, default color of white paper, ICC profile standard illuminant) if ((whitePoint[0]>0.34773-0.005 && whitePoint[0]<0.34773 + 0.005) && (whitePoint[1]>0.35952-0.005 && whitePoint[1]<0.35952 + 0.005)){ name="D50"; return name; } //D55 (0.33411, 0.34877) (5503K) (Mid-morning / Mid-afternoon Daylight) if ((whitePoint[0]>0.33411-0.001 && whitePoint[0]<0.33411 + 0.001) && (whitePoint[1]>0.34877-0.005 && whitePoint[1]<0.34877 + 0.005)){ name="D55"; return name; } //D60 (0.3217, 0.3378) (~6000K) (ACES colorspace default) if ((whitePoint[0]>0.3217-0.001 && whitePoint[0]<0.3217 + 0.001) && (whitePoint[1]>0.3378-0.005 && whitePoint[1]<0.3378 + 0.005)){ name="D60"; return name; } //D65 (0.31382, 0.33100) (6504K) (Noon Daylight, default for computer and tv screens, sRGB default) //Elle's are old school with 0.3127 and 0.3289 if ((whitePoint[0]>0.31382-0.002 && whitePoint[0]<0.31382 + 0.002) && (whitePoint[1]>0.33100-0.005 && whitePoint[1]<0.33100 + 0.002)){ name="D65"; return name; } //D75 (0.29968, 0.31740) (7504K) (North sky Daylight) if ((whitePoint[0]>0.29968-0.001 && whitePoint[0]<0.29968 + 0.001) && (whitePoint[1]>0.31740-0.005 && whitePoint[1]<0.31740 + 0.005)){ name="D75"; return name; } //E (1/3, 1/3) (5454K) (Equal Energy. CIERGB default) if ((whitePoint[0]>(1.0/3.0)-0.001 && whitePoint[0]<(1.0/3.0) + 0.001) && (whitePoint[1]>(1.0/3.0)-0.001 && whitePoint[1]<(1.0/3.0) + 0.001)){ name="E"; return name; } //The F series seems to sorta overlap with the D series, so I'll just leave them in comment here.// //F1 (0.31811, 0.33559) (6430K) (Daylight Fluorescent) //F2 (0.37925, 0.36733) (4230K) (Cool White Fluorescent) //F3 (0.41761, 0.38324) (3450K) (White Fluorescent) //F4 (0.44920, 0.39074) (2940K) (Warm White Fluorescent) //F5 (0.31975, 0.34246) (6350K) (Daylight Fluorescent) //F6 (0.38660, 0.37847) (4150K) (Lite White Fluorescent) //F7 (0.31569, 0.32960) (6500K) (D65 simulator, Daylight simulator) //F8 (0.34902, 0.35939) (5000K) (D50 simulator) //F9 (0.37829, 0.37045) (4150K) (Cool White Deluxe Fluorescent) //F10 (0.35090, 0.35444) (5000K) (Philips TL85, Ultralume 50) //F11 (0.38541, 0.37123) (4000K) (Philips TL84, Ultralume 40) //F12 (0.44256, 0.39717) (3000K) (Philips TL83, Ultralume 30) return name; } const KoColorSpace* KisAdvancedColorSpaceSelector::currentColorSpace() { QString check = ""; if (d->colorSpaceSelector->lstProfile->currentItem()) { check = d->colorSpaceSelector->lstProfile->currentItem()->text(); } else if (d->colorSpaceSelector->lstProfile->item(0)) { check = d->colorSpaceSelector->lstProfile->item(0)->text(); } return KoColorSpaceRegistry::instance()->colorSpace(d->colorSpaceSelector->cmbColorModels->currentItem().id(), d->colorSpaceSelector->cmbColorDepth->currentItem().id(), check); } void KisAdvancedColorSpaceSelector::setCurrentColorModel(const KoID& id) { d->colorSpaceSelector->cmbColorModels->setCurrent(id); fillLstProfiles(); fillCmbDepths(id); } void KisAdvancedColorSpaceSelector::setCurrentColorDepth(const KoID& id) { d->colorSpaceSelector->cmbColorDepth->setCurrent(id); fillLstProfiles(); } void KisAdvancedColorSpaceSelector::setCurrentProfile(const QString& name) { QList Items= d->colorSpaceSelector->lstProfile->findItems(name, Qt::MatchStartsWith); d->colorSpaceSelector->lstProfile->setCurrentItem(Items.at(0)); } void KisAdvancedColorSpaceSelector::setCurrentColorSpace(const KoColorSpace* colorSpace) { if (!colorSpace) { return; } setCurrentColorModel(colorSpace->colorModelId()); setCurrentColorDepth(colorSpace->colorDepthId()); setCurrentProfile(colorSpace->profile()->name()); } void KisAdvancedColorSpaceSelector::colorSpaceChanged() { bool valid = d->colorSpaceSelector->lstProfile->count() != 0; emit(selectionChanged(valid)); if (valid) { emit colorSpaceChanged(currentColorSpace()); } } void KisAdvancedColorSpaceSelector::installProfile() { KoFileDialog dialog(this, KoFileDialog::OpenFiles, "OpenDocumentICC"); dialog.setCaption(i18n("Install Color Profiles")); dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::HomeLocation)); dialog.setMimeTypeFilters(QStringList() << "application/vnd.iccprofile", "application/vnd.iccprofile"); QStringList profileNames = dialog.filenames(); KoColorSpaceEngine *iccEngine = KoColorSpaceEngineRegistry::instance()->get("icc"); Q_ASSERT(iccEngine); QString saveLocation = KoResourcePaths::saveLocation("icc_profiles"); Q_FOREACH (const QString &profileName, profileNames) { QUrl file(profileName); if (!QFile::copy(profileName, saveLocation + file.fileName())) { dbgKrita << "Could not install profile!"; return; } iccEngine->addProfile(saveLocation + file.fileName()); } fillLstProfiles(); } diff --git a/libs/ui/widgets/kis_cmb_composite.cc b/libs/ui/widgets/kis_cmb_composite.cc index 353e262533..61625d26c0 100644 --- a/libs/ui/widgets/kis_cmb_composite.cc +++ b/libs/ui/widgets/kis_cmb_composite.cc @@ -1,482 +1,491 @@ /* * kis_cmb_composite.cc - part of KImageShop/Krayon/Krita * * Copyright (c) 2004 Boudewijn Rempt (boud@valdyas.org) * Copyright (c) 2011 Silvio Heinrich * * 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_cmb_composite.h" #include #include #include "kis_composite_ops_model.h" #include "kis_categorized_item_delegate.h" #include ////////////////////////////////////////////////////////////////////////////////////////// // ---- KisCompositeOpListWidget ------------------------------------------------------ // KisCompositeOpListWidget::KisCompositeOpListWidget(QWidget* parent): KisCategorizedListView(parent), - m_model(new KisSortedCompositeOpListModel(this)) + m_model(new KisSortedCompositeOpListModel(false, this)) { setModel(m_model); setItemDelegate(new KisCategorizedItemDelegate(this)); } KisCompositeOpListWidget::~KisCompositeOpListWidget() { } KoID KisCompositeOpListWidget::selectedCompositeOp() const { KoID op; if (m_model->entryAt(op, currentIndex())) { return op; } return KoCompositeOpRegistry::instance().getDefaultCompositeOp(); } ////////////////////////////////////////////////////////////////////////////////////////// // ---- KisCompositeOpComboBox -------------------------------------------------------- // -KisCompositeOpComboBox::KisCompositeOpComboBox(QWidget* parent): - KisSqueezedComboBox(parent), - m_model(new KisSortedCompositeOpListModel(this)), - m_allowToHidePopup(true) +KisCompositeOpComboBox::KisCompositeOpComboBox(QWidget* parent) + : KisCompositeOpComboBox(false, parent) +{ +} + +KisCompositeOpComboBox::KisCompositeOpComboBox(bool limitToLayerStyles, QWidget* parent) + : KisSqueezedComboBox(parent), + m_model(new KisSortedCompositeOpListModel(limitToLayerStyles, this)), + m_allowToHidePopup(true) { m_view = new KisCategorizedListView(); m_view->setCompositeBoxControl(true); setMaxVisibleItems(100); setSizeAdjustPolicy(AdjustToContents); m_view->setResizeMode(QListView::Adjust); setToolTip(i18n("Blending Mode")); setModel(m_model); setView(m_view); setItemDelegate(new KisCategorizedItemDelegate(this)); connect(m_view, SIGNAL(sigCategoryToggled(QModelIndex,bool)), SLOT(slotCategoryToggled(QModelIndex,bool))); connect(m_view, SIGNAL(sigEntryChecked(QModelIndex)), SLOT(slotEntryChecked(QModelIndex))); selectCompositeOp(KoCompositeOpRegistry::instance().getDefaultCompositeOp()); +} + +KisCompositeOpComboBox::~KisCompositeOpComboBox() +{ + delete m_view; +} + +QList KisCompositeOpComboBox::createBlendmodeActions() +{ + QList actions; KisAction *action = 0; // // Cycle through blending modes // // Shift + + (plus) or – (minus) // KisAction *action = new KisAction(i18n("Next Blending Mode"), this); // action->setDefaultShortcut(QKeySequence(Qt::SHIFT + Qt::ALT + Qt::Key_Plus)); // connect(action, SIGNAL(triggered()), SLOT(slotNextBlendingMode())); // m_actions << action; // action = new KisAction(i18n("Previous Blending Mode"), this); // action->setDefaultShortcut(QKeySequence(Qt::SHIFT + Qt::ALT + Qt::Key_Minus)); // connect(action, SIGNAL(triggered()), SLOT(slotPreviousBlendingMode())); // m_actions << action; // Normal // Shift + Alt + N action = new KisAction(i18n("Select Normal Blending Mode"), this); action->setDefaultShortcut(QKeySequence(Qt::SHIFT + Qt::ALT + Qt::Key_N)); connect(action, SIGNAL(triggered()), SLOT(slotNormal())); - m_actions << action; + actions << action; // Dissolve // Shift + Alt + I action = new KisAction(i18n("Select Dissolve Blending Mode"), this); action->setDefaultShortcut(QKeySequence(Qt::SHIFT + Qt::ALT + Qt::Key_I)); connect(action, SIGNAL(triggered()), SLOT(slotDissolve())); - m_actions << action; + actions << action; // Behind (Brush tool only) // Shift + Alt + Q action = new KisAction(i18n("Select Behind Blending Mode"), this); action->setDefaultShortcut(QKeySequence(Qt::SHIFT + Qt::ALT + Qt::Key_Q)); connect(action, SIGNAL(triggered()), SLOT(slotBehind())); - m_actions << action; + actions << action; // Clear (Brush tool only) // Shift + Alt + R action = new KisAction(i18n("Select Clear Blending Mode"), this); action->setDefaultShortcut(QKeySequence(Qt::SHIFT + Qt::ALT + Qt::Key_R)); connect(action, SIGNAL(triggered()), SLOT(slotClear())); - m_actions << action; + actions << action; // Darken // Shift + Alt + K action = new KisAction(i18n("Select Darken Blending Mode"), this); action->setDefaultShortcut(QKeySequence(Qt::SHIFT + Qt::ALT + Qt::Key_K)); connect(action, SIGNAL(triggered()), SLOT(slotDarken())); - m_actions << action; + actions << action; // Multiply // Shift + Alt + M action = new KisAction(i18n("Select Multiply Blending Mode"), this); action->setDefaultShortcut(QKeySequence(Qt::SHIFT + Qt::ALT + Qt::Key_M)); connect(action, SIGNAL(triggered()), SLOT(slotMultiply())); - m_actions << action; + actions << action; // Color Burn // Shift + Alt + B action = new KisAction(i18n("Select Color Burn Blending Mode"), this); action->setDefaultShortcut(QKeySequence(Qt::SHIFT + Qt::ALT + Qt::Key_B)); connect(action, SIGNAL(triggered()), SLOT(slotColorBurn())); - m_actions << action; + actions << action; // Linear Burn // Shift + Alt + A action = new KisAction(i18n("Select Linear Burn Blending Mode"), this); action->setDefaultShortcut(QKeySequence(Qt::SHIFT + Qt::ALT + Qt::Key_A)); connect(action, SIGNAL(triggered()), SLOT(slotLinearBurn())); - m_actions << action; + actions << action; // Lighten // Shift + Alt + G action = new KisAction(i18n("Select Lighten Blending Mode"), this); action->setDefaultShortcut(QKeySequence(Qt::SHIFT + Qt::ALT + Qt::Key_G)); connect(action, SIGNAL(triggered()), SLOT(slotLighten())); - m_actions << action; + actions << action; // Screen // Shift + Alt + S action = new KisAction(i18n("Select Screen Blending Mode"), this); action->setDefaultShortcut(QKeySequence(Qt::SHIFT + Qt::ALT + Qt::Key_S)); connect(action, SIGNAL(triggered()), SLOT(slotScreen())); - m_actions << action; + actions << action; // Color Dodge // Shift + Alt + D action = new KisAction(i18n("Select Color Dodge Blending Mode"), this); action->setDefaultShortcut(QKeySequence(Qt::SHIFT + Qt::ALT + Qt::Key_D)); connect(action, SIGNAL(triggered()), SLOT(slotColorDodge())); - m_actions << action; + actions << action; // Linear Dodge // Shift + Alt + W action = new KisAction(i18n("Select Linear Dodge Blending Mode"), this); action->setDefaultShortcut(QKeySequence(Qt::SHIFT + Qt::ALT + Qt::Key_W)); connect(action, SIGNAL(triggered()), SLOT(slotLinearDodge())); - m_actions << action; + actions << action; // Overlay // Shift + Alt + O action = new KisAction(i18n("Select Overlay Blending Mode"), this); action->setDefaultShortcut(QKeySequence(Qt::SHIFT + Qt::ALT + Qt::Key_O)); connect(action, SIGNAL(triggered()), SLOT(slotOverlay())); - m_actions << action; + actions << action; // Hard Overlay // Shift + Alt + P action = new KisAction(i18n("Select Hard Overlay Blending Mode"), this); action->setDefaultShortcut(QKeySequence(Qt::SHIFT + Qt::ALT + Qt::Key_P)); connect(action, SIGNAL(triggered()), SLOT(slotHardOverlay())); - m_actions << action; + actions << action; // Soft Light // Shift + Alt + F action = new KisAction(i18n("Select Soft Light Blending Mode"), this); action->setDefaultShortcut(QKeySequence(Qt::SHIFT + Qt::ALT + Qt::Key_F)); connect(action, SIGNAL(triggered()), SLOT(slotSoftLight())); - m_actions << action; + actions << action; // Hard Light // Shift + Alt + H action = new KisAction(i18n("Select Hard Light Blending Mode"), this); action->setDefaultShortcut(QKeySequence(Qt::SHIFT + Qt::ALT + Qt::Key_H)); connect(action, SIGNAL(triggered()), SLOT(slotHardLight())); - m_actions << action; + actions << action; // Vivid Light // Shift + Alt + V action = new KisAction(i18n("Select Vivid Light Blending Mode"), this); action->setDefaultShortcut(QKeySequence(Qt::SHIFT + Qt::ALT + Qt::Key_V)); connect(action, SIGNAL(triggered()), SLOT(slotVividLight())); - m_actions << action; + actions << action; // Linear Light // Shift + Alt + J action = new KisAction(i18n("Select Linear Light Blending Mode"), this); action->setDefaultShortcut(QKeySequence(Qt::SHIFT + Qt::ALT + Qt::Key_J)); connect(action, SIGNAL(triggered()), SLOT(slotLinearLight())); - m_actions << action; + actions << action; // Pin Light // Shift + Alt + Z action = new KisAction(i18n("Select Pin Light Blending Mode"), this); action->setDefaultShortcut(QKeySequence(Qt::SHIFT + Qt::ALT + Qt::Key_Z)); connect(action, SIGNAL(triggered()), SLOT(slotPinLight())); - m_actions << action; + actions << action; // Hard Mix // Shift + Alt + L action = new KisAction(i18n("Select Hard Mix Blending Mode"), this); action->setDefaultShortcut(QKeySequence(Qt::SHIFT + Qt::ALT + Qt::Key_L)); connect(action, SIGNAL(triggered()), SLOT(slotHardMix())); - m_actions << action; + actions << action; // Difference // Shift + Alt + E action = new KisAction(i18n("Select Difference Blending Mode"), this); action->setDefaultShortcut(QKeySequence(Qt::SHIFT + Qt::ALT + Qt::Key_E)); connect(action, SIGNAL(triggered()), SLOT(slotDifference())); - m_actions << action; + actions << action; // Exclusion // Shift + Alt + X action = new KisAction(i18n("Select Exclusion Blending Mode"), this); action->setDefaultShortcut(QKeySequence(Qt::SHIFT + Qt::ALT + Qt::Key_X)); connect(action, SIGNAL(triggered()), SLOT(slotExclusion())); - m_actions << action; + actions << action; // Hue // Shift + Alt + U action = new KisAction(i18n("Select Hue Blending Mode"), this); action->setDefaultShortcut(QKeySequence(Qt::SHIFT + Qt::ALT + Qt::Key_U)); connect(action, SIGNAL(triggered()), SLOT(slotHue())); - m_actions << action; + actions << action; // Saturation // Shift + Alt + T action = new KisAction(i18n("Select Saturation Blending Mode"), this); action->setDefaultShortcut(QKeySequence(Qt::SHIFT + Qt::ALT + Qt::Key_T)); connect(action, SIGNAL(triggered()), SLOT(slotSaturation())); - m_actions << action; + actions << action; // Color // Shift + Alt + C action = new KisAction(i18n("Select Color Blending Mode"), this); action->setDefaultShortcut(QKeySequence(Qt::SHIFT + Qt::ALT + Qt::Key_C)); connect(action, SIGNAL(triggered()), SLOT(slotColor())); - m_actions << action; + actions << action; // Luminosity // Shift + Alt + Y action = new KisAction(i18n("Select Luminosity Blending Mode"), this); action->setDefaultShortcut(QKeySequence(Qt::SHIFT + Qt::ALT + Qt::Key_Y)); connect(action, SIGNAL(triggered()), SLOT(slotLuminosity())); - m_actions << action; - + actions << action; -} - -KisCompositeOpComboBox::~KisCompositeOpComboBox() -{ - delete m_view; + return actions; } void KisCompositeOpComboBox::validate(const KoColorSpace *cs) { m_model->validate(cs); } void KisCompositeOpComboBox::selectCompositeOp(const KoID &op) { QModelIndex index = m_model->indexOf(op); setCurrentIndex(index.row()); } KoID KisCompositeOpComboBox::selectedCompositeOp() const { KoID op; if (m_model->entryAt(op, m_model->index(currentIndex(), 0))) { return op; } return KoCompositeOpRegistry::instance().getDefaultCompositeOp(); } - -QList KisCompositeOpComboBox::blendmodeActions() const -{ - return m_actions; -} - void KisCompositeOpComboBox::slotCategoryToggled(const QModelIndex& index, bool toggled) { Q_UNUSED(index); Q_UNUSED(toggled); //NOTE: this will (should) fit the size of the // popup widget to the view // don't know if this is expected behaviour // on all supported platforms. // There is nothing written about this in the docs. showPopup(); } void KisCompositeOpComboBox::slotEntryChecked(const QModelIndex& index) { Q_UNUSED(index); m_allowToHidePopup = false; } void KisCompositeOpComboBox::hidePopup() { if (m_allowToHidePopup) { QComboBox::hidePopup(); } else { QComboBox::showPopup(); } m_allowToHidePopup = true; } void KisCompositeOpComboBox::slotNextBlendingMode() { if (currentIndex() < count()) { setCurrentIndex(currentIndex() + 1); } } void KisCompositeOpComboBox::slotPreviousBlendingMode() { if (currentIndex() > 0) { setCurrentIndex(currentIndex() - 1); } } void KisCompositeOpComboBox::slotNormal() { selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_OVER)); } void KisCompositeOpComboBox::slotDissolve() { selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_DISSOLVE)); } void KisCompositeOpComboBox::slotBehind() { selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_BEHIND)); } void KisCompositeOpComboBox::slotClear() { selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_CLEAR)); } void KisCompositeOpComboBox::slotDarken() { selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_DARKEN)); } void KisCompositeOpComboBox::slotMultiply() { selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_MULT)); } void KisCompositeOpComboBox::slotColorBurn() { selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_BURN)); } void KisCompositeOpComboBox::slotLinearBurn() { selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_LINEAR_BURN)); } void KisCompositeOpComboBox::slotLighten() { selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_LIGHTEN)); } void KisCompositeOpComboBox::slotScreen() { selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_SCREEN)); } void KisCompositeOpComboBox::slotColorDodge() { selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_DODGE)); } void KisCompositeOpComboBox::slotLinearDodge() { selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_LINEAR_DODGE)); } void KisCompositeOpComboBox::slotOverlay() { selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_OVERLAY)); } void KisCompositeOpComboBox::slotHardOverlay() { selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_HARD_OVERLAY)); } void KisCompositeOpComboBox::slotSoftLight() { selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_SOFT_LIGHT_PHOTOSHOP)); } void KisCompositeOpComboBox::slotHardLight() { selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_HARD_LIGHT)); } void KisCompositeOpComboBox::slotVividLight() { selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_VIVID_LIGHT)); } void KisCompositeOpComboBox::slotLinearLight() { selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_LINEAR_LIGHT)); } void KisCompositeOpComboBox::slotPinLight() { selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_PIN_LIGHT)); } void KisCompositeOpComboBox::slotHardMix() { selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_HARD_MIX_PHOTOSHOP)); } void KisCompositeOpComboBox::slotDifference() { selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_DIFF)); } void KisCompositeOpComboBox::slotExclusion() { selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_EXCLUSION)); } void KisCompositeOpComboBox::slotHue() { selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_HUE)); } void KisCompositeOpComboBox::slotSaturation() { selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_SATURATION)); } void KisCompositeOpComboBox::slotColor() { selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_COLOR)); } void KisCompositeOpComboBox::slotLuminosity() { selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_LUMINIZE)); } + +KisLayerStyleCompositeOpComboBox::KisLayerStyleCompositeOpComboBox(QWidget* parent) + : KisCompositeOpComboBox(true, parent) +{ +} diff --git a/libs/ui/widgets/kis_cmb_composite.h b/libs/ui/widgets/kis_cmb_composite.h index 6a23567d63..9eeb99f135 100644 --- a/libs/ui/widgets/kis_cmb_composite.h +++ b/libs/ui/widgets/kis_cmb_composite.h @@ -1,105 +1,111 @@ /* * widgets/kis_cmb_composite.h - part of KImageShop/Krayon/Krita * * Copyright (c) 2004 Boudewijn Rempt (boud@valdyas.org) * Copyright (c) 2011 Silvio Heinrich * * 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_COMPOSITEOP_WIDGETS_H_ #define KIS_COMPOSITEOP_WIDGETS_H_ #include #include #include #include "kis_categorized_list_view.h" class KoID; class KoColorSpace; class KisSortedCompositeOpListModel; class KisAction; class KRITAUI_EXPORT KisCompositeOpListWidget: public KisCategorizedListView { public: - KisCompositeOpListWidget(QWidget* parent=0); - ~KisCompositeOpListWidget() override; + KisCompositeOpListWidget(QWidget* parent = 0); + ~KisCompositeOpListWidget() override; KoID selectedCompositeOp() const; private: KisSortedCompositeOpListModel *m_model; }; class KRITAUI_EXPORT KisCompositeOpComboBox: public KisSqueezedComboBox { Q_OBJECT public: - KisCompositeOpComboBox(QWidget* parent=0); + KisCompositeOpComboBox(QWidget* parent = 0); + KisCompositeOpComboBox(bool limitToLayerStyles, QWidget* parent = 0); ~KisCompositeOpComboBox() override; void hidePopup() override; void validate(const KoColorSpace *cs); void selectCompositeOp(const KoID &op); KoID selectedCompositeOp() const; - QList blendmodeActions() const; + QList createBlendmodeActions(); private Q_SLOTS: void slotCategoryToggled(const QModelIndex& index, bool toggled); void slotEntryChecked(const QModelIndex& index); void slotNextBlendingMode(); void slotPreviousBlendingMode(); void slotNormal(); void slotDissolve(); void slotBehind(); void slotClear(); void slotDarken(); void slotMultiply(); void slotColorBurn(); void slotLinearBurn(); void slotLighten(); void slotScreen(); void slotColorDodge(); void slotLinearDodge(); void slotOverlay(); void slotHardOverlay(); void slotSoftLight(); void slotHardLight(); void slotVividLight(); void slotLinearLight(); void slotPinLight(); void slotHardMix(); void slotDifference(); void slotExclusion(); void slotHue(); void slotSaturation(); void slotColor(); void slotLuminosity(); private: KisSortedCompositeOpListModel *m_model; KisCategorizedListView *m_view; bool m_allowToHidePopup; - QList m_actions; +}; + +class KRITAUI_EXPORT KisLayerStyleCompositeOpComboBox: public KisCompositeOpComboBox +{ +public: + KisLayerStyleCompositeOpComboBox(QWidget* parent = 0); }; #endif // KIS_COMPOSITEOP_WIDGETS_H_ diff --git a/libs/ui/widgets/kis_curve_widget.cpp b/libs/ui/widgets/kis_curve_widget.cpp index 1c5292d21f..286f74cb0a 100644 --- a/libs/ui/widgets/kis_curve_widget.cpp +++ b/libs/ui/widgets/kis_curve_widget.cpp @@ -1,552 +1,552 @@ /* * 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. */ // C++ includes. #include #include // Qt includes. #include #include #include #include #include #include #include #include #include #include #include #include #include #include // KDE includes. #include #include #include #include #include // Local includes. #include "widgets/kis_curve_widget.h" #define bounds(x,a,b) (xb ? b :x)) #define MOUSE_AWAY_THRES 15 #define POINT_AREA 1E-4 #define CURVE_AREA 1E-4 #include "kis_curve_widget_p.h" KisCurveWidget::KisCurveWidget(QWidget *parent, Qt::WindowFlags f) : QWidget(parent, f), d(new KisCurveWidget::Private(this)) { setObjectName("KisCurveWidget"); d->m_grab_point_index = -1; d->m_readOnlyMode = false; d->m_guideVisible = false; d->m_pixmapDirty = true; d->m_pixmapCache = 0; d->setState(ST_NORMAL); d->m_intIn = 0; d->m_intOut = 0; connect(&d->m_modifiedSignalsCompressor, SIGNAL(timeout()), SLOT(notifyModified())); connect(this, SIGNAL(compressorShouldEmitModified()), SLOT(slotCompressorShouldEmitModified())); setMouseTracking(true); setAutoFillBackground(false); setAttribute(Qt::WA_OpaquePaintEvent); setMinimumSize(150, 50); setMaximumSize(250, 250); setFocusPolicy(Qt::StrongFocus); } KisCurveWidget::~KisCurveWidget() { delete d->m_pixmapCache; delete d; } void KisCurveWidget::setupInOutControls(QSpinBox *in, QSpinBox *out, int inMin, int inMax, int outMin, int outMax) { dropInOutControls(); d->m_intIn = in; d->m_intOut = out; if (!d->m_intIn || !d->m_intOut) return; d->m_inMin = inMin; d->m_inMax = inMax; d->m_outMin = outMin; d->m_outMax = outMax; int realInMin = qMin(inMin, inMax); // tilt elevation has range (90, 0), which QSpinBox can't handle int realInMax = qMax(inMin, inMax); d->m_intIn->setRange(realInMin, realInMax); d->m_intOut->setRange(d->m_outMin, d->m_outMax); connect(d->m_intIn, SIGNAL(valueChanged(int)), this, SLOT(inOutChanged(int)), Qt::UniqueConnection); connect(d->m_intOut, SIGNAL(valueChanged(int)), this, SLOT(inOutChanged(int)), Qt::UniqueConnection); d->syncIOControls(); } void KisCurveWidget::dropInOutControls() { if (!d->m_intIn || !d->m_intOut) return; disconnect(d->m_intIn, SIGNAL(valueChanged(int)), this, SLOT(inOutChanged(int))); disconnect(d->m_intOut, SIGNAL(valueChanged(int)), this, SLOT(inOutChanged(int))); d->m_intIn = d->m_intOut = 0; } void KisCurveWidget::inOutChanged(int) { QPointF pt; Q_ASSERT(d->m_grab_point_index >= 0); pt.setX(d->io2sp(d->m_intIn->value(), d->m_inMin, d->m_inMax)); pt.setY(d->io2sp(d->m_intOut->value(), d->m_outMin, d->m_outMax)); bool newPoint = false; if (d->jumpOverExistingPoints(pt, d->m_grab_point_index)) { newPoint = true; d->m_curve.setPoint(d->m_grab_point_index, pt); d->m_grab_point_index = d->m_curve.points().indexOf(pt); emit pointSelectedChanged(); } else { pt = d->m_curve.points()[d->m_grab_point_index]; } if (!newPoint) { // if there is a new Point, no point in rewriting values in spinboxes d->m_intIn->blockSignals(true); d->m_intOut->blockSignals(true); d->m_intIn->setValue(d->sp2io(pt.x(), d->m_inMin, d->m_inMax)); d->m_intOut->setValue(d->sp2io(pt.y(), d->m_outMin, d->m_outMax)); d->m_intIn->blockSignals(false); d->m_intOut->blockSignals(false); } d->setCurveModified(false); } void KisCurveWidget::reset(void) { d->m_grab_point_index = -1; emit pointSelectedChanged(); d->m_guideVisible = false; //remove total - 2 points. while (d->m_curve.points().count() - 2 ) { d->m_curve.removePoint(d->m_curve.points().count() - 2); } d->setCurveModified(); } void KisCurveWidget::setCurveGuide(const QColor & color) { d->m_guideVisible = true; d->m_colorGuide = color; } void KisCurveWidget::setPixmap(const QPixmap & pix) { d->m_pix = pix; d->m_pixmapDirty = true; d->setCurveRepaint(); } QPixmap KisCurveWidget::getPixmap() { return d->m_pix; } void KisCurveWidget::setBasePixmap(const QPixmap &pix) { d->m_pixmapBase = pix; } QPixmap KisCurveWidget::getBasePixmap() { return d->m_pixmapBase; } bool KisCurveWidget::pointSelected() const { return d->m_grab_point_index > 0 && d->m_grab_point_index < d->m_curve.points().count() - 1; } void KisCurveWidget::keyPressEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Delete || e->key() == Qt::Key_Backspace) { if (d->m_grab_point_index > 0 && d->m_grab_point_index < d->m_curve.points().count() - 1) { //x() find closest point to get focus afterwards double grab_point_x = d->m_curve.points()[d->m_grab_point_index].x(); int left_of_grab_point_index = d->m_grab_point_index - 1; int right_of_grab_point_index = d->m_grab_point_index + 1; int new_grab_point_index; if (fabs(d->m_curve.points()[left_of_grab_point_index].x() - grab_point_x) < fabs(d->m_curve.points()[right_of_grab_point_index].x() - grab_point_x)) { new_grab_point_index = left_of_grab_point_index; } else { new_grab_point_index = d->m_grab_point_index; } d->m_curve.removePoint(d->m_grab_point_index); d->m_grab_point_index = new_grab_point_index; emit pointSelectedChanged(); setCursor(Qt::ArrowCursor); d->setState(ST_NORMAL); } e->accept(); d->setCurveModified(); } else if (e->key() == Qt::Key_Escape && d->state() != ST_NORMAL) { d->m_curve.setPoint(d->m_grab_point_index, QPointF(d->m_grabOriginalX, d->m_grabOriginalY) ); setCursor(Qt::ArrowCursor); d->setState(ST_NORMAL); e->accept(); d->setCurveModified(); } else if ((e->key() == Qt::Key_A || e->key() == Qt::Key_Insert) && d->state() == ST_NORMAL) { /* FIXME: Lets user choose the hotkeys */ addPointInTheMiddle(); e->accept(); } else QWidget::keyPressEvent(e); } void KisCurveWidget::addPointInTheMiddle() { QPointF pt(0.5, d->m_curve.value(0.5)); if (!d->jumpOverExistingPoints(pt, -1)) return; d->m_grab_point_index = d->m_curve.addPoint(pt); emit pointSelectedChanged(); if (d->m_intIn) d->m_intIn->setFocus(Qt::TabFocusReason); d->setCurveModified(); } void KisCurveWidget::resizeEvent(QResizeEvent *e) { d->m_pixmapDirty = true; QWidget::resizeEvent(e); } void KisCurveWidget::paintEvent(QPaintEvent *) { int wWidth = width() - 1; int wHeight = height() - 1; QPainter p(this); // Antialiasing is not a good idea here, because // the grid will drift one pixel to any side due to rounding of int // FIXME: let's user tell the last word (in config) //p.setRenderHint(QPainter::Antialiasing); QPalette appPalette = QApplication::palette(); p.fillRect(rect(), appPalette.color(QPalette::Base)); // clear out previous paint call results // make the entire widget grayed out if it is disabled if (!this->isEnabled()) { p.setOpacity(0.2); } // draw background if (!d->m_pix.isNull()) { if (d->m_pixmapDirty || !d->m_pixmapCache) { delete d->m_pixmapCache; d->m_pixmapCache = new QPixmap(width(), height()); QPainter cachePainter(d->m_pixmapCache); cachePainter.scale(1.0*width() / d->m_pix.width(), 1.0*height() / d->m_pix.height()); cachePainter.drawPixmap(0, 0, d->m_pix); d->m_pixmapDirty = false; } p.drawPixmap(0, 0, *d->m_pixmapCache); } d->drawGrid(p, wWidth, wHeight); KisConfig cfg(true); if (cfg.antialiasCurves()) { p.setRenderHint(QPainter::Antialiasing); } // Draw curve. double curY; double normalizedX; int x; QPolygonF poly; p.setPen(QPen(appPalette.color(QPalette::Text), 2, Qt::SolidLine)); for (x = 0 ; x < wWidth ; x++) { normalizedX = double(x) / wWidth; curY = wHeight - d->m_curve.value(normalizedX) * wHeight; /** * Keep in mind that QLineF rounds doubles * to ints mathematically, not just rounds down * like in C */ poly.append(QPointF(x, curY)); } poly.append(QPointF(x, wHeight - d->m_curve.value(1.0) * wHeight)); p.drawPolyline(poly); QPainterPath fillCurvePath; QPolygonF fillPoly = poly; fillPoly.append(QPoint(rect().width(), rect().height())); fillPoly.append(QPointF(0,rect().height())); // add a couple points to the edges so it fills in below always QColor fillColor = appPalette.color(QPalette::Text); fillColor.setAlphaF(0.2); fillCurvePath.addPolygon(fillPoly); p.fillPath(fillCurvePath, fillColor); // Drawing curve handles. double curveX; double curveY; if (!d->m_readOnlyMode) { for (int i = 0; i < d->m_curve.points().count(); ++i) { curveX = d->m_curve.points().at(i).x(); curveY = d->m_curve.points().at(i).y(); - int handleSize = 12; // how big should control points be (diamater size) + int handleSize = 12; // how big should control points be (diameter size) if (i == d->m_grab_point_index) { // active point is slightly more "bold" p.setPen(QPen(appPalette.color(QPalette::Text), 4, Qt::SolidLine)); p.drawEllipse(QRectF(curveX * wWidth - (handleSize*0.5), wHeight - (handleSize*0.5) - curveY * wHeight, handleSize, handleSize)); } else { p.setPen(QPen(appPalette.color(QPalette::Text), 2, Qt::SolidLine)); p.drawEllipse(QRectF(curveX * wWidth - (handleSize*0.5), wHeight - (handleSize*0.5) - curveY * wHeight, handleSize, handleSize)); } } } // add border around widget to help contain everything QPainterPath widgetBoundsPath; widgetBoundsPath.addRect(rect()); p.strokePath(widgetBoundsPath, appPalette.color(QPalette::Text)); p.setOpacity(1.0); // reset to 1.0 in case we were drawing a disabled widget before } void KisCurveWidget::mousePressEvent(QMouseEvent * e) { if (d->m_readOnlyMode) return; if (e->button() != Qt::LeftButton) return; double x = e->pos().x() / (double)(width() - 1); double y = 1.0 - e->pos().y() / (double)(height() - 1); int closest_point_index = d->nearestPointInRange(QPointF(x, y), width(), height()); if (closest_point_index < 0) { QPointF newPoint(x, y); if (!d->jumpOverExistingPoints(newPoint, -1)) return; d->m_grab_point_index = d->m_curve.addPoint(newPoint); emit pointSelectedChanged(); } else { d->m_grab_point_index = closest_point_index; emit pointSelectedChanged(); } d->m_grabOriginalX = d->m_curve.points()[d->m_grab_point_index].x(); d->m_grabOriginalY = d->m_curve.points()[d->m_grab_point_index].y(); d->m_grabOffsetX = d->m_curve.points()[d->m_grab_point_index].x() - x; d->m_grabOffsetY = d->m_curve.points()[d->m_grab_point_index].y() - y; d->m_curve.setPoint(d->m_grab_point_index, QPointF(x + d->m_grabOffsetX, y + d->m_grabOffsetY)); d->m_draggedAwayPointIndex = -1; d->setState(ST_DRAG); d->setCurveModified(); } void KisCurveWidget::mouseReleaseEvent(QMouseEvent *e) { if (d->m_readOnlyMode) return; if (e->button() != Qt::LeftButton) return; setCursor(Qt::ArrowCursor); d->setState(ST_NORMAL); d->setCurveModified(); } void KisCurveWidget::mouseMoveEvent(QMouseEvent * e) { if (d->m_readOnlyMode) return; double x = e->pos().x() / (double)(width() - 1); double y = 1.0 - e->pos().y() / (double)(height() - 1); if (d->state() == ST_NORMAL) { // If no point is selected set the cursor shape if on top int nearestPointIndex = d->nearestPointInRange(QPointF(x, y), width(), height()); if (nearestPointIndex < 0) setCursor(Qt::ArrowCursor); else setCursor(Qt::CrossCursor); } else { // Else, drag the selected point bool crossedHoriz = e->pos().x() - width() > MOUSE_AWAY_THRES || e->pos().x() < -MOUSE_AWAY_THRES; bool crossedVert = e->pos().y() - height() > MOUSE_AWAY_THRES || e->pos().y() < -MOUSE_AWAY_THRES; bool removePoint = (crossedHoriz || crossedVert); if (!removePoint && d->m_draggedAwayPointIndex >= 0) { // point is no longer dragged away so reinsert it QPointF newPoint(d->m_draggedAwayPoint); d->m_grab_point_index = d->m_curve.addPoint(newPoint); d->m_draggedAwayPointIndex = -1; } if (removePoint && (d->m_draggedAwayPointIndex >= 0)) return; setCursor(Qt::CrossCursor); x += d->m_grabOffsetX; y += d->m_grabOffsetY; double leftX; double rightX; if (d->m_grab_point_index == 0) { leftX = 0.0; if (d->m_curve.points().count() > 1) rightX = d->m_curve.points()[d->m_grab_point_index + 1].x() - POINT_AREA; else rightX = 1.0; } else if (d->m_grab_point_index == d->m_curve.points().count() - 1) { leftX = d->m_curve.points()[d->m_grab_point_index - 1].x() + POINT_AREA; rightX = 1.0; } else { Q_ASSERT(d->m_grab_point_index > 0 && d->m_grab_point_index < d->m_curve.points().count() - 1); // the 1E-4 addition so we can grab the dot later. leftX = d->m_curve.points()[d->m_grab_point_index - 1].x() + POINT_AREA; rightX = d->m_curve.points()[d->m_grab_point_index + 1].x() - POINT_AREA; } x = bounds(x, leftX, rightX); y = bounds(y, 0., 1.); d->m_curve.setPoint(d->m_grab_point_index, QPointF(x, y)); if (removePoint && d->m_curve.points().count() > 2) { d->m_draggedAwayPoint = d->m_curve.points()[d->m_grab_point_index]; d->m_draggedAwayPointIndex = d->m_grab_point_index; d->m_curve.removePoint(d->m_grab_point_index); d->m_grab_point_index = bounds(d->m_grab_point_index, 0, d->m_curve.points().count() - 1); emit pointSelectedChanged(); } d->setCurveModified(); } } KisCubicCurve KisCurveWidget::curve() { return d->m_curve; } void KisCurveWidget::setCurve(KisCubicCurve inlist) { d->m_curve = inlist; d->m_grab_point_index = qBound(0, d->m_grab_point_index, d->m_curve.points().count() - 1); d->setCurveModified(); emit pointSelectedChanged(); } void KisCurveWidget::leaveEvent(QEvent *) { } void KisCurveWidget::notifyModified() { emit modified(); } void KisCurveWidget::slotCompressorShouldEmitModified() { d->m_modifiedSignalsCompressor.start(); } diff --git a/libs/ui/widgets/kis_paintop_presets_popup.h b/libs/ui/widgets/kis_paintop_presets_popup.h index fea64cc57d..0222ae9337 100644 --- a/libs/ui/widgets/kis_paintop_presets_popup.h +++ b/libs/ui/widgets/kis_paintop_presets_popup.h @@ -1,147 +1,147 @@ /* This file is part of the KDE project * Copyright (C) Boudewijn Rempt , (C) 2008 * Copyright (C) 2010 Lukáš Tvrdý * Copyright (C) 2011 Silvio Heinrich * * 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_PAINTOP_PRESETS_POPUP_H #define KIS_PAINTOP_PRESETS_POPUP_H #include #include #include #include #include #include "../kis_paint_ops_model.h" #include #include #include "widgets/kis_paintop_presets_popup.h" #include "kis_favorite_resource_manager.h" class QString; class KisCanvasResourceProvider; class KoResource; /** * Popup widget for presets with built-in functionality * for adding and removing presets. */ class KisPaintOpPresetsPopup : public QWidget { Q_OBJECT public: KisPaintOpPresetsPopup(KisCanvasResourceProvider * resourceProvider, KisFavoriteResourceManager* favoriteResourceManager, KisPresetSaveWidget* savePresetWidget, QWidget * parent = 0); ~KisPaintOpPresetsPopup() override; void setPaintOpSettingsWidget(QWidget * widget); ///Image for preset preview ///@return image cut out from the scratchpad QImage cutOutOverlay(); void setPaintOpList(const QList& list); void setCurrentPaintOpId(const QString & paintOpId); /// returns the internal ID for the paint op (brush engine) QString currentPaintOpId(); - ///fill the cutoutOverlay rect with the cotent of an image, used to get the image back when selecting a preset + ///fill the cutoutOverlay rect with the content of an image, used to get the image back when selecting a preset ///@param image image that will be used, should be image of an existing preset resource void setPresetImage(const QImage& image); void resizeEvent(QResizeEvent* ) override; bool detached() const; void updateViewSettings(); void currentPresetChanged(KisPaintOpPresetSP preset); KisPresetSaveWidget * saveDialog; // toggle the state when we are creating a brush from scratch void setCreatingBrushFromScratch(bool enable); protected: void contextMenuEvent(QContextMenuEvent *) override; void hideEvent(QHideEvent *) override; void showEvent(QShowEvent *) override; public Q_SLOTS: void switchDetached(bool show = true); void resourceSelected(KoResource* resource); void updateThemedIcons(); void slotUpdatePresetSettings(); void slotUpdateLodAvailability(); void slotRenameBrushActivated(); void slotRenameBrushDeactivated(); void slotSaveRenameCurrentBrush(); void slotCreateNewBrushPresetEngine(); Q_SIGNALS: void savePresetClicked(); void saveBrushPreset(); void defaultPresetClicked(); void paintopActivated(const QString& presetName); void signalResourceSelected(KoResource* resource); void reloadPresetClicked(); void dirtyPresetToggled(bool value); void eraserBrushSizeToggled(bool value); void eraserBrushOpacityToggled(bool value); void brushEditorShown(); void createPresetFromScratch(const QString& paintOpName); private Q_SLOTS: void slotSwitchScratchpad(bool visible); void slotResourceChanged(int key, const QVariant &value); void slotLodAvailabilityChanged(bool value); void slotLodThresholdChanged(qreal value); void slotSwitchShowEditor(bool visible); void slotUpdatePaintOpFilter(); void slotSwitchShowPresets(bool visible); void slotSaveBrushPreset(); void slotSaveNewBrushPreset(); /// we do not delete brushe presets, but blacklist them so they disappear from the interface void slotBlackListCurrentPreset(); private: struct Private; Private * const m_d; QString current_paintOpId; QList sortedBrushEnginesList; QMenu * newPresetBrushEnginesMenu; QList newBrushEngineOptions; void toggleBrushRenameUIActive(bool isRenaming); }; #endif diff --git a/libs/ui/widgets/kis_preset_live_preview_view.cpp b/libs/ui/widgets/kis_preset_live_preview_view.cpp index 33b97fc675..b719743ebf 100644 --- a/libs/ui/widgets/kis_preset_live_preview_view.cpp +++ b/libs/ui/widgets/kis_preset_live_preview_view.cpp @@ -1,381 +1,383 @@ /* * Copyright (c) 2017 Scott Petrovic * * 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 "kis_paintop_settings.h" #include #include #include "KisAsyncronousStrokeUpdateHelper.h" #include KisPresetLivePreviewView::KisPresetLivePreviewView(QWidget *parent) : QGraphicsView(parent), m_updateCompressor(100, KisSignalCompressor::FIRST_ACTIVE) { connect(&m_updateCompressor, SIGNAL(timeout()), SLOT(updateStroke())); } KisPresetLivePreviewView::~KisPresetLivePreviewView() { delete m_noPreviewText; delete m_brushPreviewScene; } void KisPresetLivePreviewView::setup() { // initializing to 0 helps check later if they actually have something in them m_noPreviewText = 0; m_sceneImageItem = 0; setHorizontalScrollBarPolicy ( Qt::ScrollBarAlwaysOff ); setVerticalScrollBarPolicy ( Qt::ScrollBarAlwaysOff ); // layer image needs to be big enough to get an entire stroke of data m_canvasSize.setWidth(this->width()); m_canvasSize.setHeight(this->height()); m_canvasCenterPoint.setX(m_canvasSize.width()*0.5); m_canvasCenterPoint.setY(m_canvasSize.height()*0.5); m_colorSpace = KoColorSpaceRegistry::instance()->rgb8(); m_image = new KisImage(0, m_canvasSize.width(), m_canvasSize.height(), m_colorSpace, "stroke sample image"); m_layer = new KisPaintLayer(m_image, "livePreviewStrokeSample", OPACITY_OPAQUE_U8, m_colorSpace); // set scene for the view m_brushPreviewScene = new QGraphicsScene(); setScene(m_brushPreviewScene); } void KisPresetLivePreviewView::setCurrentPreset(KisPaintOpPresetSP preset) { m_currentPreset = preset; } void KisPresetLivePreviewView::requestUpdateStroke() { m_updateCompressor.start(); } void KisPresetLivePreviewView::updateStroke() { // do not paint a stroke if we are any of these engines (they have some issue currently) if (m_currentPreset->paintOp().id() == "roundmarker" || m_currentPreset->paintOp().id() == "experimentbrush" || m_currentPreset->paintOp().id() == "duplicate") { paintBackground(); slotPreviewGenerationCompleted(); return; } if (!m_previewGenerationInProgress) { paintBackground(); setupAndPaintStroke(); } else { m_updateCompressor.start(); } } void KisPresetLivePreviewView::slotPreviewGenerationCompleted() { m_previewGenerationInProgress = false; QImage m_temp_image; m_temp_image = m_layer->paintDevice()->convertToQImage(0, m_image->bounds()); // only add the object once...then just update the pixmap so we can move the preview around if (!m_sceneImageItem) { m_sceneImageItem = m_brushPreviewScene->addPixmap(QPixmap::fromImage(m_temp_image)); } else { m_sceneImageItem->setPixmap(QPixmap::fromImage(m_temp_image)); } } void KisPresetLivePreviewView::paintBackground() { // clean up "no preview" text object if it exists. we will add it later if we need it if (m_noPreviewText) { this->scene()->removeItem(m_noPreviewText); m_noPreviewText = 0; } if (m_currentPreset->paintOp().id() == "colorsmudge" || m_currentPreset->paintOp().id() == "deformbrush" || m_currentPreset->paintOp().id() == "filter") { // easier to see deformations and smudging with alternating stripes in the background // paint the whole background with alternating stripes // filter engine may or may not show things depending on the filter...but it is better than nothing int grayStrips = 20; for (int i=0; i < grayStrips; i++ ) { float sectionPercent = 1.0 / (float)grayStrips; bool isAlternating = i % 2; KoColor fillColor(m_layer->paintDevice()->colorSpace()); if (isAlternating) { fillColor.fromQColor(QColor(80,80,80)); } else { fillColor.fromQColor(QColor(140,140,140)); } const QRect fillRect(m_layer->image()->width()*sectionPercent*i, 0, m_layer->image()->width()*(sectionPercent*i +sectionPercent), m_layer->image()->height()); m_layer->paintDevice()->fill(fillRect, fillColor); } m_paintColor = KoColor(Qt::white, m_colorSpace); } else if (m_currentPreset->paintOp().id() == "roundmarker" || m_currentPreset->paintOp().id() == "experimentbrush" || m_currentPreset->paintOp().id() == "duplicate" ) { // cases where we will not show a preview for now // roundbrush (quick) -- this isn't showing anything, disable showing preview // experimentbrush -- this creates artifacts that carry over to other previews and messes up their display // duplicate (clone) brush doesn't have a preview as it doesn't show anything) if(m_sceneImageItem) { this->scene()->removeItem(m_sceneImageItem); m_sceneImageItem = 0; } QFont font; font.setPixelSize(14); font.setBold(false); m_noPreviewText = this->scene()->addText(i18n("No Preview for this engine"),font); m_noPreviewText->setPos(50, this->height()/4); return; } else { // fill with gray first to clear out what existed from previous preview m_layer->paintDevice()->fill(m_image->bounds(), KoColor(palette().color(QPalette::Background) , m_colorSpace)); m_paintColor = KoColor(palette().color(QPalette::Text), m_colorSpace); } } class NotificationStroke : public QObject, public KisSimpleStrokeStrategy { Q_OBJECT public: NotificationStroke() { setClearsRedoOnStart(false); this->enableJob(JOB_INIT, true, KisStrokeJobData::BARRIER); this->enableJob(JOB_CANCEL, true, KisStrokeJobData::BARRIER); } void initStrokeCallback() { emit timeout(); } void cancelStrokeCallback() { emit cancelled(); } Q_SIGNALS: void timeout(); void cancelled(); }; void KisPresetLivePreviewView::setupAndPaintStroke() { // limit the brush stroke size. larger brush strokes just don't look good and are CPU intensive // we are making a proxy preset and setting it to the painter...otherwise setting the brush size of the original preset // will fire off signals that make this run in an infinite loop qreal originalPresetSize = m_currentPreset->settings()->paintOpSize(); qreal previewSize = qBound(3.0, m_currentPreset->settings()->paintOpSize(), 25.0 ); // constrain live preview brush size //Except for the sketchbrush where it determine sthe history. if (m_currentPreset->paintOp().id() == "sketchbrush" || m_currentPreset->paintOp().id() == "spraybrush") { previewSize = qMax(3.0, m_currentPreset->settings()->paintOpSize()); } KisPaintOpPresetSP proxy_preset = m_currentPreset->clone(); KisPaintOpSettingsSP settings = proxy_preset->settings(); settings->setPaintOpSize(previewSize); + int maxTextureSize = 200; int textureOffsetX = settings->getInt("Texture/Pattern/MaximumOffsetX")*2; int textureOffsetY = settings->getInt("Texture/Pattern/MaximumOffsetY")*2; double textureScale = settings->getDouble("Texture/Pattern/Scale"); if ( textureOffsetX*textureScale> maxTextureSize || textureOffsetY*textureScale > maxTextureSize) { int maxSize = qMax(textureOffsetX, textureOffsetY); double result = qreal(maxTextureSize) / maxSize; settings->setProperty("Texture/Pattern/Scale", result); } if (proxy_preset->paintOp().id() == "spraybrush") { QDomElement element; QDomDocument d; QString brushDefinition = settings->getString("brush_definition"); if (!brushDefinition.isEmpty()) { d.setContent(brushDefinition, false); element = d.firstChildElement("Brush"); KisBrushSP brush = KisBrush::fromXML(element); qreal width = brush->image().width(); qreal scale = brush->scale(); qreal diameterToBrushRatio = 1.0; qreal diameter = settings->getInt("Spray/diameter"); //hack, 1000 being the maximum possible brushsize. if (brush->filename().endsWith(".svg")) { diameterToBrushRatio = diameter/(1000.0*scale); scale = 25.0 / 1000.0; } else { if (width * scale > 25.0) { diameterToBrushRatio = diameter / (width * scale); scale = 25.0 / width; } } settings->setProperty("Spray/diameter", int(25.0 * diameterToBrushRatio)); brush->setScale(scale); d.clear(); element = d.createElement("Brush"); brush->toXML(d, element); d.appendChild(element); settings->setProperty("brush_definition", d.toString()); } } // Preset preview cannot display gradient color source: there is // no resource manager for KisResourcesSnapshot, therefore gradient is nullptr. // BUG: 385521 (Selecting "Gradient" in brush editor crashes krita) if (proxy_preset->paintOp().id() == "paintbrush") { QString colorSourceType = settings->getString("ColorSource/Type", "plain"); if (colorSourceType == "gradient") { settings->setProperty("ColorSource/Type", "plain"); } } proxy_preset->setSettings(settings); KisResourcesSnapshotSP resources = new KisResourcesSnapshot(m_image, m_layer); + resources->setOpacity(settings->paintOpOpacity()); resources->setBrush(proxy_preset); resources->setFGColorOverride(m_paintColor); KisFreehandStrokeInfo *strokeInfo = new KisFreehandStrokeInfo(); KisStrokeStrategy *stroke = new FreehandStrokeStrategy(resources, strokeInfo, kundo2_noi18n("temp_stroke")); KisStrokeId strokeId = m_image->startStroke(stroke); // paint the stroke. The sketchbrush gets a different shape than the others to show how it works if (proxy_preset->paintOp().id() == "sketchbrush" || proxy_preset->paintOp().id() == "curvebrush" || proxy_preset->paintOp().id() == "particlebrush") { qreal startX = m_canvasCenterPoint.x() - (this->width()*0.4); qreal endX = m_canvasCenterPoint.x() + (this->width()*0.4); qreal middle = m_canvasCenterPoint.y(); KisPaintInformation pointOne; pointOne.setPressure(0.0); pointOne.setPos(QPointF(startX, middle)); KisPaintInformation pointTwo; pointTwo.setPressure(0.0); pointTwo.setPos(QPointF(startX, middle)); int repeats = 8; for (int i = 0; i < repeats; i++) { pointOne.setPos(pointTwo.pos()); pointOne.setPressure(pointTwo.pressure()); pointTwo.setPressure((1.0/repeats)*(i+1)); qreal xPos = ((1.0/repeats) * (i+1) * (endX-startX) )+startX; pointTwo.setPos(QPointF(xPos, middle)); qreal offset = (this->height()/(repeats*1.5))*(i+1); qreal handleY = middle + offset; if (i%2 == 0) { handleY = middle - offset; } m_image->addJob(strokeId, new FreehandStrokeStrategy::Data(0, pointOne, QPointF(pointOne.pos().x(), handleY), QPointF(pointTwo.pos().x(), handleY), pointTwo)); m_image->addJob(strokeId, new KisAsyncronousStrokeUpdateHelper::UpdateData(true)); } } else { // paint an S curve m_curvePointPI1.setPos(QPointF(m_canvasCenterPoint.x() - (this->width()*0.45), m_canvasCenterPoint.y() + (this->height()*0.2))); m_curvePointPI1.setPressure(0.0); m_curvePointPI2.setPos(QPointF(m_canvasCenterPoint.x() + (this->width()*0.4), m_canvasCenterPoint.y() - (this->height()*0.2) )); m_curvePointPI2.setPressure(1.0); m_image->addJob(strokeId, new FreehandStrokeStrategy::Data(0, m_curvePointPI1, QPointF(m_canvasCenterPoint.x(), m_canvasCenterPoint.y()-this->height()), QPointF(m_canvasCenterPoint.x(), m_canvasCenterPoint.y()+this->height()), m_curvePointPI2)); m_image->addJob(strokeId, new KisAsyncronousStrokeUpdateHelper::UpdateData(true)); } m_image->endStroke(strokeId); m_previewGenerationInProgress = true; NotificationStroke *notificationStroke = new NotificationStroke(); connect(notificationStroke, SIGNAL(timeout()), SLOT(slotPreviewGenerationCompleted())); KisStrokeId notificationId = m_image->startStroke(notificationStroke); m_image->endStroke(notificationId); // TODO: if we don't have any regressions because of it until 4.2.8, then // just remove this code. // even though the brush is cloned, the proxy_preset still has some connection to the original preset which will mess brush sizing // we need to return brush size to normal.The normal brush sends out a lot of extra signals, so keeping the proxy for now //proxy_preset->settings()->setPaintOpSize(originalPresetSize); } #include "kis_preset_live_preview_view.moc" diff --git a/libs/widgets/KisDlgInternalColorSelector.cpp b/libs/widgets/KisDlgInternalColorSelector.cpp index 825aa4e77d..f228e43b6f 100644 --- a/libs/widgets/KisDlgInternalColorSelector.cpp +++ b/libs/widgets/KisDlgInternalColorSelector.cpp @@ -1,354 +1,354 @@ /* * Copyright (C) Wolthera van Hovell tot Westerflier , (C) 2016 * * 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 "KoColorSpaceRegistry.h" #include #include #include #include #include #include #include "kis_signal_compressor.h" #include "KoColorDisplayRendererInterface.h" #include "kis_spinbox_color_selector.h" #include "KisDlgInternalColorSelector.h" #include "ui_WdgDlgInternalColorSelector.h" #include "kis_config_notifier.h" #include "kis_color_input.h" #include "kis_icon_utils.h" #include "KisSqueezedComboBox.h" std::function KisDlgInternalColorSelector::s_screenColorPickerFactory = 0; struct KisDlgInternalColorSelector::Private { bool allowUpdates = true; KoColor currentColor; KoColor previousColor; KoColor sRGB = KoColor(KoColorSpaceRegistry::instance()->rgb8()); const KoColorSpace *currentColorSpace; bool lockUsedCS = false; bool chooseAlpha = false; KisSignalCompressor *compressColorChanges; const KoColorDisplayRendererInterface *displayRenderer; KisHexColorInput *hexColorInput = 0; KisPaletteModel *paletteModel = 0; KisPaletteListWidget *paletteChooser = 0; KisScreenColorPickerBase *screenColorPicker = 0; }; KisDlgInternalColorSelector::KisDlgInternalColorSelector(QWidget *parent, KoColor color, Config config, const QString &caption, const KoColorDisplayRendererInterface *displayRenderer) : QDialog(parent) , m_d(new Private) { setModal(config.modal); setFocusPolicy(Qt::ClickFocus); m_ui = new Ui_WdgDlgInternalColorSelector(); m_ui->setupUi(this); setWindowTitle(caption); m_d->currentColor = color; m_d->currentColorSpace = m_d->currentColor.colorSpace(); m_d->displayRenderer = displayRenderer; m_ui->spinboxselector->slotSetColor(color); connect(m_ui->spinboxselector, SIGNAL(sigNewColor(KoColor)), this, SLOT(slotColorUpdated(KoColor))); m_ui->visualSelector->slotSetColor(color); m_ui->visualSelector->setDisplayRenderer(displayRenderer); m_ui->visualSelector->setConfig(false, config.modal); if (config.visualColorSelector) { connect(m_ui->visualSelector, SIGNAL(sigNewColor(KoColor)), this, SLOT(slotColorUpdated(KoColor))); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), m_ui->visualSelector, SLOT(configurationChanged())); } else { m_ui->visualSelector->hide(); } m_d->paletteChooser = new KisPaletteListWidget(this); m_d->paletteModel = new KisPaletteModel(this); m_ui->bnPaletteChooser->setIcon(KisIconUtils::loadIcon("hi16-palette_library")); m_ui->paletteBox->setPaletteModel(m_d->paletteModel); m_ui->paletteBox->setDisplayRenderer(displayRenderer); m_ui->cmbNameList->setCompanionView(m_ui->paletteBox); connect(m_d->paletteChooser, SIGNAL(sigPaletteSelected(KoColorSet*)), this, SLOT(slotChangePalette(KoColorSet*))); connect(m_ui->cmbNameList, SIGNAL(sigColorSelected(KoColor)), SLOT(slotColorUpdated(KoColor))); - // For some bizare reason, the modal dialog doesn't like having the colorset set, so let's not. + // For some bizarre reason, the modal dialog doesn't like having the colorset set, so let's not. if (config.paletteBox) { //TODO: Add disable signal as well. Might be not necessary...? KConfigGroup cfg(KSharedConfig::openConfig()->group("")); QString paletteName = cfg.readEntry("internal_selector_active_color_set", QString()); KoResourceServer* rServer = KoResourceServerProvider::instance()->paletteServer(); KoColorSet *savedPal = rServer->resourceByName(paletteName); if (savedPal) { this->slotChangePalette(savedPal); } else { if (rServer->resources().count()) { savedPal = rServer->resources().first(); if (savedPal) { this->slotChangePalette(savedPal); } } } connect(m_ui->paletteBox, SIGNAL(sigColorSelected(KoColor)), this, SLOT(slotColorUpdated(KoColor))); m_ui->bnPaletteChooser->setPopupWidget(m_d->paletteChooser); } else { m_ui->paletteBox->setEnabled(false); m_ui->cmbNameList->setEnabled(false); m_ui->bnPaletteChooser->setEnabled(false); } if (config.prevNextButtons) { m_ui->currentColor->setColor(m_d->currentColor); m_ui->currentColor->setDisplayRenderer(displayRenderer); m_ui->previousColor->setColor(m_d->currentColor); m_ui->previousColor->setDisplayRenderer(displayRenderer); connect(m_ui->previousColor, SIGNAL(triggered(KoColorPatch*)), SLOT(slotSetColorFromPatch(KoColorPatch*))); } else { m_ui->currentColor->hide(); m_ui->previousColor->hide(); } if (config.hexInput) { m_d->sRGB.fromKoColor(m_d->currentColor); m_d->hexColorInput = new KisHexColorInput(this, &m_d->sRGB); m_d->hexColorInput->update(); connect(m_d->hexColorInput, SIGNAL(updated()), SLOT(slotSetColorFromHex())); m_ui->rightPane->addWidget(m_d->hexColorInput); m_d->hexColorInput->setToolTip(i18n("This is a hexcode input, for webcolors. It can only get colors in the sRGB space.")); } // KisScreenColorPicker is in the kritaui module, so dependency inversion is used to access it. m_ui->screenColorPickerWidget->setLayout(new QHBoxLayout(m_ui->screenColorPickerWidget)); if (s_screenColorPickerFactory) { m_d->screenColorPicker = s_screenColorPickerFactory(m_ui->screenColorPickerWidget); m_ui->screenColorPickerWidget->layout()->addWidget(m_d->screenColorPicker); if (config.screenColorPicker) { connect(m_d->screenColorPicker, SIGNAL(sigNewColorPicked(KoColor)),this, SLOT(slotColorUpdated(KoColor))); } else { m_d->screenColorPicker->hide(); } } m_d->compressColorChanges = new KisSignalCompressor(100 /* ms */, KisSignalCompressor::POSTPONE, this); connect(m_d->compressColorChanges, SIGNAL(timeout()), this, SLOT(endUpdateWithNewColor())); connect(m_ui->buttonBox, SIGNAL(accepted()), this, SLOT(accept()), Qt::UniqueConnection); connect(m_ui->buttonBox, SIGNAL(rejected()), this, SLOT(reject()), Qt::UniqueConnection); connect(this, SIGNAL(finished(int)), SLOT(slotFinishUp())); } KisDlgInternalColorSelector::~KisDlgInternalColorSelector() { delete m_ui; } void KisDlgInternalColorSelector::slotColorUpdated(KoColor newColor) { // not-so-nice solution: if someone calls this slot directly and that code was // triggered by our compressor signal, our compressor is technically the sender()! if (sender() == m_d->compressColorChanges) { return; } // Do not accept external updates while a color update emit is pending; // Note: Assumes external updates only come from parent(), a separate slot might be better if (m_d->allowUpdates || (QObject::sender() && QObject::sender() != this->parent())) { // Enforce palette colors KConfigGroup group(KSharedConfig::openConfig(), ""); if (group.readEntry("colorsettings/forcepalettecolors", false)) { newColor = m_ui->paletteBox->closestColor(newColor); } if (m_d->lockUsedCS){ newColor.convertTo(m_d->currentColorSpace); m_d->currentColor = newColor; } else { m_d->currentColor = newColor; } updateAllElements(QObject::sender()); } } void KisDlgInternalColorSelector::slotSetColorFromPatch(KoColorPatch *patch) { slotColorUpdated(patch->color()); } void KisDlgInternalColorSelector::colorSpaceChanged(const KoColorSpace *cs) { if (cs == m_d->currentColorSpace) { return; } m_d->currentColorSpace = KoColorSpaceRegistry::instance()->colorSpace(cs->colorModelId().id(), cs->colorDepthId().id(), cs->profile()); m_ui->spinboxselector->slotSetColorSpace(m_d->currentColorSpace); m_ui->visualSelector->slotsetColorSpace(m_d->currentColorSpace); } void KisDlgInternalColorSelector::lockUsedColorSpace(const KoColorSpace *cs) { colorSpaceChanged(cs); if (m_d->currentColor.colorSpace() != m_d->currentColorSpace) { m_d->currentColor.convertTo(m_d->currentColorSpace); } m_d->lockUsedCS = true; } void KisDlgInternalColorSelector::setDisplayRenderer(const KoColorDisplayRendererInterface *displayRenderer) { if (displayRenderer) { m_d->displayRenderer = displayRenderer; m_ui->visualSelector->setDisplayRenderer(displayRenderer); m_ui->currentColor->setDisplayRenderer(displayRenderer); m_ui->previousColor->setDisplayRenderer(displayRenderer); m_ui->paletteBox->setDisplayRenderer(displayRenderer); } else { m_d->displayRenderer = KoDumbColorDisplayRenderer::instance(); } } KoColor KisDlgInternalColorSelector::getModalColorDialog(const KoColor color, QWidget* parent, QString caption) { Config config = Config(); KisDlgInternalColorSelector dialog(parent, color, config, caption); dialog.setPreviousColor(color); dialog.exec(); return dialog.getCurrentColor(); } KoColor KisDlgInternalColorSelector::getCurrentColor() { return m_d->currentColor; } void KisDlgInternalColorSelector::chooseAlpha(bool chooseAlpha) { m_d->chooseAlpha = chooseAlpha; } void KisDlgInternalColorSelector::slotConfigurationChanged() { //m_d->canvas->displayColorConverter()-> //slotColorSpaceChanged(m_d->canvas->image()->colorSpace()); } void KisDlgInternalColorSelector::setPreviousColor(KoColor c) { m_d->previousColor = c; } void KisDlgInternalColorSelector::reject() { slotColorUpdated(m_d->previousColor); QDialog::reject(); } void KisDlgInternalColorSelector::updateAllElements(QObject *source) { //update everything!!! if (source != m_ui->spinboxselector) { m_ui->spinboxselector->slotSetColor(m_d->currentColor); } if (source != m_ui->visualSelector) { m_ui->visualSelector->slotSetColor(m_d->currentColor); } if (source != m_d->hexColorInput) { m_d->sRGB.fromKoColor(m_d->currentColor); m_d->hexColorInput->update(); } if (source != m_ui->paletteBox) { m_ui->paletteBox->selectClosestColor(m_d->currentColor); } m_ui->previousColor->setColor(m_d->previousColor); m_ui->currentColor->setColor(m_d->currentColor); if (source && source != this->parent()) { m_d->allowUpdates = false; m_d->compressColorChanges->start(); } if (m_d->screenColorPicker) { m_d->screenColorPicker->updateIcons(); } } void KisDlgInternalColorSelector::endUpdateWithNewColor() { emit signalForegroundColorChosen(m_d->currentColor); m_d->allowUpdates = true; } void KisDlgInternalColorSelector::focusInEvent(QFocusEvent *) { //setPreviousColor(); } void KisDlgInternalColorSelector::slotFinishUp() { setPreviousColor(m_d->currentColor); KConfigGroup cfg(KSharedConfig::openConfig()->group("")); if (m_d->paletteModel) { if (m_d->paletteModel->colorSet()) { cfg.writeEntry("internal_selector_active_color_set", m_d->paletteModel->colorSet()->name()); } } } void KisDlgInternalColorSelector::slotSetColorFromHex() { slotColorUpdated(m_d->sRGB); } void KisDlgInternalColorSelector::slotChangePalette(KoColorSet *set) { if (!set) { return; } m_d->paletteModel->setPalette(set); } void KisDlgInternalColorSelector::showEvent(QShowEvent *event) { updateAllElements(0); QDialog::showEvent(event); } diff --git a/libs/widgets/KoResourceItemChooserSync.h b/libs/widgets/KoResourceItemChooserSync.h index 7bfd213657..e85e112324 100644 --- a/libs/widgets/KoResourceItemChooserSync.h +++ b/libs/widgets/KoResourceItemChooserSync.h @@ -1,65 +1,65 @@ /* This file is part of the KDE project Copyright (c) 2014 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. */ #ifndef KORESOURCEITEMCHOOSERSYNC_H #define KORESOURCEITEMCHOOSERSYNC_H #include #include #include "kritawidgets_export.h" /** * KoResourceItemChooserSync is a singleton that syncs the size of entries in the * resource item choosers between different choosers * To use the syncing it has to be turned on in the KoResourceItemChooser */ class KRITAWIDGETS_EXPORT KoResourceItemChooserSync : public QObject { Q_OBJECT public: KoResourceItemChooserSync(); ~KoResourceItemChooserSync() override; static KoResourceItemChooserSync* instance(); /// Gets the base length /// @returns the base length of items int baseLength(); /// Set the base length - /// @param length base length for the items, will be clamped if ouside range + /// @param length base length for the items, will be clamped if outside range void setBaseLength(int length); Q_SIGNALS: /// Signal is emitted when the base length is changed and will trigger and update in /// the resource item choosers void baseLengthChanged(int length); private: KoResourceItemChooserSync(const KoResourceItemChooserSync&); KoResourceItemChooserSync operator=(const KoResourceItemChooserSync&); private: struct Private; const QScopedPointer d; }; #endif // KORESOURCEITEMCHOOSERSYNC_H diff --git a/libs/widgetutils/KisActionsSnapshot.h b/libs/widgetutils/KisActionsSnapshot.h index b938dc648b..68dfb52f45 100644 --- a/libs/widgetutils/KisActionsSnapshot.h +++ b/libs/widgetutils/KisActionsSnapshot.h @@ -1,62 +1,62 @@ /* * 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. */ #ifndef KISACTIONSSNAPSHOT_H #define KISACTIONSSNAPSHOT_H #include #include #include class QAction; class KActionCollection; /** * @brief The KisActionsSnapshot class */ class KRITAWIDGETUTILS_EXPORT KisActionsSnapshot { public: KisActionsSnapshot(); ~KisActionsSnapshot(); /** * @brief registers the action in the snapshot and sorts it into a proper * category. The action is *not* owned by the snapshot. * * @param name id string of the action * @param action the action itself */ void addAction(const QString &name, QAction *action); /** * Returns all action collections of the current snapshot * - * WARNING: the collections are owned by the shapshot! Don't destroy + * WARNING: the collections are owned by the snapshot! Don't destroy * the snapshot before you are done with the collections! */ QMap actionCollections(); private: struct Private; const QScopedPointer m_d; }; #endif // KISACTIONSSNAPSHOT_H diff --git a/libs/widgetutils/KisDialogStateSaver.cpp b/libs/widgetutils/KisDialogStateSaver.cpp index 1241f61345..b5d63f378c 100644 --- a/libs/widgetutils/KisDialogStateSaver.cpp +++ b/libs/widgetutils/KisDialogStateSaver.cpp @@ -1,184 +1,197 @@ /* * Copyright (c) 2019 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 "KisDialogStateSaver.h" #include #include #include #include #include #include #include #include #include +#include +#include #include "kis_int_parse_spin_box.h" #include "kis_double_parse_spin_box.h" #include "kis_double_parse_unit_spin_box.h" void KisDialogStateSaver::saveState(QWidget *parent, const QString &dialogName) { Q_ASSERT(parent); Q_ASSERT(!dialogName.isEmpty()); KConfigGroup group(KSharedConfig::openConfig(), dialogName); Q_FOREACH(QWidget *widget, parent->findChildren(QString())) { if (!widget->objectName().isEmpty() ) { if (qobject_cast(widget)) { group.writeEntry(widget->objectName(), qobject_cast(widget)->value()); } else if (qobject_cast(widget)) { group.writeEntry(widget->objectName(), qobject_cast(widget)->value()); } else if (qobject_cast(widget)) { // XXX: also save the unit group.writeEntry(widget->objectName(), qobject_cast(widget)->value()); } else if (qobject_cast(widget)) { group.writeEntry(widget->objectName(), qobject_cast(widget)->isChecked()); } else if (qobject_cast(widget)) { group.writeEntry(widget->objectName(), qobject_cast(widget)->currentIndex()); } else if (qobject_cast(widget)) { group.writeEntry(widget->objectName(), qobject_cast(widget)->text()); } else if (qobject_cast(widget)) { group.writeEntry(widget->objectName(), qobject_cast(widget)->value()); } else if (qobject_cast(widget)) { group.writeEntry(widget->objectName(), qobject_cast(widget)->value()); } else if (qobject_cast(widget)) { group.writeEntry(widget->objectName(), qobject_cast(widget)->value()); } + else if (qobject_cast(widget)) { + group.writeEntry(widget->objectName(), qobject_cast(widget)->isChecked()); + } else { - qWarning() << "Cannot save state for object" << widget; + //qWarning() << "Cannot save state for object" << widget; } } else { qWarning() << "Dialog" << dialogName << "has a widget without an objectname:" << widget; } } } void KisDialogStateSaver::restoreState(QWidget *parent, const QString &dialogName, const QMap &defaults) { Q_ASSERT(parent); Q_ASSERT(!dialogName.isEmpty()); KConfigGroup group( KSharedConfig::openConfig(), dialogName); Q_FOREACH(QWidget *widget, parent->findChildren(QString())) { if (!widget->objectName().isEmpty()) { QString widgetName = widget->objectName(); QVariant defaultValue; if (defaults.contains(widgetName)) { defaultValue = defaults[widgetName]; } if (qobject_cast(widget)) { if (defaultValue.isValid()) { qobject_cast(widget)->setValue(defaultValue.toInt()); } else { qobject_cast(widget)->setValue(group.readEntry(widgetName, qobject_cast(widget)->value())); } } else if (qobject_cast(widget)) { if (defaultValue.isValid()) { qobject_cast(widget)->setValue(defaultValue.toInt()); } else { qobject_cast(widget)->setValue(group.readEntry(widgetName, qobject_cast(widget)->value())); } } else if (qobject_cast(widget)) { if (defaultValue.isValid()) { qobject_cast(widget)->setValue(defaultValue.toInt()); } else { qobject_cast(widget)->setValue(group.readEntry(widgetName, qobject_cast(widget)->value())); } } else if (qobject_cast(widget)) { if (defaultValue.isValid()) { qobject_cast(widget)->setChecked(defaultValue.toBool()); } else { qobject_cast(widget)->setChecked(group.readEntry(widgetName, qobject_cast(widget)->isChecked())); } } else if (qobject_cast(widget)) { if (defaultValue.isValid()) { qobject_cast(widget)->setCurrentIndex(defaultValue.toInt()); } else { qobject_cast(widget)->setCurrentIndex(group.readEntry(widgetName, qobject_cast(widget)->currentIndex())); } } else if (qobject_cast(widget)) { if (defaultValue.isValid()) { qobject_cast(widget)->setText(defaultValue.toString()); } else { qobject_cast(widget)->setText(group.readEntry(widgetName, qobject_cast(widget)->text())); } } else if (qobject_cast(widget)) { if (defaultValue.isValid()) { qobject_cast(widget)->setValue(defaultValue.toInt()); } else { qobject_cast(widget)->setValue(group.readEntry(widgetName, qobject_cast(widget)->value())); } } else if (qobject_cast(widget)) { if (defaultValue.isValid()) { qobject_cast(widget)->setValue(defaultValue.toInt()); } else { qobject_cast(widget)->setValue(group.readEntry(widgetName, qobject_cast(widget)->value())); } } else if (qobject_cast(widget)) { if (defaultValue.isValid()) { qobject_cast(widget)->setValue(defaultValue.toDouble()); } else { qobject_cast(widget)->setValue(group.readEntry(widgetName, qobject_cast(widget)->value())); } - } + else if (qobject_cast(widget)) { + if (defaultValue.isValid()) { + qobject_cast(widget)->setChecked(defaultValue.toBool()); + } + else { + qobject_cast(widget)->setChecked(group.readEntry(widgetName, qobject_cast(widget)->isChecked())); + } + } + else { //qWarning() << "Cannot restore state for object" << widget; } } else { qWarning() << "Dialog" << dialogName << "has a widget without an object name:" << widget; } } } diff --git a/libs/widgetutils/KoGroupButton.h b/libs/widgetutils/KoGroupButton.h index 7519c4b471..fd31710391 100644 --- a/libs/widgetutils/KoGroupButton.h +++ b/libs/widgetutils/KoGroupButton.h @@ -1,71 +1,71 @@ /* This file is part of the KDE libraries Copyright (C) 2007 Aurélien Gâteau Copyright (C) 2012 Jean-Nicolas Artaud Copyright (C) 2012 Jarosław Staniek This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. 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 KOGROUPBUTTON_H #define KOGROUPBUTTON_H #include "kritawidgetutils_export.h" #include /** * A thin tool button which can be visually grouped with other buttons. * * The group can thus look like one solid bar: ( button1 | button2 | button3 ) * - * For groupping layout can be used. For exclusive checkable behaviour assign QButtonGroup on the buttons. + * For grouping layout can be used. For exclusive checkable behaviour assign QButtonGroup on the buttons. */ class KRITAWIDGETUTILS_EXPORT KoGroupButton : public KisHighlightedToolButton { Q_OBJECT Q_ENUMS( GroupPosition ) Q_PROPERTY( GroupPosition groupPosition READ groupPosition WRITE setGroupPosition ) public: /** * Position of the button within the button group what affects the appearance. */ enum GroupPosition { NoGroup, //!< No particular position, gives the button unchanged appearance GroupLeft, //!< The button is at the left of the group, so it would have rounded the left part GroupRight, //!< The button is at the right of the group, so it would have rounded the right part GroupCenter //!< The button is on the center of the group, so it would have separators on both sides }; explicit KoGroupButton(GroupPosition position, QWidget* parent = 0); /** * Creates button with no NoGroup position. */ explicit KoGroupButton(QWidget* parent = 0); ~KoGroupButton() override; void setGroupPosition(KoGroupButton::GroupPosition groupPosition); KoGroupButton::GroupPosition groupPosition() const; protected: void paintEvent(QPaintEvent* event) override; private: class Private; Private *const d; }; #endif /* KOGROUPBUTTON_H */ diff --git a/libs/widgetutils/KoProgressUpdater.cpp b/libs/widgetutils/KoProgressUpdater.cpp index 65822e0a48..9451f0d4b5 100644 --- a/libs/widgetutils/KoProgressUpdater.cpp +++ b/libs/widgetutils/KoProgressUpdater.cpp @@ -1,348 +1,348 @@ /* This file is part of the KDE project * Copyright (C) 2006-2007 Thomas Zander * Copyright (C) 2009 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 "KoProgressUpdater.h" #include #include #include #include #include "KoUpdaterPrivate_p.h" #include "KoUpdater.h" #include "KoProgressProxy.h" #include class Q_DECL_HIDDEN KoProgressUpdater::Private { public: Private(KoProgressUpdater *_q, KoProgressProxy *proxy, QPointer parentUpdater, Mode _mode) : q(_q) , parentProgressProxy(proxy) , parentUpdater(parentUpdater) , mode(_mode) , currentProgress(0) , updated(false) , updateGuiTimer(_q) , canceled(false) { } KoProgressUpdater *q; private: KoProgressProxy *parentProgressProxy; QPointer parentUpdater; public: Mode mode; int currentProgress = 0; bool isUndefinedState = false; bool updated; // is true whenever the progress needs to be recomputed QTimer updateGuiTimer; // fires regularly to update the progress bar widget QList > subtasks; bool canceled; int updateInterval = 250; // ms, 4 updates per second should be enough bool autoNestNames = false; QString taskName; int taskMax = -1; bool isStarted = false; void updateParentText(); void clearState(); KoProgressProxy* progressProxy() { return parentUpdater ? parentUpdater : parentProgressProxy; } }; // NOTE: do not make the KoProgressUpdater object part of the QObject // hierarchy. Do not make KoProgressProxy its parent (note that KoProgressProxy // is not necessarily castable to QObject ). This prevents proper functioning // of progress reporting in multi-threaded environments. KoProgressUpdater::KoProgressUpdater(KoProgressProxy *progressProxy, Mode mode) : d (new Private(this, progressProxy, 0, mode)) { KIS_ASSERT_RECOVER_RETURN(progressProxy); connect(&d->updateGuiTimer, SIGNAL(timeout()), SLOT(updateUi())); } KoProgressUpdater::KoProgressUpdater(QPointer updater) : d (new Private(this, 0, updater, Unthreaded)) { KIS_ASSERT_RECOVER_RETURN(updater); connect(&d->updateGuiTimer, SIGNAL(timeout()), SLOT(updateUi())); } KoProgressUpdater::~KoProgressUpdater() { if (d->progressProxy()) { d->progressProxy()->setRange(0, d->taskMax); d->progressProxy()->setValue(d->progressProxy()->maximum()); } // make sure to stop the timer to avoid accessing // the data we are going to delete right now d->updateGuiTimer.stop(); qDeleteAll(d->subtasks); d->subtasks.clear(); delete d; } void KoProgressUpdater::start(int range, const QString &text) { d->clearState(); d->taskName = text; d->taskMax = range - 1; d->isStarted = true; if (d->progressProxy()) { d->progressProxy()->setRange(0, d->taskMax); d->progressProxy()->setValue(0); d->updateParentText(); } d->updateGuiTimer.start(d->updateInterval); } QPointer KoProgressUpdater::startSubtask(int weight, const QString &name, bool isPersistent) { if (!d->isStarted) { // lazy initialization for intermediate proxies start(); } KoUpdaterPrivate *p = new KoUpdaterPrivate(this, weight, name, isPersistent); d->subtasks.append(p); connect(p, SIGNAL(sigUpdated()), SLOT(update())); QPointer updater = p->connectedUpdater(); if (!d->updateGuiTimer.isActive()) { // we maybe need to restart the timer if it was stopped in updateUi() cause // other sub-tasks created before this one finished already. d->updateGuiTimer.start(d->updateInterval); } d->updated = true; return updater; } void KoProgressUpdater::removePersistentSubtask(QPointer updater) { for (auto it = d->subtasks.begin(); it != d->subtasks.end();) { if ((*it)->connectedUpdater() != updater) { ++it; } else { KIS_SAFE_ASSERT_RECOVER_NOOP((*it)->isPersistent()); (*it)->deleteLater(); it = d->subtasks.erase(it); break; } } updateUi(); } void KoProgressUpdater::cancel() { Q_FOREACH (KoUpdaterPrivate *updater, d->subtasks) { updater->setProgress(100); updater->setInterrupted(true); } d->canceled = true; updateUi(); } void KoProgressUpdater::update() { d->updated = true; if (d->mode == Unthreaded) { qApp->processEvents(); } if (!d->updateGuiTimer.isActive()) { d->updateGuiTimer.start(d->updateInterval); } } void KoProgressUpdater::updateUi() { // This function runs in the app main thread. All the progress // updates arrive at the KoUpdaterPrivate instances through - // queued connections, so until we relinguish control to the + // queued connections, so until we relinquish control to the // event loop, the progress values cannot change, and that // won't happen until we return from this function (which is // triggered by a timer) /** * We shouldn't let progress updater to interfere the progress * reporting when it is not initialized. */ if (d->subtasks.isEmpty()) return; if (d->updated) { int totalProgress = 0; int totalWeight = 0; d->isUndefinedState = false; Q_FOREACH (QPointer updater, d->subtasks) { if (updater->interrupted()) { d->currentProgress = -1; break; } if (!updater->hasValidRange()) { totalWeight = 0; totalProgress = 0; d->isUndefinedState = true; break; } if (updater->isPersistent() && updater->isCompleted()) { continue; } const int progress = qBound(0, updater->progress(), 100); totalProgress += progress * updater->weight(); totalWeight += updater->weight(); } const int progressPercent = totalWeight > 0 ? totalProgress / totalWeight : -1; d->currentProgress = d->taskMax == 99 ? progressPercent : qRound(qreal(progressPercent) * d->taskMax / 99.0); d->updated = false; } if (d->progressProxy()) { if (!d->isUndefinedState) { d->progressProxy()->setRange(0, d->taskMax); if (d->currentProgress == -1) { d->currentProgress = d->progressProxy()->maximum(); } if (d->currentProgress >= d->progressProxy()->maximum()) { // we're done d->updateGuiTimer.stop(); d->clearState(); } else { d->progressProxy()->setValue(d->currentProgress); } } else { d->progressProxy()->setRange(0,0); d->progressProxy()->setValue(0); } d->updateParentText(); } } void KoProgressUpdater::Private::updateParentText() { if (!progressProxy()) return; QString actionName = taskName; if (autoNestNames) { Q_FOREACH (QPointer updater, subtasks) { if (updater->isPersistent() && updater->isCompleted()) { continue; } if (updater->progress() < 100) { const QString subTaskName = updater->mergedSubTaskName(); if (!subTaskName.isEmpty()) { if (actionName.isEmpty()) { actionName = subTaskName; } else { actionName = QString("%1: %2").arg(actionName).arg(subTaskName); } } break; } } progressProxy()->setAutoNestedName(actionName); } else { progressProxy()->setFormat(actionName); } } void KoProgressUpdater::Private::clearState() { for (auto it = subtasks.begin(); it != subtasks.end();) { if (!(*it)->isPersistent()) { (*it)->deleteLater(); it = subtasks.erase(it); } else { if ((*it)->interrupted()) { (*it)->setInterrupted(false); } ++it; } } progressProxy()->setRange(0, taskMax); progressProxy()->setValue(progressProxy()->maximum()); canceled = false; } bool KoProgressUpdater::interrupted() const { return d->canceled; } void KoProgressUpdater::setUpdateInterval(int ms) { d->updateInterval = ms; if (d->updateGuiTimer.isActive()) { d->updateGuiTimer.start(d->updateInterval); } } int KoProgressUpdater::updateInterval() const { return d->updateInterval; } void KoProgressUpdater::setAutoNestNames(bool value) { d->autoNestNames = value; update(); } bool KoProgressUpdater::autoNestNames() const { return d->autoNestNames; } diff --git a/libs/widgetutils/xmlgui/KisShortcutsEditor_p.h b/libs/widgetutils/xmlgui/KisShortcutsEditor_p.h index ec63653b57..084764444e 100644 --- a/libs/widgetutils/xmlgui/KisShortcutsEditor_p.h +++ b/libs/widgetutils/xmlgui/KisShortcutsEditor_p.h @@ -1,189 +1,189 @@ /** * Copyright (C) 1997 Nicolas Hadacek * Copyright (C) 1998 Matthias Ettrich * Copyright (C) 2001 Ellis Whitehead * Copyright (C) 2006 Hamish Rodda * Copyright (C) 2007 Roberto Raggi * Copyright (C) 2007 Andreas Hartmetz * Copyright (C) 2008 Michael Jansen * Copyright (c) 2015 Michael Abrahams * * 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 3 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 KISSHORTCUTSEDITOR_P #define KISSHORTCUTSEDITOR_P #include "KisShortcutsEditor.h" #include #include #include #include "KisShortcutsDialog_p.h" // NEEDED FOR KisShortcutsEditorPrivate #include "ui_KisShortcutsDialog.h" /// Declared at the end of the file - provides the basic storage unit for this class class KisShortcutsEditorItem; /** * @internal */ class KisShortcutsEditorPrivate { public: //! Represents the three hierarchies the dialog displays. enum hierarchyLevel {Root = 0, /* Base level node (Tools, Krita...) */ Program, /* We use this like "Path Tool, Default Tool," */ Action}; /* Individual actions */ KisShortcutsEditorPrivate(KisShortcutsEditor *q); void initGUI(KisShortcutsEditor::ActionTypes actionTypes, KisShortcutsEditor::LetterShortcuts allowLetterShortcuts); void appendToView(uint nList, const QString &title = QString()); //used in appendToView QTreeWidgetItem *findOrMakeItem(QTreeWidgetItem *parent, const QString &name); // Set all shortcuts to their default values (bindings). void allDefault(); // clear all shortcuts void clearConfiguration(); //changeXXX were described as "conflict resolution functions" void changeKeyShortcut(KisShortcutsEditorItem *item, uint column, const QKeySequence &capture); //this invokes the appropriate conflict resolution function void capturedShortcut(const QVariant &, const QModelIndex &); /** - * Add @p action at hierchy level @p level. + * Add @p action at hierarchy level @p level. * * Filters out QActions (TODO: hmm) and unnamed actions before adding. * * @return true if the actions was successfully added */ bool addAction(QAction *action, QTreeWidgetItem *hier[], hierarchyLevel level); void printShortcuts() const; // TODO: Is this necessary w/o global actions? void setActionTypes(KisShortcutsEditor::ActionTypes actionTypes); public: // Members QList actionCollections; KisShortcutsEditor *q; Ui::KisShortcutsDialog ui; KisShortcutsEditor::ActionTypes actionTypes; KisShortcutsEditorDelegate *delegate; }; // Hack to make two protected methods public. // Used by both KisShortcutsEditorPrivate and KisShortcutsEditorDelegate class QTreeWidgetHack : public QTreeWidget { public: QTreeWidgetItem *itemFromIndex(const QModelIndex &index) const { return QTreeWidget::itemFromIndex(index); } QModelIndex indexFromItem(QTreeWidgetItem *item, int column) const { return QTreeWidget::indexFromItem(item, column); } }; /** * A QTreeWidgetItem that can handle QActions. It also provides undo functionality. * * Call commit() to save pending changes. * * @internal */ class KisShortcutsEditorItem : public QTreeWidgetItem { public: KisShortcutsEditorItem(QTreeWidgetItem *parent, QAction *action); //! Destructor will erase unsaved changes. ~KisShortcutsEditorItem() override; //! Undo the changes since the last commit. void undo(); //! Commit the changes. void commit(); QVariant data(int column, int role = Qt::DisplayRole) const override; bool operator<(const QTreeWidgetItem &other) const override; QKeySequence keySequence(uint column) const; void setKeySequence(uint column, const QKeySequence &seq); bool isModified(uint column) const; bool isModified() const; void setNameBold(bool flag) { m_isNameBold = flag; } private: friend class KisShortcutsEditorPrivate; //! Recheck modified status - could have changed back to initial value void updateModified(); //! The action this item is responsible for QAction *m_action; //! Should the Name column be painted in bold? bool m_isNameBold{false}; //@{ //! The original shortcuts before user changes. 0 means no change. QList *m_oldLocalShortcut{0}; //@} //! The localized action name QString m_actionNameInTable; //! The action id. Needed for exporting and importing QString m_id; //! The collator, for sorting QCollator m_collator; }; #endif // KISSHORTCUTSEDITOR_P diff --git a/libs/widgetutils/xmlgui/kcheckaccelerators.cpp b/libs/widgetutils/xmlgui/kcheckaccelerators.cpp index 58cdc83ed0..7a3a2cca5b 100644 --- a/libs/widgetutils/xmlgui/kcheckaccelerators.cpp +++ b/libs/widgetutils/xmlgui/kcheckaccelerators.cpp @@ -1,317 +1,317 @@ /* This file is part of the KDE libraries Copyright (C) 1997 Matthias Kalle Dalheimer (kalle@kde.org) Copyright (C) 1998, 1999, 2000 KDE Team Copyright (C) 2008 Nick Shaforostoff 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 "kcheckaccelerators.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class KCheckAcceleratorsInitializer : public QObject { Q_OBJECT public: explicit KCheckAcceleratorsInitializer(QObject *parent = 0) : QObject(parent) { } public Q_SLOTS: void initiateIfNeeded() { KConfigGroup cg(KSharedConfig::openConfig(), "Development"); QString sKey = cg.readEntry("CheckAccelerators").trimmed(); int key = 0; if (!sKey.isEmpty()) { QList cuts = QKeySequence::listFromString(sKey); if (!cuts.isEmpty()) { key = cuts.first()[0]; } } const bool autoCheck = cg.readEntry("AutoCheckAccelerators", true); const bool copyWidgetText = cg.readEntry("CopyWidgetText", false); if (!copyWidgetText && key == 0 && !autoCheck) { deleteLater(); return; } new KCheckAccelerators(qApp, key, autoCheck, copyWidgetText); deleteLater(); } }; static void startupFunc() { // Call initiateIfNeeded once we're in the event loop // This is to prevent using KSharedConfig before main() can set the app name QCoreApplication *app = QCoreApplication::instance(); KCheckAcceleratorsInitializer *initializer = new KCheckAcceleratorsInitializer(app); QMetaObject::invokeMethod(initializer, "initiateIfNeeded", Qt::QueuedConnection); } Q_COREAPP_STARTUP_FUNCTION(startupFunc) KCheckAccelerators::KCheckAccelerators(QObject *parent, int key_, bool autoCheck_, bool copyWidgetText_) : QObject(parent) , key(key_) , block(false) , autoCheck(autoCheck_) , copyWidgetText(copyWidgetText_) , drklash(0) { setObjectName(QStringLiteral("kapp_accel_filter")); KConfigGroup cg(KSharedConfig::openConfig(), "Development"); alwaysShow = cg.readEntry("AlwaysShowCheckAccelerators", false); copyWidgetTextCommand = cg.readEntry("CopyWidgetTextCommand", QString()); parent->installEventFilter(this); connect(&autoCheckTimer, SIGNAL(timeout()), SLOT(autoCheckSlot())); } bool KCheckAccelerators::eventFilter(QObject *obj, QEvent *e) { if (block) { return false; } - switch (e->type()) { // just simplify debuggin + switch (e->type()) { // just simplify debugging case QEvent::ShortcutOverride: if (key && (static_cast(e)->key() == key)) { block = true; checkAccelerators(false); block = false; e->accept(); return true; } break; case QEvent::ChildAdded: case QEvent::ChildRemoved: // Only care about widgets; this also avoids starting the timer in other threads if (!static_cast(e)->child()->isWidgetType()) { break; } Q_FALLTHROUGH(); case QEvent::Resize: case QEvent::LayoutRequest: case QEvent::WindowActivate: case QEvent::WindowDeactivate: if (autoCheck) { autoCheckTimer.setSingleShot(true); autoCheckTimer.start(20); // 20 ms } break; //case QEvent::MouseButtonDblClick: case QEvent::MouseButtonPress: if (copyWidgetText && static_cast(e)->button() == Qt::MidButton) { //kWarning()<<"obj"<(obj)->childAt(static_cast(e)->pos()); if (!w) { w = static_cast(obj); } if (!w) { return false; } //kWarning()<<"MouseButtonDblClick"<(w)) { text = static_cast(w)->text(); } else if (qobject_cast(w)) { text = static_cast(w)->text(); } else if (qobject_cast(w)) { text = static_cast(w)->currentText(); } else if (qobject_cast(w)) { text = static_cast(w)->tabText(static_cast(w)->tabAt(static_cast(e)->pos())); } else if (qobject_cast(w)) { text = static_cast(w)->title(); } else if (qobject_cast(obj)) { QAction *a = static_cast(obj)->actionAt(static_cast(e)->pos()); if (!a) { return false; } text = a->text(); if (text.isEmpty()) { text = a->iconText(); } } if (text.isEmpty()) { return false; } if (static_cast(e)->modifiers() == Qt::ControlModifier) { text.remove(QChar::fromLatin1('&')); } //kWarning()<setText(text); } else { QProcess *script = new QProcess(this); script->start(copyWidgetTextCommand.arg(text).arg(QFile::decodeName(KLocalizedString::applicationDomain()))); connect(script, SIGNAL(finished(int,QProcess::ExitStatus)), script, SLOT(deleteLater())); } e->accept(); return true; //kWarning()<<"MouseButtonDblClick"<(obj)->childAt(static_cast(e)->globalPos()); } return false; case QEvent::Timer: case QEvent::MouseMove: case QEvent::Paint: return false; default: // qDebug() << "KCheckAccelerators::eventFilter " << e->type() << " " << autoCheck; break; } return false; } void KCheckAccelerators::autoCheckSlot() { if (block) { autoCheckTimer.setSingleShot(true); autoCheckTimer.start(20); return; } block = true; checkAccelerators(!alwaysShow); block = false; } void KCheckAccelerators::createDialog(QWidget *actWin, bool automatic) { if (drklash) { return; } drklash = new QDialog(actWin); drklash->setAttribute(Qt::WA_DeleteOnClose); drklash->setObjectName(QStringLiteral("kapp_accel_check_dlg")); drklash->setWindowTitle(i18nc("@title:window", "Dr. Klash' Accelerator Diagnosis")); drklash->resize(500, 460); QVBoxLayout *layout = new QVBoxLayout(drklash); drklash_view = new QTextBrowser(drklash); layout->addWidget(drklash_view); QCheckBox *disableAutoCheck = 0; if (automatic) { disableAutoCheck = new QCheckBox(i18nc("@option:check", "Disable automatic checking"), drklash); connect(disableAutoCheck, SIGNAL(toggled(bool)), SLOT(slotDisableCheck(bool))); layout->addWidget(disableAutoCheck); } QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close, drklash); layout->addWidget(buttonBox); connect(buttonBox, SIGNAL(rejected()), drklash, SLOT(close())); if (disableAutoCheck) { disableAutoCheck->setFocus(); } else { drklash_view->setFocus(); } } void KCheckAccelerators::slotDisableCheck(bool on) { autoCheck = !on; if (!on) { autoCheckSlot(); } } void KCheckAccelerators::checkAccelerators(bool automatic) { QWidget *actWin = qApp->activeWindow(); if (!actWin) { return; } KAcceleratorManager::manage(actWin); QString a, c, r; KAcceleratorManager::last_manage(a, c, r); if (automatic) { // for now we only show dialogs on F12 checks return; } if (c.isEmpty() && r.isEmpty() && (automatic || a.isEmpty())) { return; } QString s; if (! c.isEmpty()) { s += i18n("

Accelerators changed

"); s += QStringLiteral(""); s += c; s += QStringLiteral("
"); s += i18n("Old Text"); s += QStringLiteral(""); s += i18n("New Text"); s += QStringLiteral("
"); } if (! r.isEmpty()) { s += i18n("

Accelerators removed

"); s += QStringLiteral(""); s += r; s += QStringLiteral("
"); s += i18n("Old Text"); s += QStringLiteral("
"); } if (! a.isEmpty()) { s += i18n("

Accelerators added (just for your info)

"); s += QStringLiteral(""); s += a; s += QStringLiteral("
"); s += i18n("New Text"); s += QStringLiteral("
"); } createDialog(actWin, automatic); drklash_view->setHtml(s); drklash->show(); drklash->raise(); // dlg will be destroyed before returning } #include "kcheckaccelerators.moc" diff --git a/libs/widgetutils/xmlgui/kpartgui.dtd b/libs/widgetutils/xmlgui/kpartgui.dtd index e5ea8c4811..9fe4954500 100644 --- a/libs/widgetutils/xmlgui/kpartgui.dtd +++ b/libs/widgetutils/xmlgui/kpartgui.dtd @@ -1,192 +1,192 @@ - diff --git a/packaging/linux/appimage/build-krita.sh b/packaging/linux/appimage/build-krita.sh index 086c00baeb..84288455e0 100755 --- a/packaging/linux/appimage/build-krita.sh +++ b/packaging/linux/appimage/build-krita.sh @@ -1,51 +1,52 @@ #!/bin/bash # Halt on errors and be verbose about what we are doing set -e set -x # Read in our parameters export BUILD_PREFIX=$1 export KRITA_SOURCES=$2 # qjsonparser, used to add metadata to the plugins needs to work in a en_US.UTF-8 environment. # That's not always the case, so make sure it is export LC_ALL=en_US.UTF-8 export LANG=en_us.UTF-8 # We want to use $prefix/deps/usr/ for all our dependencies export DEPS_INSTALL_PREFIX=$BUILD_PREFIX/deps/usr/ export DOWNLOADS_DIR=$BUILD_PREFIX/downloads/ # Setup variables needed to help everything find what we build export LD_LIBRARY_PATH=$DEPS_INSTALL_PREFIX/lib:$LD_LIBRARY_PATH export PATH=$DEPS_INSTALL_PREFIX/bin:$PATH export PKG_CONFIG_PATH=$DEPS_INSTALL_PREFIX/share/pkgconfig:$DEPS_INSTALL_PREFIX/lib/pkgconfig:/usr/lib/pkgconfig:$PKG_CONFIG_PATH export CMAKE_PREFIX_PATH=$DEPS_INSTALL_PREFIX:$CMAKE_PREFIX_PATH export PYTHONPATH=$DEPS_INSTALL_PREFIX/sip:$DEPS_INSTALL_PREFIX/lib/python3.5/site-packages:$DEPS_INSTALL_PREFIX/lib/python3.5 export PYTHONHOME=$DEPS_INSTALL_PREFIX # Make sure our build directory exists if [ ! -d $BUILD_PREFIX/krita-build/ ] ; then mkdir -p $BUILD_PREFIX/krita-build/ fi # Now switch to it cd $BUILD_PREFIX/krita-build/ # Determine how many CPUs we have CPU_COUNT=`grep processor /proc/cpuinfo | wc -l` # Configure Krita cmake $KRITA_SOURCES \ -DCMAKE_INSTALL_PREFIX:PATH=$BUILD_PREFIX/krita.appdir/usr \ -DDEFINE_NO_DEPRECATED=1 \ -DCMAKE_BUILD_TYPE=Release \ -DFOUNDATION_BUILD=1 \ + -DHIDE_SAFE_ASSERTS=ON \ -DBUILD_TESTING=FALSE \ -DPYQT_SIP_DIR_OVERRIDE=$DEPS_INSTALL_PREFIX/share/sip/ \ -DHAVE_MEMORY_LEAK_TRACKER=FALSE # Build and Install Krita (ready for the next phase) make -j$CPU_COUNT install diff --git a/packaging/linux/snap/.gitignore b/packaging/linux/snap/.gitignore new file mode 100644 index 0000000000..ea38c6dd42 --- /dev/null +++ b/packaging/linux/snap/.gitignore @@ -0,0 +1,14 @@ +# gitginore template for creating Snap packages +# website: https://snapcraft.io/ + +parts/ +prime/ +stage/ +*.snap + +# Snapcraft global state tracking data(automatically generated) +# https://forum.snapcraft.io/t/location-to-save-global-state/768 +/snap/.snapcraft/ + +# Source archive packed by `snapcraft cleanbuild` before pushing to the LXD container +/*_source.tar.bz2 diff --git a/packaging/linux/snap/build.sh b/packaging/linux/snap/build.sh index 4e810dde77..6a2e5ca0f7 100755 --- a/packaging/linux/snap/build.sh +++ b/packaging/linux/snap/build.sh @@ -1,21 +1,22 @@ #!/bin/bash set -ex IMAGE=ubuntu:18.04 CONTAINER=container-krita-snap function at_exit { lxc stop $CONTAINER } lxc stop $CONTAINER || true lxc launch --ephemeral "$IMAGE" $CONTAINER sleep 4 # so network is up trap at_exit INT TERM EXIT lxc file push -p --recursive . $CONTAINER/workspace lxc exec $CONTAINER -- /workspace/snap/build_in_container.sh -lxc file pull --recursive $CONTAINER/workspace/snap/result . +rm -r result +lxc file pull --recursive $CONTAINER/workspace/result . diff --git a/packaging/linux/snap/build_in_container.sh b/packaging/linux/snap/build_in_container.sh index a9b05c38bd..0edfffe8c5 100755 --- a/packaging/linux/snap/build_in_container.sh +++ b/packaging/linux/snap/build_in_container.sh @@ -1,19 +1,19 @@ #!/bin/bash set -ex cd /workspace/snap ping -c1 networkcheck.kde.org apt-key adv --keyserver keyserver.ubuntu.com --recv E6D4736255751E5D echo 'deb http://archive.neon.kde.org/unstable bionic main' > /etc/apt/sources.list.d/neon.list apt update snap install --edge --classic snapcraft snapcraft --version snapcraft --destructive-mode -mkdir -p result -mv *.snap result/ +mkdir -p /workspace/result +mv *.snap /workspace/result/ diff --git a/packaging/linux/snap/kf5-locale-gen b/packaging/linux/snap/scripts/kf5-locale-gen similarity index 100% rename from packaging/linux/snap/kf5-locale-gen rename to packaging/linux/snap/scripts/kf5-locale-gen diff --git a/packaging/linux/snap/qt5-launch b/packaging/linux/snap/scripts/qt5-launch similarity index 100% rename from packaging/linux/snap/qt5-launch rename to packaging/linux/snap/scripts/qt5-launch diff --git a/packaging/linux/snap/snapcraft.yaml b/packaging/linux/snap/snapcraft.yaml index 4f6f90ab22..6a71aa7a57 100644 --- a/packaging/linux/snap/snapcraft.yaml +++ b/packaging/linux/snap/snapcraft.yaml @@ -1,175 +1,179 @@ name: krita -version: 4.2.2 +version: 4.2.7.1 summary: Krita is the digital painting studio for artists description: Krita is a creative application for raster images. Whether you want to create from scratch or work with existing images, Krita is for you. You can work with photos or scanned images, or start with a blank slate. Krita supports most graphics tablets out of the box. base: core18 apps: krita: - command: qt5-launch usr/bin/krita + command: usr/bin/krita + command-chain: + - bin/qt5-launch plugs: [x11, unity7, home, opengl, network, network-bind, removable-media, desktop, desktop-legacy] desktop: usr/share/applications/org.kde.krita.desktop layout: /usr/bin/ffmpeg: bind-file: $SNAP/usr/bin/ffmpeg parts: krita: plugin: cmake configflags: - "-DCMAKE_INSTALL_PREFIX=/usr" - "-DCMAKE_BUILD_TYPE=Release" - "-DENABLE_TESTING=OFF" - "-DBUILD_TESTING=OFF" + - "-DHIDE_SAFE_ASSERTS=OFF" - "-DKDE_SKIP_TEST_SETTINGS=ON" - source: https://download.kde.org/stable/krita/4.2.2/krita-4.2.2.tar.xz + source: https://download.kde.org/stable/krita/4.2.7.1/krita-4.2.7.1.tar.xz # Use these instead to build from the git source # source: https://anongit.kde.org/krita.git # source-type: git # source-branch: master override-stage: | # Stage the part snapcraftctl stage # Modify the .desktop file in the stage to point to where the icon will be in the installed snap sed -i 's|Icon=\(.*\)|Icon=${SNAP}/usr/share/icons/hicolor/1024x1024/apps/\1.png|' usr/share/applications/org.kde.krita.desktop build-packages: - gettext - build-essential - cmake - libboost-dev - libboost-system-dev - libeigen3-dev - libexiv2-dev - libfftw3-dev - libfontconfig1-dev - libfreetype6-dev - libgl1-mesa-dev - libglew-dev - libglib2.0-dev - libglu1-mesa-dev - libgsf-1-dev - libgsl-dev - libjpeg-dev - liblcms2-dev - libopenexr-dev - libpng-dev - libpoppler-qt5-dev + - libopenjpeg-dev - libtiff5-dev - libvc-dev - libopencolorio-dev - libx11-dev - libxml2-dev - libxslt1-dev - libxi-dev - pkg-config - vc-dev - zlib1g-dev - libkf5kdcraw-dev - shared-mime-info - libopenimageio-dev - extra-cmake-modules - libkf5archive-dev - libkf5coreaddons-dev - libkf5guiaddons-dev - libkf5itemmodels-dev - libkf5itemviews-dev - libkf5widgetsaddons-dev - libkf5i18n-dev - libkf5windowsystem-dev - libkf5completion-dev - libkf5iconthemes-dev - libkf5kiocore5 - libqt5svg5-dev - libqt5x11extras5-dev - libqt5opengl5-dev - libquazip5-dev - qtmultimedia5-dev - qtdeclarative5-dev - libkf5crash-dev runtime: plugin: nil stage-packages: - libexiv2-27 - libfftw3-double3 - libgomp1 - libgsl23 - libilmbase12 - libjpeg8 - liblcms2-2 - libopencolorio1v5 - libopenexr22 - libpng16-16 - libstdc++6 - libtiff5 - libx11-6 - libxcb1 - libxi6 - zlib1g - libpoppler-qt5-1 - shared-mime-info - libboost-system1.65.1 - librtmp1 - libqt5quickwidgets5 - libkf5archive5 - libkf5completion5 - libkf5configcore5 - libkf5configgui5 - libkf5coreaddons5 - libkf5guiaddons5 - libkf5i18n5 - libkf5itemviews5 - libkf5widgetsaddons5 - libkf5windowsystem5 - libkf5crash5 - libqt5concurrent5 - libqt5core5a - libqt5dbus5 - libqt5gui5 - libqt5network5 - libqt5printsupport5 - libqt5svg5 - libqt5widgets5 - libqt5x11extras5 - libqt5xml5 - locales - libc-bin - libquazip5-1 - libqt5quick5 - libqt5quickparticles5 - libqt5quickshapes5 - qt5-qmltooling-plugins - libqt5qml5 - libqt5multimedia5 - libqt5multimediagsttools5 - libqt5multimediaquick5 - libqt5multimediawidgets5 # Required for rendering animations - ffmpeg - libglu1-mesa - libslang2 prime: - "-usr/share/wallpapers/*" - "-usr/share/fonts/*" plasma-integration: plugin: nil stage-packages: - plasma-integration - kde-style-breeze - breeze-icon-theme - kio # runtime slaves for kio prime: - "-usr/share/wallpapers/*" - "-usr/share/fonts/*" launcher: plugin: dump - source: . + source: scripts organize: qt5-launch: bin/qt5-launch kf5-locale-gen: bin/kf5-locale-gen diff --git a/packaging/macos/dmgstyle.sh b/packaging/macos/dmgstyle.sh index 2396ce784d..8c2738ba80 100755 --- a/packaging/macos/dmgstyle.sh +++ b/packaging/macos/dmgstyle.sh @@ -1,186 +1,186 @@ #!/usr/bin/env sh # get krita disk mounted image current properties # creates a dummy folder to aid in setting the layout for # the dmg image file. set_dmgfolder_props() { cp ${DMG_background} "${DMG_STYLE_DIR}/.background" BG_FILENAME=${DMG_background##*/} printf ' tell application "Finder" tell folder "%s" of %s open set current view of container window to icon view set toolbar visible of container window to false set statusbar visible of container window to false set the bounds of container window to {186, 156, 956, 592} set theViewOptions to the icon view options of container window set arrangement of theViewOptions to not arranged set icon size of theViewOptions to 80 set background picture of theViewOptions to file ".background:%s" set position of item "krita.app" of container window to {279, 272} set position of item "Applications" of container window to {597, 272} set position of item "Terms of Use" of container window to {597, 110} update without registering applications close end tell end tell ' "${DMG_STYLE_DIR_OSA}" "${DMG_STYLE_LOCATION}" "${BG_FILENAME}" | osascript get_dmgfolder_props > /dev/null } get_dmgfolder_props() { printf ' tell application "Finder" tell folder "%s" of %s open set v_cv to current view of container window log v_cv set v_tb to toolbar visible of container window log v_tb set v_stat to statusbar visible of container window log v_stat set v_bounds to the bounds of container window log v_bounds set theViewOptions to the icon view options of container window set v_arr to arrangement of theViewOptions log v_arr set v_icons to icon size of theViewOptions log v_icons set v_pos_k to position of item "krita.app" of container window log v_pos_k set v_pos_ap to position of item "Applications" of container window log v_pos_ap set v_pos_term to position of item "Terms of Use" of container window log v_pos_term update without registering applications end tell end tell ' "${DMG_STYLE_DIR_OSA}" "${DMG_STYLE_LOCATION}" | osascript 2>&1 | awk '{printf "%s\n" ,$0}' } # Print Suitable command to paste on osxdeploy.sh print_osascript_cmd() { OLD_IFS=${IFS} IFS=$'\n' dmg_ops=($@) printf 'tell application "Finder" tell disk "%%s" open set current view of container window to %s set toolbar visible of container window to %s set statusbar visible of container window to %s set the bounds of container window to {%s} set theViewOptions to the icon view options of container window set arrangement of theViewOptions to %s set icon size of theViewOptions to %s set background picture of theViewOptions to file ".background:%%s" set position of item "krita.app" of container window to {%s} set position of item "Applications" of container window to {%s} set position of item "Terms of Use" of container window to {%s} update without registering applications delay 1 close end tell end tell ' "${dmg_ops[@]}" IFS=${OLD_IFS} } print_help() { printf \ "dmgstyle.sh is a minitool to aid in the creation of the DMG look for krita out is designed to be copy pasted into osxdeploy.sh in the osasrcipt section - Run once with set to set new background - Edit by moving elements or + J to set net icon size - To get current options for osxdeploy: rerun with no arguments. USAGE: dmgstyle.sh [option] Options: - No option fetchs dummy design window state for paste in osxdeploy + No option fetches dummy design window state for paste in osxdeploy set set default values changing the background to image in " } if test -z ${BUILDROOT}; then echo "ERROR: BUILDROOT env not set!" echo "\t Must point to the root of the buildfiles as stated in 3rdparty Readme" print_help exit fi BUILDROOT=$(cd "$(dirname "${BUILDROOT}")"; pwd -P)/$(basename "${BUILDROOT}") # force full path # if in home we get path minus $HOME # if in Mounted disk we get path minus "/Volumes/diskname" if [[ -n "$(grep /Users/$(whoami) <<< ${BUILDROOT})" ]];then # buildroot is in home build_folder=${BUILDROOT#${HOME}/} # removes home dmg_vol_name="home" else tmp_folder=${BUILDROOT#/Volumes/} build_folder=${tmp_folder#*/} # get minus all printf -v dmg_vol_name 'disk "%s"' ${tmp_folder%%/*} # get volume name fi # Setting global variables DMG_DUMMY_NAME="dmg_style_dummy" DMG_STYLE_DIR="${BUILDROOT}/${DMG_DUMMY_NAME}" DMG_STYLE_DIR_OSA=$(printf "%s/%s" ${build_folder} ${DMG_DUMMY_NAME} | tr / :) DMG_STYLE_LOCATION=${dmg_vol_name} KIS_INSTALL_DIR="${BUILDROOT}/i" if [[ ${1} = "-h" || ${1} = "--help" ]]; then print_help exit fi if [[ ${1} = "set" ]]; then # Create style container folder it if doesn't exist if [[ ! -e ${DMG_STYLE_DIR} ]]; then mkdir "${DMG_STYLE_DIR}" mkdir "${DMG_STYLE_DIR}/Terms of Use" mkdir "${DMG_STYLE_DIR}/.background" ln -s "/Applications" "${DMG_STYLE_DIR}/Applications" ln -s "${KIS_INSTALL_DIR}/bin/krita.app" "${DMG_STYLE_DIR}/krita.app" fi ## check background validity if [[ -f ${2} ]]; then DMG_validBG=0 echo "attempting to check background is valid jpg or png..." BG_FORMAT=$(sips --getProperty format ${2} | awk '{printf $2}') if [[ "png" = ${BG_FORMAT} || "jpeg" = ${BG_FORMAT} ]];then echo "valid image file" DMG_background=$(cd "$(dirname "${2}")"; pwd -P)/$(basename "${2}") DMG_validBG=1 fi fi if [[ ${DMG_validBG} -eq 0 ]]; then echo "No jpg or png given or valid file detected!!" exit else BG_DPI=$(sips --getProperty dpiWidth ${DMG_background} | grep dpi | awk '{print $2}') if [[ $(echo "${BG_DPI} > 150" | bc -l) -eq 1 ]]; then printf "WARNING: image dpi has an effect on apparent size! Check dpi is adequate for screen display if image appears very small Current dpi is: %s\n" ${BG_DPI} fi set_dmgfolder_props fi else if [[ ! -e ${DMG_STYLE_DIR} ]]; then echo "First run must use option [set]!!" exit fi osa_values=$(get_dmgfolder_props) print_osascript_cmd "${osa_values}" fi diff --git a/packaging/macos/osxbuild.sh b/packaging/macos/osxbuild.sh index 0a72eb592d..ec39bee74e 100755 --- a/packaging/macos/osxbuild.sh +++ b/packaging/macos/osxbuild.sh @@ -1,535 +1,536 @@ #!/usr/bin/env bash # osxbuild.sh automates building and installing of krita and krita dependencies # for OSX, the script only needs you to set BUILDROOT environment to work # properly. # # Run with no args for a short help about each command. # builddeps: Attempts to build krita dependencies in the necessary order, # intermediate steps for creating symlinks and fixing rpath of some # packages midway is also managed. Order goes from top to bottom, to add # new steps just place them in the proper place. # rebuilddeps: This re-runs all make and make install of dependencies 3rdparty # this was needed as deleting the entire install directory an rerunning build # step for dependencies does not install if they are already built. This step # forces installation. Have not tested it lately so it might not be needed anymore # build: Runs cmake build and make step for krita sources. It always run cmake step, so # it might take a bit longer than a pure on the source tree. The script tries # to set the make flag -jN to a proper N. # install: Runs install step for krita sources. # fixboost: Search for all libraries using boost and sets a proper @rpath for boost as by # default it fails to set a proper @rpath # buildinstall: Runs build, install and fixboost steps.# if test -z $BUILDROOT; then echo "ERROR: BUILDROOT env not set, exiting!" echo "\t Must point to the root of the buildfiles as stated in 3rdparty Readme" exit fi echo "BUILDROOT set to ${BUILDROOT}" export KIS_SRC_DIR=${BUILDROOT}/krita export KIS_TBUILD_DIR=${BUILDROOT}/depbuild export KIS_TDEPINSTALL_DIR=${BUILDROOT}/depinstall export KIS_DOWN_DIR=${BUILDROOT}/down export KIS_BUILD_DIR=${BUILDROOT}/kisbuild export KIS_INSTALL_DIR=${BUILDROOT}/i # flags for OSX environment # Qt only supports from 10.12 up, and https://doc.qt.io/qt-5/macos.html#target-platforms warns against setting it lower export MACOSX_DEPLOYMENT_TARGET=10.12 export QMAKE_MACOSX_DEPLOYMENT_TARGET=10.12 # Build time variables if test -z $(which cmake); then echo "ERROR: cmake not found, exiting!" exit fi export PATH=${KIS_INSTALL_DIR}/bin:$PATH export C_INCLUDE_PATH=${KIS_INSTALL_DIR}/include:/usr/include:${C_INCLUDE_PATH} export CPLUS_INCLUDE_PATH=${KIS_INSTALL_DIR}/include:/usr/include:${CPLUS_INCLUDE_PATH} export LIBRARY_PATH=${KIS_INSTALL_DIR}/lib:/usr/lib:${LIBRARY_PATH} # export CPPFLAGS=-I${KIS_INSTALL_DIR}/include # export LDFLAGS=-L${KIS_INSTALL_DIR}/lib export FRAMEWORK_PATH=${KIS_INSTALL_DIR}/lib/ # export PYTHONHOME=${KIS_INSTALL_DIR} # export PYTHONPATH=${KIS_INSTALL_DIR}/sip:${KIS_INSTALL_DIR}/lib/python3.5/site-packages:${KIS_INSTALL_DIR}/lib/python3.5 # This will make the debug output prettier export KDE_COLOR_DEBUG=1 export QTEST_COLORED=1 export OUPUT_LOG="${BUILDROOT}/osxbuild.log" export ERROR_LOG="${BUILDROOT}/osxbuild-error.log" printf "" > "${OUPUT_LOG}" printf "" > "${ERROR_LOG}" # configure max core for make compile ((MAKE_THREADS=1)) if test ${OSTYPE} == "darwin*"; then ((MAKE_THREADS = $(sysctl -n hw.ncpu) - 1)) fi # Prints stderr and stdout to log files # >(tee) works but breaks sigint log_cmd () { if [[ "${VERBOSE}" ]]; then "$@" 1>> ${OUPUT_LOG} 2>> ${ERROR_LOG} else "$@" 2>> ${ERROR_LOG} | tee -a ${OUPUT_LOG} > /dev/null fi } # Log messages to logfile log () { if [[ "${VERBOSE}" ]]; then printf "%s\n" "${@}" | tee -a ${OUPUT_LOG} else printf "%s\n" "${@}" | tee -a ${OUPUT_LOG} > /dev/null fi } # if previous command gives error # print msg print_if_error() { error_stat="${?}" if [ ${error_stat} -ne 0 ]; then printf "\e[31m%s %s\e[0m\n" "Error:" "${1}" 2>> ${ERROR_LOG} printf "%s\r" "${error_stat}" fi } -# print status messges +# print status messages print_msg() { printf "\e[32m%s\e[0m\n" "${1}" printf "%s\n" "${1}" >> ${OUPUT_LOG} } check_dir_path () { printf "%s" "Checking if ${1} exists and is dir... " if test -d ${1}; then echo -e "OK" elif test -e ${1}; then echo -e "\n\tERROR: file ${1} exists but is not a directory!" >&2 return 1 else echo -e "Creating ${1}" mkdir ${1} fi return 0 } # builds dependencies for the first time cmake_3rdparty () { cd ${KIS_TBUILD_DIR} local build_pkgs=("${@}") # convert to array if [[ ${2} = "1" ]]; then local nofix="true" local build_pkgs=(${build_pkgs[@]:0:1}) fi for package in ${build_pkgs[@]} ; do print_msg "Building ${package}" log_cmd cmake --build . --config RelWithDebInfo --target ${package} if [[ ! $(print_if_error "Failed build ${package}") ]]; then print_msg "Build Success! ${package}" fi # fixes does not depend on failure if [[ ! ${nofix} ]]; then build_3rdparty_fixes ${package} fi done } build_3rdparty_fixes(){ pkg=${1} if [[ "${pkg}" = "ext_qt" && -e "${KIS_INSTALL_DIR}/bin/qmake" ]]; then ln -sf qmake "${KIS_INSTALL_DIR}/bin/qmake-qt5" elif [[ "${pkg}" = "ext_openexr" ]]; then # open exr will fail the first time is called # rpath needs to be fixed an build rerun log "Fixing rpath on openexr file: b44ExpLogTable" log "Fixing rpath on openexr file: dwaLookups" log_cmd install_name_tool -add_rpath ${KIS_INSTALL_DIR}/lib ${KIS_TBUILD_DIR}/ext_openexr/ext_openexr-prefix/src/ext_openexr-build/IlmImf/./b44ExpLogTable log_cmd install_name_tool -add_rpath ${KIS_INSTALL_DIR}/lib ${KIS_TBUILD_DIR}/ext_openexr/ext_openexr-prefix/src/ext_openexr-build/IlmImf/./dwaLookups # we must rerun build! cmake_3rdparty ext_openexr "1" elif [[ "${pkg}" = "ext_fontconfig" ]]; then log "fixing rpath on fc-cache" log_cmd install_name_tool -add_rpath ${KIS_INSTALL_DIR}/lib ${KIS_TBUILD_DIR}/ext_fontconfig/ext_fontconfig-prefix/src/ext_fontconfig-build/fc-cache/.libs/fc-cache # rerun rebuild cmake_3rdparty ext_fontconfig "1" fi } build_3rdparty () { print_msg "building in ${KIS_TBUILD_DIR}" log "$(check_dir_path ${KIS_TBUILD_DIR})" log "$(check_dir_path ${KIS_DOWN_DIR})" log "$(check_dir_path ${KIS_INSTALL_DIR})" cd ${KIS_TBUILD_DIR} log_cmd cmake ${KIS_SRC_DIR}/3rdparty/ \ -DCMAKE_OSX_DEPLOYMENT_TARGET=10.12 \ -DCMAKE_INSTALL_PREFIX=${KIS_INSTALL_DIR} \ -DEXTERNALS_DOWNLOAD_DIR=${KIS_DOWN_DIR} \ -DINSTALL_ROOT=${KIS_INSTALL_DIR} # -DCPPFLAGS=-I${KIS_INSTALL_DIR}/include \ # -DLDFLAGS=-L${KIS_INSTALL_DIR}/lib print_msg "finished 3rdparty build setup" if [[ -n ${1} ]]; then cmake_3rdparty "${@}" exit fi # build 3rdparty tools # The order must not be changed! cmake_3rdparty \ ext_pkgconfig \ ext_gettext \ ext_openssl \ ext_qt \ ext_zlib \ ext_boost \ ext_eigen3 \ ext_exiv2 \ ext_fftw3 \ ext_ilmbase \ ext_jpeg \ ext_lcms2 \ ext_ocio \ ext_openexr cmake_3rdparty \ ext_png \ ext_tiff \ ext_gsl \ ext_vc \ ext_libraw \ ext_giflib \ ext_freetype \ ext_fontconfig \ ext_poppler # Stop if qmake link was not created # this meant qt build fail and further builds will # also fail. log_cmd test -L "${KIS_INSTALL_DIR}/bin/qmake-qt5" if [[ $(print_if_error "qmake link missing!") ]]; then printf " link: ${KIS_INSTALL_DIR}/bin/qmake-qt5 missing! It probably means ext_qt failed!! check, fix and rerun!\n" exit fi # for python cmake_3rdparty \ ext_python \ ext_sip \ ext_pyqt cmake_3rdparty ext_libheif cmake_3rdparty \ ext_extra_cmake_modules \ ext_kconfig \ ext_kwidgetsaddons \ ext_kcompletion \ ext_kcoreaddons \ ext_kguiaddons \ ext_ki18n \ ext_kitemmodels \ ext_kitemviews \ ext_kimageformats \ ext_kwindowsystem \ ext_quazip } # Recall cmake for all 3rd party packages # make is only on target first run # subsequent runs only call make install rebuild_3rdparty () { print_msg "starting rebuild of 3rdparty packages" build_install_ext() { for pkg in ${@:1:${#@}}; do { cd ${KIS_TBUILD_DIR}/${pkg}/${pkg}-prefix/src/${pkg}-stamp } || { cd ${KIS_TBUILD_DIR}/ext_frameworks/${pkg}-prefix/src/${pkg}-stamp } || { cd ${KIS_TBUILD_DIR}/ext_heif/${pkg}-prefix/src/${pkg}-stamp } log "Installing ${pkg} files..." rm ${pkg}-configure ${pkg}-build ${pkg}-install cmake_3rdparty ${pkg} cd ${KIS_TBUILD_DIR} done } # Do not process complete list only pkgs given. if ! test -z ${1}; then build_install_ext ${@} exit fi build_install_ext \ ext_pkgconfig \ ext_iconv \ ext_gettext \ ext_openssl \ ext_qt \ ext_zlib \ ext_boost \ ext_eigen3 \ ext_expat \ ext_exiv2 \ ext_fftw3 \ ext_ilmbase \ ext_jpeg \ ext_patch \ ext_lcms2 \ ext_ocio \ ext_ilmbase \ ext_openexr \ ext_png \ ext_tiff \ ext_gsl \ ext_vc \ ext_libraw \ ext_giflib \ ext_fontconfig \ ext_freetype \ ext_poppler \ ext_python \ ext_sip \ ext_pyqt \ build_install_ext \ ext_yasm \ ext_nasm \ ext_libx265 \ ext_libde265 \ ext_libheif \ # Build kde_frameworks build_install_ext \ ext_extra_cmake_modules \ ext_kconfig \ ext_kwidgetsaddons \ ext_kcompletion \ ext_kcoreaddons \ ext_kguiaddons \ ext_ki18n \ ext_kitemmodels \ ext_kitemviews \ ext_kimageformats \ ext_kwindowsystem \ ext_quazip } #not tested set_krita_dirs() { if [[ -n ${1} ]]; then KIS_BUILD_DIR=${BUILDROOT}/b_${1} KIS_INSTALL_DIR=${BUILDROOT}/i_${1} KIS_SRC_DIR=${BUILDROOT}/src_${1} fi } # build_krita # run cmake krita build_krita () { export DYLD_FRAMEWORK_PATH=${FRAMEWORK_PATH} echo ${KIS_BUILD_DIR} echo ${KIS_INSTALL_DIR} log_cmd check_dir_path ${KIS_BUILD_DIR} cd ${KIS_BUILD_DIR} cmake ${KIS_SRC_DIR} \ -DFOUNDATION_BUILD=ON \ -DBoost_INCLUDE_DIR=${KIS_INSTALL_DIR}/include \ -DCMAKE_INSTALL_PREFIX=${KIS_INSTALL_DIR} \ -DDEFINE_NO_DEPRECATED=1 \ -DBUILD_TESTING=OFF \ + -DHIDE_SAFE_ASSERTS=OFF \ -DKDE_INSTALL_BUNDLEDIR=${KIS_INSTALL_DIR}/bin \ -DPYQT_SIP_DIR_OVERRIDE=${KIS_INSTALL_DIR}/share/sip/ \ -DCMAKE_BUILD_TYPE=RelWithDebInfo \ -DCMAKE_OSX_DEPLOYMENT_TARGET=10.12 \ -DPYTHON_INCLUDE_DIR=${KIS_INSTALL_DIR}/lib/Python.framework/Headers # copiling phase make -j${MAKE_THREADS} # compile integrations if test ${OSTYPE} == "darwin*"; then cd ${KIS_BUILD_DIR}/krita/integration/kritaquicklook make -j${MAKE_THREADS} fi } build_krita_tarball () { filename="$(basename ${1})" KIS_CUSTOM_BUILD="${BUILDROOT}/releases/${filename%.tar.gz}" print_msg "Tarball BUILDROOT is ${KIS_CUSTOM_BUILD}" filename_dir=$(dirname "${1}") cd "${filename_dir}" file_abspath="$(pwd)/${1##*/}" mkdir "${KIS_CUSTOM_BUILD}" 2> /dev/null cd "${KIS_CUSTOM_BUILD}" mkdir "src" "build" 2> /dev/null tar -xzf "${file_abspath}" --strip-components=1 --directory "src" if [[ $(print_if_error "Untar ${file_abspath} failed!") ]]; then exit fi KIS_BUILD_DIR="${KIS_CUSTOM_BUILD}/build" KIS_SRC_DIR="${KIS_CUSTOM_BUILD}/src" build_krita print_msg "Build done!" print_msg "to install run osxbuild.sh install ${KIS_BUILD_DIR}" } install_krita () { # custom install provided if [[ -n "${1}" ]]; then KIS_BUILD_DIR="${1}" fi print_msg "Install krita from ${KIS_BUILD_DIR}" log_cmd check_dir_path ${KIS_BUILD_DIR} cd ${KIS_BUILD_DIR} if [[ $(print_if_error "could not cd to ${KIS_BUILD_DIR}") ]]; then exit fi make install # compile integrations if test ${OSTYPE} == "darwin*"; then cd ${KIS_BUILD_DIR}/krita/integration/kritaquicklook make install fi } # Runs all fixes for path and packages. # Historically only fixed boost @rpath fix_boost_rpath () { # helpers to define function only once fixboost_find () { for FILE in "${@}"; do if [[ -n "$(otool -L $FILE | grep boost)" ]]; then log "Fixing -- $FILE" log_cmd install_name_tool -change libboost_system.dylib @rpath/libboost_system.dylib $FILE fi done } batch_fixboost() { xargs -P4 -I FILE bash -c 'fixboost_find "FILE"' } export -f fixboost_find export -f log export -f log_cmd print_msg "Fixing boost in... ${KIS_INSTALL_DIR}" # install_name_tool -add_rpath ${KIS_INSTALL_DIR}/lib $BUILDROOT/$KRITA_INSTALL/bin/krita.app/Contents/MacOS/gmic_krita_qt log_cmd install_name_tool -add_rpath ${KIS_INSTALL_DIR}/lib ${KIS_INSTALL_DIR}/bin/krita.app/Contents/MacOS/krita # echo "Added rpath ${KIS_INSTALL_DIR}/lib to krita bin" # install_name_tool -add_rpath ${BUILDROOT}/deps/lib ${KIS_INSTALL_DIR}/bin/krita.app/Contents/MacOS/krita log_cmd install_name_tool -change libboost_system.dylib @rpath/libboost_system.dylib ${KIS_INSTALL_DIR}/bin/krita.app/Contents/MacOS/krita find -L "${KIS_INSTALL_DIR}" -name '*so' -o -name '*dylib' | batch_fixboost log "Fixing boost done!" } print_usage () { printf "USAGE: osxbuild.sh [pkg|file]\n" printf "BUILDSTEPS:\t\t" printf "\n builddeps \t\t Run cmake step for 3rd party dependencies, optionally takes a [pkg] arg" printf "\n rebuilddeps \t\t Rerun make and make install step for 3rd party deps, optionally takes a [pkg] arg - \t\t\t usefull for cleaning install directory and quickly reinstall all deps." + \t\t\t useful for cleaning install directory and quickly reinstall all deps." printf "\n fixboost \t\t Fixes broken boost \@rpath on OSX" printf "\n build \t\t\t Builds krita" printf "\n buildtarball \t\t\t Builds krita from provided [file] tarball" printf "\n install \t\t Installs krita. Optionally accepts a [build dir] as argument \t\t\t this will install krita from given directory" printf "\n buildinstall \t\t Build and Installs krita, running fixboost after installing" printf "\n" } if [[ ${#} -eq 0 ]]; then echo "ERROR: No option given!" print_usage exit 1 fi if [[ ${1} = "builddeps" ]]; then build_3rdparty "${@:2}" elif [[ ${1} = "rebuilddeps" ]]; then rebuild_3rdparty "${@:2}" elif [[ ${1} = "fixboost" ]]; then if [[ -d ${1} ]]; then KIS_BUILD_DIR="${1}" fi fix_boost_rpath elif [[ ${1} = "build" ]]; then build_krita ${2} elif [[ ${1} = "buildtarball" ]]; then # uncomment line to optionally change # install directory providing a third argument # This is not on by default as build success requires all # deps installed in the given dir beforehand. # KIS_INSTALL_DIR=${3} build_krita_tarball ${2} elif [[ ${1} = "install" ]]; then install_krita ${2} fix_boost_rpath elif [[ ${1} = "buildinstall" ]]; then build_krita ${2} install_krita ${2} fix_boost_rpath ${2} elif [[ ${1} = "test" ]]; then ${KIS_INSTALL_DIR}/bin/krita.app/Contents/MacOS/krita else echo "Option ${1} not supported" print_usage exit 1 fi # after finishig sometimes it complains about missing matching quotes. diff --git a/packaging/macos/osxdeploy.sh b/packaging/macos/osxdeploy.sh index e23d2e59e2..b181e99230 100755 --- a/packaging/macos/osxdeploy.sh +++ b/packaging/macos/osxdeploy.sh @@ -1,572 +1,713 @@ #!/usr/bin/env bash # Krita tool to create dmg from installed source # Copies all files to a folder to be converted into the final dmg # osxdeploy.sh automates the creation of the release DMG. # default background and style are used if none provided # A short explanation of what it does: # - Copies krita.app contents to kritadmg folder -# - Copies i/share to Contents/Resources excluding unnecesary files +# - Copies i/share to Contents/Resources excluding unnecessary files # - Copies translations, qml and quicklook PlugIns # - Copies i/plugins and i/lib/plugins to Contents/PlugIns # - Runs macdeployqt: macdeployqt is not built by default in ext_qt # build by: # cd ${BUILDROOT}/depbuild/ext_qt/ext_qt-prefix/src/ext_qt/qttools/src # make sub-macdeployqt-all # make sub-macdeployqt-install_subtargets # make install # the script changes dir to installation/bin to run macdeployqt as it can be buggy -# if not runned from the same folder as the binary is on. +# if not run from the same folder as the binary is on. # - Fix rpath from krita bin -# - Find missing libraries from plugins and copy to Framworks or plugins. +# - Find missing libraries from plugins and copy to Frameworks or plugins. # This uses oTool iterative to find all unique libraries, then it searches each # library fond in folder, and if not found attempts to copy contents -# to the appropiate folder, either Frameworks (if frameworks is in namefile, or +# to the appropriate folder, either Frameworks (if frameworks is in namefile, or # library has plugin isnot in path), or plugin if otherwise. # - Builds DMG # Building DMG creates a new dmg with the contents of # mounts the dmg and sets the style for dmg. # unmount # Compress resulting dmg into krita_nightly-.dmg # deletes temporary files. if test -z ${BUILDROOT}; then echo "ERROR: BUILDROOT env not set!" echo "\t Must point to the root of the buildfiles as stated in 3rdparty Readme" echo "exiting..." exit fi +# print status messages +print_msg() { + printf "\e[32m${1}\e[0m\n" "${@:2}" + # printf "%s\n" "${1}" >> ${OUPUT_LOG} +} + +# print error +print_error() { + printf "\e[31m%s %s\e[0m\n" "Error:" "${1}" +} + get_script_dir() { script_source="${BASH_SOURCE[0]}" # go to target until finding root. while [ -L "${script_source}" ]; do script_target="$(readlink ${script_source})" if [[ "${script_source}" = /* ]]; then script_source="$script_target" else script_dir="$(dirname "${script_source}")" script_source="${script_dir}/${script_target}" fi done echo "$(dirname ${script_source})" } DMG_title="krita" #if changed krita.temp.dmg must be deleted manually SCRIPT_SOURCE_DIR="$(get_script_dir)" # There is some duplication between build and deploy scripts # a config env file could would be a nice idea. KIS_SRC_DIR=${BUILDROOT}/krita KIS_INSTALL_DIR=${BUILDROOT}/i KIS_BUILD_DIR=${BUILDROOT}/kisbuild # only used for getting git sha number KRITA_DMG=${BUILDROOT}/kritadmg KRITA_DMG_TEMPLATE=${BUILDROOT}/kritadmg-template export PATH=${KIS_INSTALL_DIR}/bin:$PATH # flags for OSX environment # We only support from 10.11 up export MACOSX_DEPLOYMENT_TARGET=10.11 export QMAKE_MACOSX_DEPLOYMENT_TARGET=10.11 -# Attempt to find python_version -local_PY_MAYOR_VERSION=$(python -c "import sys; print(sys.version_info[0])") -local_PY_MINOR_VERSION=$(python -c "import sys; print(sys.version_info[1])") -PY_VERSION="${local_PY_MAYOR_VERSION}.${local_PY_MINOR_VERSION}" -echo "Detected Python ${PY_VERSION}" print_usage () { - echo "USAGE: osxdeploy.sh [-s=] [-style=] [-bg=]" - echo "\t -s Code sign identity for codesign" - echo "\t -style Style file defined from 'dmgstyle.sh' output" - echo "\t -bg Set a background image for dmg folder" - echo "\t osxdeploy needs an input image to add to the dmg background - \t image recomended size is at least 950x500\n" -} + printf "USAGE: + osxdeploy.sh [-s=] [-notarize-ac=] [-style=] [-bg=] + -s \t\t\t Code sign identity for codesign + -notarize-ac \t Apple account name for notarization purposes +\t\t\t script will attempt to get password from keychain, if fails provide one with +\t\t\t the -notarize-pass option: To add a password run + +\t\t\t security add-generic-password -a \"AC_USERNAME\" -w -s \"KRITA_AC_PASS\" + + -notarize-pass \t If given, the Apple account password. Otherwise an attempt will be macdeployqt_exists +\t\t\t to get the password from keychain using the account given in option. + + -style \t\t Style file defined from 'dmgstyle.sh' output + + -bg \t\t Set a background image for dmg folder. +\t\t\t osxdeploy needs an input image to attach to the dmg background +\t\t\t image recommended size is at least 950x500 +" +} # Attempt to detach previous mouted DMG if [[ -d "/Volumes/${DMG_title}" ]]; then echo "WARNING: Another Krita DMG is mounted!" echo "Attempting eject…" hdiutil detach "/Volumes/${DMG_title}" if [ $? -ne 0 ]; then exit fi echo "Success!" fi -# Parse input args +# -- Parse input args for arg in "${@}"; do if [ "${arg}" = -bg=* -a -f "${arg#*=}" ]; then DMG_validBG=0 bg_filename=${arg#*=} echo "attempting to check background is valid jpg or png..." BG_FORMAT=$(sips --getProperty format ${bg_filename} | awk '{printf $2}') if [[ "png" = ${BG_FORMAT} || "jpeg" = ${BG_FORMAT} ]];then echo "valid image file" DMG_background=$(cd "$(dirname "${bg_filename}")"; pwd -P)/$(basename "${bg_filename}") DMG_validBG=1 # check imageDPI BG_DPI=$(sips --getProperty dpiWidth ${DMG_background} | grep dpi | awk '{print $2}') if [[ $(echo "${BG_DPI} > 150" | bc -l) -eq 1 ]]; then printf "WARNING: image dpi has an effect on apparent size! Check dpi is adequate for screen display if image appears very small Current dpi is: %s\n" ${BG_DPI} fi fi fi # If string starts with -sign if [[ ${arg} = -s=* ]]; then CODE_SIGNATURE="${arg#*=}" fi + if [[ ${arg} = -notarize-ac=* ]]; then + NOTARIZE_ACC="${arg#*=}" + fi + + if [[ ${arg} = -notarize-pass=* ]]; then + NOTARIZE_PASS="${arg#*=}" + fi + if [[ ${arg} = -style=* ]]; then style_filename="${arg#*=}" if [[ -f "${style_filename}" ]]; then DMG_STYLE="${style_filename}" fi fi if [[ ${arg} = "-h" || ${arg} = "--help" ]]; then print_usage exit fi done +# -- Checks and messages + +### PYTHONAttempt to find python_version +local_PY_MAYOR_VERSION=$(python -c "import sys; print(sys.version_info[0])") +local_PY_MINOR_VERSION=$(python -c "import sys; print(sys.version_info[1])") +PY_VERSION="${local_PY_MAYOR_VERSION}.${local_PY_MINOR_VERSION}" + +print_msg "Detected Python %s" "${PY_VERSION}" + +### Code Signature & NOTARIZATION +NOTARIZE="false" +if [[ -z "${CODE_SIGNATURE}" ]]; then + echo "WARNING: No code signature provided, Code will not be signed" +else + print_msg "Code will be signed with %s" "${CODE_SIGNATURE}" + ### NOTARIZATION + + if [[ -n "${NOTARIZE_ACC}" ]]; then + security find-generic-password -s "KRITA_AC_PASS" > /dev/null 2>&1 + if [[ ${?} -eq 0 || -n "${NOTARIZE_PASS}" ]]; then + NOTARIZE="true" + else + echo "No password given for notarization or KRITA_AC_PASS missig in keychain" + fi + fi +fi + +if [[ ${NOTARIZE} = "true" ]]; then + print_msg "Notarization checks complete, This build will be notarized" +else + echo "WARNING: Account information missing, Notarization will not be performed" +fi + +### STYLE for DMG if [[ ! ${DMG_STYLE} ]]; then DMG_STYLE="${SCRIPT_SOURCE_DIR}/default.style" fi -echo "Using style from: ${DMG_STYLE}" +print_msg "Using style from: %s" "${DMG_STYLE}" + +### Background for DMG if [[ ${DMG_validBG} -eq 0 ]]; then echo "No jpg or png valid file detected!!" echo "Using default style" DMG_background="${SCRIPT_SOURCE_DIR}/krita_dmgBG.jpg" fi -if [[ -z "${CODE_SIGNATURE}" ]]; then - echo "WARNING: No signature provided, Code will not be signed" -else - printf 'Code will be signed with "%s"\n' "${CODE_SIGNATURE}" -fi + # Helper functions countArgs () { echo "${#}" } stringContains () { echo "$(grep "${2}" <<< "${1}")" } +waiting_fixed() { + local message="${1}" + local waitTime=${2} + + for i in $(seq ${waitTime}); do + sleep 1 + printf -v dots '%*s' ${i} + printf -v spaces '%*s' $((${waitTime} - $i)) + printf "\r%s [%s%s]" "${message}" "${dots// /.}" "${spaces}" + done + printf "\n" +} + add_lib_to_list() { local llist=${2} if test -z "$(grep ${1##*/} <<< ${llist})" ; then local llist="${llist} ${1##*/} " fi echo "${llist}" } # Find all @rpath and Absolute to buildroot path libs # Add to libs_used # converts absolute buildroot path to @rpath find_needed_libs () { - # echo "Analizing libraries with oTool..." >&2 + # echo "Analyzing libraries with oTool..." >&2 local libs_used="" # input lib_lists founded for libFile in ${@}; do if test -z "$(file ${libFile} | grep 'Mach-O')" ; then # echo "skipping ${libFile}" >&2 continue fi oToolResult=$(otool -L ${libFile} | awk '{print $1}') resultArray=(${oToolResult}) # convert to array for lib in ${resultArray[@]:1}; do if test "${lib:0:1}" = "@"; then local libs_used=$(add_lib_to_list "${lib}" "${libs_used}") fi if [[ "${lib:0:${#BUILDROOT}}" = "${BUILDROOT}" ]]; then printf "Fixing %s: %s\n" "${libFile#${KRITA_DMG}/}" "${lib##*/}" >&2 if [[ "${lib##*/}" = "${libFile##*/}" ]]; then install_name_tool -id ${lib##*/} "${libFile}" else install_name_tool -change ${lib} "@rpath/${lib##*${BUILDROOT}/i/lib/}" "${libFile}" local libs_used=$(add_lib_to_list "${lib}" "${libs_used}") fi fi done done echo "${libs_used}" # return updated list } find_missing_libs (){ # echo "Searching for missing libs on deployment folders…" >&2 local libs_missing="" for lib in ${@}; do if test -z "$(find ${KRITA_DMG}/krita.app/Contents/ -name ${lib})"; then # echo "Adding ${lib} to missing libraries." >&2 libs_missing="${libs_missing} ${lib}" fi done echo "${libs_missing}" } copy_missing_libs () { for lib in ${@}; do result=$(find -L "${BUILDROOT}/i" -name "${lib}") if test $(countArgs ${result}) -eq 1; then if [ "$(stringContains "${result}" "plugin")" ]; then cp -pv ${result} ${KRITA_DMG}/krita.app/Contents/PlugIns/ krita_findmissinglibs "${KRITA_DMG}/krita.app/Contents/PlugIns/${result##*/}" else cp -pv ${result} ${KRITA_DMG}/krita.app/Contents/Frameworks/ krita_findmissinglibs "${KRITA_DMG}/krita.app/Contents/Frameworks/${result##*/}" fi else echo "${lib} might be a missing framework" if [ "$(stringContains "${result}" "framework")" ]; then echo "copying framework ${BUILDROOT}/i/lib/${lib}.framework to dmg" # rsync only included ${lib} Resources Versions rsync -priul ${BUILDROOT}/i/lib/${lib}.framework/${lib} ${KRITA_DMG}/krita.app/Contents/Frameworks/${lib}.framework/ rsync -priul ${BUILDROOT}/i/lib/${lib}.framework/Resources ${KRITA_DMG}/krita.app/Contents/Frameworks/${lib}.framework/ rsync -priul ${BUILDROOT}/i/lib/${lib}.framework/Versions ${KRITA_DMG}/krita.app/Contents/Frameworks/${lib}.framework/ krita_findmissinglibs "$(find "${KRITA_DMG}/krita.app/Contents/Frameworks/${lib}.framework/" -type f -perm 755)" fi fi done } krita_findmissinglibs() { neededLibs=$(find_needed_libs "${@}") missingLibs=$(find_missing_libs ${neededLibs}) if test $(countArgs ${missingLibs}) -gt 0; then printf "Found missing libs: %s\n" "${missingLibs}" copy_missing_libs ${missingLibs} fi } strip_python_dmginstall() { # reduce size of framework python # Removes tests, installers, pyenv, distutils - echo "Removing unnecesary files from Python.Framework to be packaged..." + echo "Removing unnecessary files from Python.Framework to be packaged..." PythonFrameworkBase="${KRITA_DMG}/krita.app/Contents/Frameworks/Python.framework" cd ${PythonFrameworkBase} find . -name "test*" -type d | xargs rm -rf find "${PythonFrameworkBase}/Versions/${PY_VERSION}/bin" -not -name "python*" \( -type f -or -type l \) | xargs rm -f cd "${PythonFrameworkBase}/Versions/${PY_VERSION}/lib/python${PY_VERSION}" rm -rf distutils tkinter ensurepip venv lib2to3 idlelib } # Some libraries require r_path to be removed # we must not apply delete rpath globally delete_install_rpath() { xargs -P4 -I FILE install_name_tool -delete_rpath "${BUILDROOT}/i/lib" FILE 2> "${BUILDROOT}/deploy_error.log" } fix_python_framework() { # Fix python.framework rpath and slims down installation PythonFrameworkBase="${KRITA_DMG}/krita.app/Contents/Frameworks/Python.framework" # Fix main library pythonLib="${PythonFrameworkBase}/Python" install_name_tool -id "${pythonLib##*/}" "${pythonLib}" install_name_tool -add_rpath @loader_path/../../../ "${pythonLib}" 2> /dev/null install_name_tool -change @loader_path/../../../../libintl.9.dylib @loader_path/../../../libintl.9.dylib "${pythonLib}" # Fix all executables install_name_tool -add_rpath @executable_path/../../../../../../../ "${PythonFrameworkBase}/Versions/Current/Resources/Python.app/Contents/MacOS/Python" install_name_tool -change "${KIS_INSTALL_DIR}/lib/Python.framework/Versions/${PY_VERSION}/Python" @executable_path/../../../../../../Python "${PythonFrameworkBase}/Versions/Current/Resources/Python.app/Contents/MacOS/Python" install_name_tool -add_rpath @executable_path/../../../../ "${PythonFrameworkBase}/Versions/Current/bin/python${PY_VERSION}" install_name_tool -add_rpath @executable_path/../../../../ "${PythonFrameworkBase}/Versions/Current/bin/python${PY_VERSION}m" # Fix rpaths from Python.Framework find ${PythonFrameworkBase} -type f -perm 755 | delete_install_rpath find "${PythonFrameworkBase}/Versions/Current/site-packages/PyQt5" -type f -name "*.so" | delete_install_rpath } # Checks for macdeployqt # If not present attempts to install # If it fails shows an informatve message # (For now, macdeployqt is fundamental to deploy) macdeployqt_exists() { printf "Checking for macdeployqt... " if [[ ! -e "${KIS_INSTALL_DIR}/bin/macdeployqt" ]]; then printf "Not Found!\n" printf "Attempting to install macdeployqt\n" cd ${BUILDROOT}/depbuild/ext_qt/ext_qt-prefix/src/ext_qt/qttools/src make sub-macdeployqt-all make sub-macdeployqt-install_subtargets make install if [[ ! -e "${KIS_INSTALL_DIR}/bin/macdeployqt" ]]; then printf " ERROR: Failed to install macdeployqt! Compile and install from qt source directory Source code to build could be located in qttools/src in qt source dir: ${BUILDROOT}/depbuild/ext_qt/ext_qt-prefix/src/ext_qt/qttools/src From the source dir, build and install: make sub-macdeployqt-all make sub-macdeployqt-install_subtargets make install " printf "\nexiting...\n" exit else echo "Done!" fi else echo "Found!" fi } krita_deploy () { # check for macdeployqt macdeployqt_exists cd ${BUILDROOT} # Update files in krita.app echo "Deleting previous kritadmg run..." rm -rf ./krita.dmg ${KRITA_DMG} # Copy new builtFiles echo "Preparing ${KRITA_DMG} for deployment..." echo "Copying krita.app..." mkdir "${KRITA_DMG}" rsync -prul ${KIS_INSTALL_DIR}/bin/krita.app ${KRITA_DMG} mkdir -p ${KRITA_DMG}/krita.app/Contents/PlugIns mkdir -p ${KRITA_DMG}/krita.app/Contents/Frameworks echo "Copying share..." # Deletes old copies of translation and qml to be recreated cd ${KIS_INSTALL_DIR}/share/ rsync -prul --delete ./ \ --exclude krita_SRCS.icns \ --exclude aclocal \ --exclude doc \ --exclude ECM \ --exclude eigen3 \ --exclude emacs \ --exclude gettext \ --exclude gettext-0.19.8 \ --exclude info \ --exclude kf5 \ --exclude kservices5 \ --exclude man \ --exclude ocio \ --exclude pkgconfig \ --exclude mime \ --exclude translations \ --exclude qml \ ${KRITA_DMG}/krita.app/Contents/Resources cd ${BUILDROOT} echo "Copying translations..." rsync -prul ${KIS_INSTALL_DIR}/translations/ \ ${KRITA_DMG}/krita.app/Contents/Resources/translations echo "Copying kritaquicklook..." mkdir -p ${KRITA_DMG}/krita.app/Contents/Library/QuickLook rsync -prul ${KIS_INSTALL_DIR}/plugins/kritaquicklook.qlgenerator ${KRITA_DMG}/krita.app/Contents/Library/QuickLook cd ${KRITA_DMG}/krita.app/Contents ln -shF Resources share echo "Copying qml..." rsync -prul ${KIS_INSTALL_DIR}/qml Resources/qml echo "Copying plugins..." # exclude kritaquicklook.qlgenerator/ cd ${KIS_INSTALL_DIR}/plugins/ rsync -prul --delete --delete-excluded ./ \ --exclude kritaquicklook.qlgenerator \ ${KRITA_DMG}/krita.app/Contents/PlugIns cd ${BUILDROOT} rsync -prul ${KIS_INSTALL_DIR}/lib/kritaplugins/ ${KRITA_DMG}/krita.app/Contents/PlugIns # rsync -prul {KIS_INSTALL_DIR}/lib/libkrita* Frameworks/ # To avoid errors macdeployqt must be run from bin location # ext_qt will not build macdeployqt by default so it must be build manually # cd ${BUILDROOT}/depbuild/ext_qt/ext_qt-prefix/src/ext_qt/qttools/src # make sub-macdeployqt-all # make sub-macdeployqt-install_subtargets # make install echo "Running macdeployqt..." cd ${KIS_INSTALL_DIR}/bin ./macdeployqt ${KRITA_DMG}/krita.app \ -verbose=0 \ -executable=${KRITA_DMG}/krita.app/Contents/MacOS/krita \ -libpath=${KIS_INSTALL_DIR}/lib \ -qmldir=${KIS_INSTALL_DIR}/qml \ # -extra-plugins=${KIS_INSTALL_DIR}/lib/kritaplugins \ # -extra-plugins=${KIS_INSTALL_DIR}/lib/plugins \ # -extra-plugins=${KIS_INSTALL_DIR}/plugins cd ${BUILDROOT} echo "macdeployqt done!" echo "Copying python..." # Copy this framework last! # It is best that macdeployqt does not modify Python.framework # folders with period in name are treated as Frameworks for codesign rsync -prul ${KIS_INSTALL_DIR}/lib/Python.framework ${KRITA_DMG}/krita.app/Contents/Frameworks/ rsync -prul ${KIS_INSTALL_DIR}/lib/krita-python-libs ${KRITA_DMG}/krita.app/Contents/Frameworks/ # change perms on Python to allow header change chmod +w ${KRITA_DMG}/krita.app/Contents/Frameworks/Python.framework/Python fix_python_framework strip_python_dmginstall # fix python pyc # precompile all pyc so the dont alter signature echo "Precompiling all python files..." cd ${KRITA_DMG}/krita.app ${KIS_INSTALL_DIR}/bin/python -m compileall . &> /dev/null install_name_tool -delete_rpath @loader_path/../../../../lib ${KRITA_DMG}/krita.app/Contents/MacOS/krita rm -rf ${KRITA_DMG}/krita.app/Contents/PlugIns/kf5/org.kde.kwindowsystem.platforms # repair krita for plugins printf "Searching for missing libraries\n" krita_findmissinglibs $(find ${KRITA_DMG}/krita.app/Contents -type f -perm 755 -or -name "*.dylib" -or -name "*.so") printf "removing absolute or broken linksys, if any\n" find "${KRITA_DMG}/krita.app/Contents" -type l \( -lname "/*" -or -not -exec test -e {} \; \) -print | xargs rm echo "Done!" } + # helper to define function only once batch_codesign() { - xargs -P4 -I FILE codesign -f -s "${CODE_SIGNATURE}" FILE + xargs -P4 -I FILE codesign --options runtime --timestamp -f -s "${CODE_SIGNATURE}" FILE } # Code sign must be done as recommended by apple "sign code inside out in individual stages" signBundle() { cd ${KRITA_DMG} # sign Frameworks and libs cd ${KRITA_DMG}/krita.app/Contents/Frameworks - # remove debug version as both versions cant be signed. + # remove debug version as both versions can't be signed. rm ${KRITA_DMG}/krita.app/Contents/Frameworks/QtScript.framework/Versions/Current/QtScript_debug find . -type f -perm 755 -or -name "*.dylib" -or -name "*.so" | batch_codesign find . -type d -name "*.framework" | xargs printf "%s/Versions/Current\n" | batch_codesign # Sign all other files in Framework (needed) # there are many files in python do we need to sign them all? find krita-python-libs -type f | batch_codesign # find python -type f | batch_codesign # Sing only libraries and plugins cd ${KRITA_DMG}/krita.app/Contents/PlugIns find . -type f | batch_codesign cd ${KRITA_DMG}/krita.app/Contents/Library/QuickLook printf "kritaquicklook.qlgenerator" | batch_codesign # It is recommended to sign every Resource file cd ${KRITA_DMG}/krita.app/Contents/Resources find . -type f | batch_codesign #Finally sign krita and krita.app printf "${KRITA_DMG}/krita.app/Contents/MacOS/krita" | batch_codesign printf "${KRITA_DMG}/krita.app" | batch_codesign } +# Notarize build on macOS servers +# based on https://github.com/Beep6581/RawTherapee/blob/6fa533c40b34dec527f1176d47cc6c683422a73f/tools/osx/macosx_bundle.sh#L225-L250 +notarize_build() { + local NOT_SRC_DIR=${1} + local NOT_SRC_FILE=${2} + + if [[ ${NOTARIZE} = "true" ]]; then + printf "performing notarization of %s\n" "${2}" + cd "${NOT_SRC_DIR}" + + if [[ -z "${NOTARIZE_PASS}" ]]; then + NOTARIZE_PASS="@keychain:KRITA_AC_PASS" + fi + + ditto -c -k --sequesterRsrc --keepParent "${NOT_SRC_FILE}" "${BUILDROOT}/tmp_notarize/${NOT_SRC_FILE}.zip" + + # echo "xcrun altool --notarize-app --primary-bundle-id \"org.krita\" --username \"${NOTARIZE_ACC}\" --password \"${NOTARIZE_PASS}\" --file \"${BUILDROOT}/tmp_notarize/${NOT_SRC_FILE}.zip\"" + local altoolResponse="$(xcrun altool --notarize-app --primary-bundle-id "org.krita" --username "${NOTARIZE_ACC}" --password "${NOTARIZE_PASS}" --file "${BUILDROOT}/tmp_notarize/${NOT_SRC_FILE}.zip" 2>&1)" + + if [[ -n "$(grep 'Error' <<< ${altoolResponse})" ]]; then + printf "ERROR: xcrun altool exited with the following error! \n\n%s\n\n" "${altoolResponse}" + printf "This could mean there is an error in AppleID authentication!\n" + printf "aborting notarization\n" + NOTARIZE="false" + return + else + printf "Response:\n\n%s\n\n" "${altoolResponse}" + fi + + local uuid="$(grep 'RequestUUID' <<< ${altoolResponse} | awk '{ print $3 }')" + echo "RequestUUID = ${uuid}" # Display identifier string + + waiting_fixed "Waiting to retrieve notarize status" 15 + + while true ; do + fullstatus=$(xcrun altool --notarization-info "${uuid}" --username "${NOTARIZE_ACC}" --password "${NOTARIZE_PASS}" 2>&1) # get the status + notarize_status=`echo "${fullstatus}" | grep 'Status\:' | awk '{ print $2 }'` + echo "${fullstatus}" + if [[ "${notarize_status}" = "success" ]]; then + xcrun stapler staple "${NOT_SRC_FILE}" #staple the ticket + xcrun stapler validate -v "${NOT_SRC_FILE}" + print_msg "Notarization success!" + break + elif [[ "${notarize_status}" = "in" ]]; then + waiting_fixed "Notarization still in progress, sleeping for 15 seconds and trying again" 15 + else + echo "Notarization failed! full status below" + echo "${fullstatus}" + exit 1 + fi + done + fi +} + createDMG () { printf "Creating of dmg with contents of %s...\n" "${KRITA_DMG}" cd ${BUILDROOT} DMG_size=700 ## Build dmg from folder # create dmg on local system # usage of -fsargs minimze gaps at front of filesystem (reduce size) hdiutil create -srcfolder "${KRITA_DMG}" -volname "${DMG_title}" -fs HFS+ \ -fsargs "-c c=64,a=16,e=16" -format UDRW -size ${DMG_size}m krita.temp.dmg # Next line is only useful if we have a dmg as a template! # previous hdiutil must be uncommented # cp krita-template.dmg krita.dmg device=$(hdiutil attach -readwrite -noverify -noautoopen "krita.temp.dmg" | egrep '^/dev/' | sed 1q | awk '{print $1}') # rsync -priul --delete ${KRITA_DMG}/krita.app "/Volumes/${DMG_title}" # Set style for dmg if [[ ! -d "/Volumes/${DMG_title}/.background" ]]; then mkdir "/Volumes/${DMG_title}/.background" fi cp -v ${DMG_background} "/Volumes/${DMG_title}/.background/" mkdir "/Volumes/${DMG_title}/Terms of Use" cp -v "${KIS_SRC_DIR}/packaging/macos/Terms_of_use.rtf" "/Volumes/${DMG_title}/Terms of Use/" ln -s "/Applications" "/Volumes/${DMG_title}/Applications" ## Apple script to set style style="$(<"${DMG_STYLE}")" printf "${style}" "${DMG_title}" "${DMG_background##*/}" | osascript #Set Icon for DMG cp -v "${SCRIPT_SOURCE_DIR}/KritaIcon.icns" "/Volumes/${DMG_title}/.VolumeIcon.icns" SetFile -a C "/Volumes/${DMG_title}" chmod -Rf go-w "/Volumes/${DMG_title}" - # ensure all writting operations to dmg are over + # ensure all writing operations to dmg are over sync hdiutil detach $device hdiutil convert "krita.temp.dmg" -format UDZO -imagekey -zlib-level=9 -o krita-out.dmg # Add git version number GIT_SHA=$(grep "#define KRITA_GIT_SHA1_STRING" ${KIS_BUILD_DIR}/libs/version/kritagitversion.h | awk '{gsub(/"/, "", $3); printf $3}') mv krita-out.dmg krita-nightly_${GIT_SHA}.dmg echo "moved krita-out.dmg to krita-nightly_${GIT_SHA}.dmg" rm krita.temp.dmg if [[ -n "${CODE_SIGNATURE}" ]]; then printf "krita-nightly_${GIT_SHA}.dmg" | batch_codesign fi + notarize_build ${BUILDROOT} "krita-nightly_${GIT_SHA}.dmg" + echo "dmg done!" } ####################### # Program starts!! ######################## -# Run deploy command, instalation is assumed to exist in BUILDROOT/i +# Run deploy command, installation is assumed to exist in BUILDROOT/i krita_deploy # Code sign krita.app if signature given if [[ -n "${CODE_SIGNATURE}" ]]; then signBundle fi -# Create DMG from files insiede ${KRITA_DMG} folder + +notarize_build ${KRITA_DMG} krita.app + +# Create DMG from files inside ${KRITA_DMG} folder createDMG + +if [[ "${NOTARIZE}" = "false" ]]; then + macosVersion="$(sw_vers | grep ProductVersion | awk ' + BEGIN { FS = "[ .\t]" } + { print $3} + ')" + if (( ${macosVersion} == 15 )); then + print_error "Build not notarized! Needed for macOS versions above 10.14" + fi +fi diff --git a/packaging/windows/installer/installer_krita.nsi b/packaging/windows/installer/installer_krita.nsi index 41140f640e..20819075e6 100644 --- a/packaging/windows/installer/installer_krita.nsi +++ b/packaging/windows/installer/installer_krita.nsi @@ -1,662 +1,662 @@ !ifndef KRITA_INSTALLER_32 & KRITA_INSTALLER_64 !error "Either one of KRITA_INSTALLER_32 or KRITA_INSTALLER_64 must be defined." !endif !ifdef KRITA_INSTALLER_32 & KRITA_INSTALLER_64 !error "Only one of KRITA_INSTALLER_32 or KRITA_INSTALLER_64 should be defined." !endif !ifndef KRITA_PACKAGE_ROOT !error "KRITA_PACKAGE_ROOT should be defined and point to the root of the package files." !endif !ifdef KRITA_INSTALLER_64 !define KRITA_INSTALLER_BITNESS 64 !else !define KRITA_INSTALLER_BITNESS 32 !endif Unicode true ManifestDPIAware true # Krita constants (can be overridden in command line params) !define /ifndef KRITA_VERSION "0.0.0.0" !define /ifndef KRITA_VERSION_DISPLAY "test-version" #!define /ifndef KRITA_VERSION_GIT "" !define /ifndef KRITA_INSTALLER_OUTPUT_DIR "" !ifdef KRITA_INSTALLER_64 !define /ifndef KRITA_INSTALLER_OUTPUT_NAME "krita_x64_setup.exe" !else !define /ifndef KRITA_INSTALLER_OUTPUT_NAME "krita_x86_setup.exe" !endif # Krita constants (fixed) !if "${KRITA_INSTALLER_OUTPUT_DIR}" == "" !define KRITA_INSTALLER_OUTPUT "${KRITA_INSTALLER_OUTPUT_NAME}" !else !define KRITA_INSTALLER_OUTPUT "${KRITA_INSTALLER_OUTPUT_DIR}\${KRITA_INSTALLER_OUTPUT_NAME}" !endif !define KRTIA_PUBLISHER "Krita Foundation" !ifdef KRITA_INSTALLER_64 !define KRITA_PRODUCTNAME "Krita (x64)" !define KRITA_UNINSTALL_REGKEY "Krita_x64" !else !define KRITA_PRODUCTNAME "Krita (x86)" !define KRITA_UNINSTALL_REGKEY "Krita_x86" !endif VIProductVersion "${KRITA_VERSION}" VIAddVersionKey "CompanyName" "${KRTIA_PUBLISHER}" VIAddVersionKey "FileDescription" "${KRITA_PRODUCTNAME} ${KRITA_VERSION_DISPLAY} Setup" VIAddVersionKey "FileVersion" "${KRITA_VERSION}" VIAddVersionKey "InternalName" "${KRITA_INSTALLER_OUTPUT_NAME}" VIAddVersionKey "LegalCopyright" "${KRTIA_PUBLISHER}" VIAddVersionKey "OriginalFileName" "${KRITA_INSTALLER_OUTPUT_NAME}" VIAddVersionKey "ProductName" "${KRITA_PRODUCTNAME} ${KRITA_VERSION_DISPLAY} Setup" VIAddVersionKey "ProductVersion" "${KRITA_VERSION}" BrandingText "[NSIS ${NSIS_VERSION}] ${KRITA_PRODUCTNAME} ${KRITA_VERSION}" Name "${KRITA_PRODUCTNAME} ${KRITA_VERSION_DISPLAY}" OutFile ${KRITA_INSTALLER_OUTPUT} !ifdef KRITA_INSTALLER_64 InstallDir "$PROGRAMFILES64\Krita (x64)" !else InstallDir "$PROGRAMFILES32\Krita (x86)" !endif XPstyle on ShowInstDetails show ShowUninstDetails show Var KritaStartMenuFolder Var CreateDesktopIcon !include MUI2.nsh !define MUI_FINISHPAGE_NOAUTOCLOSE # Installer Pages !insertmacro MUI_PAGE_WELCOME !define MUI_LICENSEPAGE_CHECKBOX !insertmacro MUI_PAGE_LICENSE "license_gpl-3.0.rtf" !insertmacro MUI_PAGE_DIRECTORY !insertmacro MUI_PAGE_COMPONENTS !define MUI_PAGE_CUSTOMFUNCTION_PRE func_ShellExLicensePage_Init !define MUI_PAGE_HEADER_TEXT "License Agreement (Krita Shell Extension)" !insertmacro MUI_PAGE_LICENSE "license.rtf" !define MUI_STARTMENUPAGE_DEFAULTFOLDER "Krita" !define MUI_STARTMENUPAGE_REGISTRY_ROOT HKLM !define MUI_STARTMENUPAGE_REGISTRY_KEY "Software\Krita" !define MUI_STARTMENUPAGE_REGISTRY_VALUENAME "StartMenuFolder" !define MUI_STARTMENUPAGE_NODISABLE !insertmacro MUI_PAGE_STARTMENU Krita $KritaStartMenuFolder Page Custom func_DesktopShortcutPage_Init Page Custom func_BeforeInstallPage_Init !insertmacro MUI_PAGE_INSTFILES !insertmacro MUI_PAGE_FINISH # Uninstaller Pages !insertmacro MUI_UNPAGE_CONFIRM !insertmacro MUI_UNPAGE_INSTFILES !insertmacro MUI_LANGUAGE "English" !include Sections.nsh !include LogicLib.nsh !include x64.nsh !include WinVer.nsh !include WordFunc.nsh !define KRITA_SHELLEX_DIR "$INSTDIR\shellex" !include "include\FileExists2.nsh" !include "include\IsFileInUse.nsh" !include "krita_versions_detect.nsh" !include "krita_shell_integration.nsh" Var KritaMsiProductX86 Var KritaMsiProductX64 Var KritaNsisVersion Var KritaNsisBitness Var KritaNsisInstallLocation Var PrevShellExInstallLocation Var PrevShellExStandalone Var UninstallShellExStandalone Section "-Remove_shellex" SEC_remove_shellex ${If} $PrevShellExInstallLocation != "" ${AndIf} $PrevShellExStandalone == 1 ${AndIf} $KritaNsisVersion == "" ${AndIf} ${FileExists} "$PrevShellExInstallLocation\uninstall.exe" push $R0 DetailPrint "Removing Krita Shell Integration..." SetDetailsPrint listonly ExecWait "$PrevShellExInstallLocation\uninstall.exe /S _?=$PrevShellExInstallLocation" $R0 ${If} $R0 != 0 ${IfNot} ${Silent} MessageBox MB_OK|MB_ICONSTOP "Failed to remove Krita Shell Integration." ${EndIf} SetDetailsPrint both DetailPrint "Failed to remove Krita Shell Integration." Abort ${EndIf} Delete "$PrevShellExInstallLocation\uninstall.exe" RMDir /REBOOTOK "$PrevShellExInstallLocation" SetRebootFlag false SetDetailsPrint lastused DetailPrint "Krita Shell Integration removed." pop $R0 ${EndIf} SectionEnd Section "Remove Old Version" SEC_remove_old_version ${If} $KritaNsisInstallLocation != "" ${AndIf} ${FileExists} "$KritaNsisInstallLocation\uninstall.exe" push $R0 DetailPrint "Removing previous version..." SetDetailsPrint listonly ExecWait "$KritaNsisInstallLocation\uninstall.exe /S _?=$KritaNsisInstallLocation" $R0 ${If} $R0 != 0 ${IfNot} ${Silent} MessageBox MB_OK|MB_ICONSTOP "Failed to remove previous version of Krita." ${EndIf} SetDetailsPrint both DetailPrint "Failed to remove previous version of Krita." Abort ${EndIf} Delete "$KritaNsisInstallLocation\uninstall.exe" RMDir /REBOOTOK "$KritaNsisInstallLocation" SetRebootFlag false SetDetailsPrint lastused DetailPrint "Previous version removed." pop $R0 ${EndIf} SectionEnd Section "-Thing" SetOutPath $INSTDIR WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${KRITA_UNINSTALL_REGKEY}" \ "DisplayName" "${KRITA_PRODUCTNAME} ${KRITA_VERSION_DISPLAY}" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${KRITA_UNINSTALL_REGKEY}" \ "UninstallString" "$\"$INSTDIR\uninstall.exe$\"" WriteUninstaller $INSTDIR\uninstall.exe WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${KRITA_UNINSTALL_REGKEY}" \ "DisplayVersion" "${KRITA_VERSION}" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${KRITA_UNINSTALL_REGKEY}" \ "DisplayIcon" "$\"$INSTDIR\shellex\krita.ico$\",0" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${KRITA_UNINSTALL_REGKEY}" \ "URLInfoAbout" "https://krita.org/" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${KRITA_UNINSTALL_REGKEY}" \ "InstallLocation" "$INSTDIR" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${KRITA_UNINSTALL_REGKEY}" \ "Publisher" "${KRTIA_PUBLISHER}" #WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${KRITA_UNINSTALL_REGKEY}" \ # "EstimatedSize" 250000 WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${KRITA_UNINSTALL_REGKEY}" \ "NoModify" 1 WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${KRITA_UNINSTALL_REGKEY}" \ "NoRepair" 1 # Registry entries for version recognition # InstallLocation: # Where krita is installed WriteRegStr HKLM "Software\Krita" \ "InstallLocation" "$INSTDIR" # Version: # Version of Krita WriteRegStr HKLM "Software\Krita" \ "Version" "${KRITA_VERSION}" # x64: # Set to 1 for 64-bit Krita, can be missing for 32-bit Krita !ifdef KRITA_INSTALLER_64 WriteRegDWORD HKLM "Software\Krita" \ "x64" 1 !else DeleteRegValue HKLM "Software\Krita" "x64" !endif # StartMenuFolder: # Start Menu Folder # Handled by Modern UI 2.0 MUI_PAGE_STARTMENU SectionEnd Section "${KRITA_PRODUCTNAME}" SEC_product_main # TODO: Maybe switch to explicit file list? File /r /x ffmpeg.exe /x ffmpeg_README.txt /x ffmpeg_LICENSE.txt ${KRITA_PACKAGE_ROOT}\bin File /r ${KRITA_PACKAGE_ROOT}\lib File /r ${KRITA_PACKAGE_ROOT}\share File /r ${KRITA_PACKAGE_ROOT}\python SectionEnd Section "-Main_associate" CreateDirectory ${KRITA_SHELLEX_DIR} ${Krita_RegisterFileAssociation} "$INSTDIR\bin\krita.exe" SectionEnd Section "-Main_Shortcuts" # Placing this after Krita_RegisterFileAssociation to get the icon !insertmacro MUI_STARTMENU_WRITE_BEGIN Krita CreateDirectory "$SMPROGRAMS\$KritaStartMenuFolder" CreateShortcut "$SMPROGRAMS\$KritaStartMenuFolder\${KRITA_PRODUCTNAME}.lnk" "$INSTDIR\bin\krita.exe" "" "$INSTDIR\shellex\krita.ico" 0 CreateDirectory "$SMPROGRAMS\$KritaStartMenuFolder\Tools" CreateShortcut "$SMPROGRAMS\$KritaStartMenuFolder\Tools\Uninstall ${KRITA_PRODUCTNAME}.lnk" "$INSTDIR\Uninstall.exe" !insertmacro MUI_STARTMENU_WRITE_END ${If} $CreateDesktopIcon == 1 # For the desktop icon, keep the name short and omit version info CreateShortcut "$DESKTOP\Krita.lnk" "$INSTDIR\bin\krita.exe" "" "$INSTDIR\shellex\krita.ico" 0 ${EndIf} SectionEnd Section "Shell Integration" SEC_shellex ${If} ${RunningX64} ${Krita_RegisterComComonents} 64 ${EndIf} ${Krita_RegisterComComonents} 32 ${Krita_RegisterShellExtension} # ShellExtension\InstallLocation: # Where the shell extension is installed # If installed by Krita installer, this must point to shellex sub-dir WriteRegStr HKLM "Software\Krita\ShellExtension" \ "InstallLocation" "$INSTDIR\shellex" # ShellExtension\Version: # Version of the shell extension WriteRegStr HKLM "Software\Krita\ShellExtension" \ "Version" "${KRITASHELLEX_VERSION}" # ShellExtension\Standalone: # 0 = Installed by Krita installer # 1 = Standalone installer WriteRegDWORD HKLM "Software\Krita\ShellExtension" \ "Standalone" 0 # ShellExtension\KritaExePath: # Path to krita.exe as specified by user or by Krita installer # Empty if not specified WriteRegStr HKLM "Software\Krita\ShellExtension" \ "KritaExePath" "$INSTDIR\bin\krita.exe" SectionEnd !ifdef HAS_FFMPEG Section "Bundled FFmpeg" SEC_ffmpeg File /oname=bin\ffmpeg.exe ${KRITA_PACKAGE_ROOT}\bin\ffmpeg.exe File /oname=bin\ffmpeg_LICENSE.txt ${KRITA_PACKAGE_ROOT}\bin\ffmpeg_LICENSE.txt File /oname=bin\ffmpeg_README.txt ${KRITA_PACKAGE_ROOT}\bin\ffmpeg_README.txt SectionEnd !endif Section "-Main_refreshShell" ${RefreshShell} SectionEnd !insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN !insertmacro MUI_DESCRIPTION_TEXT ${SEC_remove_shellex} "Remove previously installed Krita Shell Integration." !insertmacro MUI_DESCRIPTION_TEXT ${SEC_remove_old_version} "Remove previously installed Krita $KritaNsisVersion ($KritaNsisBitness-bit)." !insertmacro MUI_DESCRIPTION_TEXT ${SEC_product_main} "${KRITA_PRODUCTNAME} ${KRITA_VERSION_DISPLAY}$\r$\n$\r$\nVersion: ${KRITA_VERSION}" !insertmacro MUI_DESCRIPTION_TEXT ${SEC_shellex} "Shell Extension component to provide thumbnails and file properties display for Krita files.$\r$\n$\r$\nVersion: ${KRITASHELLEX_VERSION}" !ifdef HAS_FFMPEG !insertmacro MUI_DESCRIPTION_TEXT ${SEC_ffmpeg} "Install a bundled version of FFmpeg for exporting animations." !endif !insertmacro MUI_FUNCTION_DESCRIPTION_END Section "un.Shell Integration" ${If} $UninstallShellExStandalone == 1 push $R0 DetailPrint "Removing Krita Shell Integration..." SetDetailsPrint listonly ExecWait "$INSTDIR\shellex\uninstall.exe /S _?=$INSTDIR\shellex" $R0 ${If} $R0 != 0 ${IfNot} ${Silent} MessageBox MB_OK|MB_ICONSTOP "Failed to remove Krita Shell Integration. Please report this bug!" ${EndIf} SetDetailsPrint lastused SetDetailsPrint both DetailPrint "Failed to remove Krita Shell Integration." ${EndIf} Delete "$INSTDIR\shellex\uninstall.exe" RMDir /REBOOTOK "$INSTDIR\shellex" SetDetailsPrint lastused DetailPrint "Krita Shell Integration removed." pop $R0 ${Else} ${Krita_UnregisterShellExtension} ${If} ${RunningX64} ${Krita_UnregisterComComonents} 64 ${EndIf} ${Krita_UnregisterComComonents} 32 ${EndIf} SectionEnd Section "un.Main_associate" # TODO: Conditional, use install log ${If} $UninstallShellExStandalone != 1 ${Krita_UnregisterFileAssociation} ${EndIf} SectionEnd Section "un.Main_Shortcuts" Delete "$DESKTOP\Krita.lnk" !insertmacro MUI_STARTMENU_GETFOLDER Krita $KritaStartMenuFolder Delete "$SMPROGRAMS\$KritaStartMenuFolder\Tools\Uninstall ${KRITA_PRODUCTNAME}.lnk" RMDir "$SMPROGRAMS\$KritaStartMenuFolder\Tools" Delete "$SMPROGRAMS\$KritaStartMenuFolder\${KRITA_PRODUCTNAME}.lnk" RMDir "$SMPROGRAMS\$KritaStartMenuFolder" SectionEnd Section "un.${KRITA_PRODUCTNAME}" # TODO: Maybe switch to explicit file list or some sort of install log? RMDir /r $INSTDIR\bin RMDir /r $INSTDIR\lib RMDir /r $INSTDIR\share RMDir /r $INSTDIR\python SectionEnd Section "un.Thing" RMDir /REBOOTOK $INSTDIR\shellex DeleteRegKey HKLM "Software\Krita" DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${KRITA_UNINSTALL_REGKEY}" Delete $INSTDIR\uninstall.exe RMDir /REBOOTOK $INSTDIR SectionEnd Section "un.Main_refreshShell" ${RefreshShell} SectionEnd Function .onInit SetShellVarContext all !insertmacro SetSectionFlag ${SEC_product_main} ${SF_RO} !insertmacro SetSectionFlag ${SEC_product_main} ${SF_BOLD} !insertmacro SetSectionFlag ${SEC_remove_old_version} ${SF_RO} !ifdef HAS_FFMPEG !insertmacro SetSectionFlag ${SEC_ffmpeg} ${SF_RO} !endif StrCpy $CreateDesktopIcon 1 # Create desktop icon by default ${IfNot} ${AtLeastWin7} ${IfNot} ${Silent} MessageBox MB_OK|MB_ICONSTOP "${KRITA_PRODUCTNAME} ${KRITA_VERSION_DISPLAY} requires Windows 7 or above." ${EndIf} Abort ${EndIf} !ifdef KRITA_INSTALLER_64 ${If} ${RunningX64} SetRegView 64 ${Else} ${IfNot} ${Silent} MessageBox MB_OK|MB_ICONSTOP "You are running 32-bit Windows, but this installer installs Krita 64-bit which can only be installed on 64-bit Windows. Please download the 32-bit version on https://krita.org/" ${EndIf} Abort ${Endif} !else ${If} ${RunningX64} SetRegView 64 ${IfNot} ${Silent} MessageBox MB_YESNO|MB_ICONEXCLAMATION "You are trying to install 32-bit Krita on 64-bit Windows. You are strongly recommended to install the 64-bit version of Krita instead since it offers better performance.$\nIf you want to use the 32-bit version for testing, you should consider using the zip package instead.$\n$\nDo you still wish to install the 32-bit version of Krita?" \ /SD IDYES \ IDYES lbl_allow32on64 Abort ${EndIf} lbl_allow32on64: ${Endif} !endif # Detect other Krita versions ${DetectKritaMsi32bit} $KritaMsiProductX86 ${If} ${RunningX64} ${DetectKritaMsi64bit} $KritaMsiProductX64 ${IfKritaMsi3Alpha} $KritaMsiProductX64 ${IfNot} ${Silent} MessageBox MB_YESNO|MB_ICONQUESTION|MB_DEFBUTTON2 "Krita 3.0 Alpha 1 is installed. It must be removed before ${KRITA_PRODUCTNAME} ${KRITA_VERSION_DISPLAY} can be installed.$\nDo you wish to remove it now?" \ /SD IDYES \ IDYES lbl_removeKrita3alpha Abort ${EndIf} lbl_removeKrita3alpha: push $R0 ${MsiUninstall} $KritaMsiProductX64 $R0 ${If} $R0 != 0 ${IfNot} ${Silent} MessageBox MB_OK|MB_ICONSTOP "Failed to remove Krita 3.0 Alpha 1." ${EndIf} Abort ${EndIf} pop $R0 StrCpy $KritaMsiProductX64 "" ${ElseIf} $KritaMsiProductX64 != "" ${If} $KritaMsiProductX86 != "" ${IfNot} ${Silent} MessageBox MB_YESNO|MB_ICONQUESTION|MB_DEFBUTTON2 "Both 32-bit and 64-bit editions of Krita 2.9 or below are installed.$\nBoth must be removed before ${KRITA_PRODUCTNAME} ${KRITA_VERSION_DISPLAY} can be installed.$\nDo you want to remove them now?" \ /SD IDYES \ IDYES lbl_removeKritaBoth Abort ${EndIf} lbl_removeKritaBoth: push $R0 ${MsiUninstall} $KritaMsiProductX86 $R0 ${If} $R0 != 0 ${IfNot} ${Silent} MessageBox MB_OK|MB_ICONSTOP "Failed to remove Krita (32-bit)." ${EndIf} Abort ${EndIf} ${MsiUninstall} $KritaMsiProductX64 $R0 ${If} $R0 != 0 ${IfNot} ${Silent} MessageBox MB_OK|MB_ICONSTOP "Failed to remove Krita (64-bit)." ${EndIf} Abort ${EndIf} pop $R0 StrCpy $KritaMsiProductX86 "" StrCpy $KritaMsiProductX64 "" ${Else} ${IfNot} ${Silent} MessageBox MB_YESNO|MB_ICONQUESTION|MB_DEFBUTTON2 "Krita (64-bit) 2.9 or below is installed.$\nIt must be removed before ${KRITA_PRODUCTNAME} ${KRITA_VERSION_DISPLAY} can be installed.$\nDo you wish to remove it now?" \ /SD IDYES \ IDYES lbl_removeKritaX64 Abort ${EndIf} lbl_removeKritaX64: push $R0 ${MsiUninstall} $KritaMsiProductX64 $R0 ${If} $R0 != 0 ${IfNot} ${Silent} MessageBox MB_OK|MB_ICONSTOP "Failed to remove Krita (64-bit)." ${EndIf} Abort ${EndIf} pop $R0 StrCpy $KritaMsiProductX64 "" ${EndIf} ${EndIf} ${Endif} ${If} $KritaMsiProductX86 != "" ${IfNot} ${Silent} MessageBox MB_YESNO|MB_ICONQUESTION|MB_DEFBUTTON2 "Krita (32-bit) 2.9 or below is installed.$\nIt must be removed before ${KRITA_PRODUCTNAME} ${KRITA_VERSION_DISPLAY} can be installed.$\nDo you wish to remove it now?" \ /SD IDYES \ IDYES lbl_removeKritaX86 Abort ${EndIf} lbl_removeKritaX86: push $R0 ${MsiUninstall} $KritaMsiProductX86 $R0 ${If} $R0 != 0 ${IfNot} ${Silent} MessageBox MB_OK|MB_ICONSTOP "Failed to remove Krita (32-bit)." ${EndIf} Abort ${EndIf} pop $R0 StrCpy $KritaMsiProductX86 "" ${EndIf} ${DetectKritaNsis} $KritaNsisVersion $KritaNsisBitness $KritaNsisInstallLocation ${If} $KritaNsisVersion != "" push $R0 ${VersionCompare} "${KRITA_VERSION}" "$KritaNsisVersion" $R0 ${If} $R0 == 0 # Same version installed... probably ${If} $KritaNsisBitness == ${KRITA_INSTALLER_BITNESS} # Very likely the same version ${IfNot} ${Silent} MessageBox MB_OK|MB_ICONINFORMATION "It appears that ${KRITA_PRODUCTNAME} ${KRITA_VERSION_DISPLAY} is already installed.$\nThis setup will reinstall it." ${EndIf} ${Else} # Very likely the same version but different arch ${IfNot} ${Silent} !ifdef KRITA_INSTALLER_64 MessageBox MB_OK|MB_ICONINFORMATION "It appears that Krita 32-bit ${KRITA_VERSION_DISPLAY} is currently installed. This setup will replace it with the 64-bit version." !else MessageBox MB_OK|MB_ICONEXCLAMATION "It appears that Krita 64-bit ${KRITA_VERSION_DISPLAY} is currently installed. This setup will replace it with the 32-bit version." !endif ${EndIf} ${EndIf} ${ElseIf} $R0 == 1 # Upgrade ${If} $KritaNsisBitness == ${KRITA_INSTALLER_BITNESS} - # Slient about upgrade + # Silent about upgrade ${Else} # Upgrade but different arch ${IfNot} ${Silent} !ifdef KRITA_INSTALLER_64 MessageBox MB_OK|MB_ICONINFORMATION "It appears that Krita 32-bit ($KritaNsisVersion) is currently installed. This setup will replace it with the 64-bit version of Krita ${KRITA_VERSION_DISPLAY}." !else MessageBox MB_OK|MB_ICONEXCLAMATION "It appears that Krita 64-bit ($KritaNsisVersion) is currently installed. This setup will replace it with the 32-bit version of Krita ${KRITA_VERSION_DISPLAY}." !endif ${EndIf} ${EndIf} ${ElseIf} $R0 == 2 ${IfNot} ${Silent} MessageBox MB_OK|MB_ICONSTOP "It appears that a newer version of Krita $KritaNsisBitness-bit ($KritaNsisVersion) is currently installed. If you want to downgrade Krita to ${KRITA_VERSION_DISPLAY}, please uninstall the newer version manually before running this setup." ${EndIf} Abort ${Else} ${IfNot} ${Silent} MessageBox MB_OK|MB_ICONSTOP "Unexpected state" ${EndIf} Abort ${EndIf} !insertmacro SetSectionFlag ${SEC_remove_old_version} ${SF_SELECTED} # Detect if Krita is running... ${If} ${IsFileinUse} "$KritaNsisInstallLocation\bin\krita.exe" ${IfNot} ${Silent} MessageBox MB_OK|MB_ICONEXCLAMATION "Krita appears to be running. Please close Krita before running this installer." ${EndIf} Abort ${EndIf} pop $R0 ${Else} !insertmacro ClearSectionFlag ${SEC_remove_old_version} ${SF_SELECTED} SectionSetText ${SEC_remove_old_version} "" ${EndIf} # Detect standalone shell extension # TODO: Would it be possible to update Krita without replacing the standalone shellex? ClearErrors ReadRegStr $PrevShellExInstallLocation HKLM "Software\Krita\ShellExtension" "InstallLocation" #ReadRegStr $PrevShellExVersion HKLM "Software\Krita\ShellExtension" "Version" ReadRegDWORD $PrevShellExStandalone HKLM "Software\Krita\ShellExtension" "Standalone" #ReadRegStr $PrevShellExKritaExePath HKLM "Software\Krita\ShellExtension" "KritaExePath" ${If} ${Errors} # TODO: Assume no previous version installed or what? ${EndIf} ${If} $PrevShellExStandalone == 1 ${IfNot} ${Silent} MessageBox MB_YESNO|MB_ICONQUESTION "Krita Shell Integration was installed separately. It will be uninstalled automatically when installing Krita.$\nDo you want to continue?" \ /SD IDYES \ IDYES lbl_allowremoveshellex Abort ${EndIf} lbl_allowremoveshellex: #!insertmacro SetSectionFlag ${SEC_remove_shellex} ${SF_SELECTED} ${Else} #!insertmacro ClearSectionFlag ${SEC_remove_shellex} ${SF_SELECTED} #SectionSetText ${SEC_remove_shellex} "" ${EndIf} FunctionEnd Function un.onInit SetShellVarContext all !ifdef KRITA_INSTALLER_64 ${If} ${RunningX64} SetRegView 64 ${Else} Abort ${Endif} !else ${If} ${RunningX64} SetRegView 64 ${Endif} !endif ReadRegDWORD $UninstallShellExStandalone HKLM "Software\Krita\ShellExtension" "Standalone" ${If} ${IsFileinUse} "$INSTDIR\bin\krita.exe" ${IfNot} ${Silent} MessageBox MB_OK|MB_ICONEXCLAMATION "Krita appears to be running. Please close Krita before uninstalling." ${EndIf} Abort ${EndIf} FunctionEnd Function func_ShellExLicensePage_Init ${IfNot} ${SectionIsSelected} ${SEC_shellex} # Skip ShellEx license page if not selected Abort ${EndIf} FunctionEnd Var hwndChkDesktopIcon Function func_DesktopShortcutPage_Init push $R0 nsDialogs::Create 1018 pop $R0 ${If} $R0 == error Abort ${EndIf} !insertmacro MUI_HEADER_TEXT "Desktop Icon" "Configure desktop shortcut icon." ${NSD_CreateLabel} 0u 0u 300u 20u "You can choose to create a shortcut icon on the desktop for launching Krita." pop $R0 ${NSD_CreateCheckbox} 0u 20u 300u 10u "Create a desktop icon" pop $hwndChkDesktopIcon ${If} $CreateDesktopIcon == 1 ${NSD_Check} $hwndChkDesktopIcon ${Else} ${NSD_Uncheck} $hwndChkDesktopIcon ${EndIf} ${NSD_OnClick} $hwndChkDesktopIcon func_DesktopShortcutPage_CheckChange nsDialogs::Show pop $R0 FunctionEnd Function func_DesktopShortcutPage_CheckChange ${NSD_GetState} $hwndChkDesktopIcon $CreateDesktopIcon ${If} $CreateDesktopIcon == ${BST_CHECKED} StrCpy $CreateDesktopIcon 1 ${Else} StrCpy $CreateDesktopIcon 0 ${EndIf} FunctionEnd Function func_BeforeInstallPage_Init push $R0 nsDialogs::Create 1018 pop $R0 ${If} $R0 == error Abort ${EndIf} !insertmacro MUI_HEADER_TEXT "Confirm Installation" "Confirm installation of ${KRITA_PRODUCTNAME} ${KRITA_VERSION_DISPLAY}." ${NSD_CreateLabel} 0u 0u 300u 140u "Setup is ready to install ${KRITA_PRODUCTNAME} ${KRITA_VERSION_DISPLAY}. You may review the install options before you continue.$\r$\n$\r$\n$_CLICK" pop $R0 # TODO: Add install option summary for review? nsDialogs::Show pop $R0 FunctionEnd diff --git a/plugins/color/colorspaceextensions/kis_hsv_adjustment.cpp b/plugins/color/colorspaceextensions/kis_hsv_adjustment.cpp index 51c4131ce6..acf896a935 100644 --- a/plugins/color/colorspaceextensions/kis_hsv_adjustment.cpp +++ b/plugins/color/colorspaceextensions/kis_hsv_adjustment.cpp @@ -1,677 +1,940 @@ /* * Copyright (c) 2007 Cyrille Berger * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; version 2 * of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_hsv_adjustment.h" #include #ifdef HAVE_OPENEXR #include #endif #include #include #include #include #include #include #include #include #include #define SCALE_TO_FLOAT( v ) KoColorSpaceMaths< _channel_type_, float>::scaleToA( v ) #define SCALE_FROM_FLOAT( v ) KoColorSpaceMaths< float, _channel_type_>::scaleToA( v ) template void clamp(float* r, float* g, float* b); #define FLOAT_CLAMP( v ) *v = (*v < 0.0) ? 0.0 : ( (*v>1.0) ? 1.0 : *v ) template<> void clamp(float* r, float* g, float* b) { FLOAT_CLAMP(r); FLOAT_CLAMP(g); FLOAT_CLAMP(b); } template<> void clamp(float* r, float* g, float* b) { FLOAT_CLAMP(r); FLOAT_CLAMP(g); FLOAT_CLAMP(b); } #ifdef HAVE_OPENEXR template<> void clamp(float* r, float* g, float* b) { Q_UNUSED(r); Q_UNUSED(g); Q_UNUSED(b); } #endif template<> void clamp(float* r, float* g, float* b) { Q_UNUSED(r); Q_UNUSED(g); Q_UNUSED(b); } +#include "kis_global.h" + +static inline void writeRGBSimple(float *r, float *g, float *b, + int sextant, + float x, float m, float M) +{ + switch (sextant) { + case 0: *r = M; *g = x + m; *b = m; break; + case 1: *r = x + m; *g = M; *b = m; break; + case 2: *r = m; *g = M; *b = x + m; break; + case 3: *r = m; *g = x + m; *b = M; break; + case 4: *r = x + m; *g = m; *b = M; break; + case 5: *r = M; *g = m; *b = x + m; break; + } +} + +struct HSVPolicy +{ + inline bool hasChroma(float v) { + static const float EPSILON = 1e-9f; + return v > EPSILON; + } + + inline float valueFromRGB(float r, float g, float b, float m, float M) { + Q_UNUSED(r); + Q_UNUSED(g); + Q_UNUSED(b); + Q_UNUSED(m); + return M; + } + + inline float fixupChroma(float c, float v) { + return qMin(v, c); + } + + inline void writeRGB(float *r, float *g, float *b, + int sextant, + float x, float c, float v) { + + const float m = v - c; + writeRGBSimple(r, g, b, sextant, x, m, v); + } +}; + +struct HSLPolicy +{ + inline bool hasChroma(float v) { + static const float EPSILON = 1e-9f; + return v > EPSILON && v < 1.0f - EPSILON; + } + + inline float valueFromRGB(float r, float g, float b, float m, float M) { + Q_UNUSED(r); + Q_UNUSED(g); + Q_UNUSED(b); + return 0.5f * (M + m); + } + + inline float fixupChroma(float c, float v) { + if (v >= 0.5f) { + c = qMin(c, 2.0f - 2.0f * v); + } else { + c = qMin(c, 2.0f * v); + } + + return c; + } + + inline void writeRGB(float *r, float *g, float *b, + int sextant, + float x, float c, float v) { + + const float M = v + 0.5f * c; + const float m = v - 0.5f * c; + + writeRGBSimple(r, g, b, sextant, x, m, M); + } +}; + +struct HCIPolicy +{ + inline bool hasChroma(float v) { + static const float EPSILON = 1e-9f; + return v > EPSILON && v < 1.0f - EPSILON; + } + + inline float valueFromRGB(float r, float g, float b, float m, float M) { + Q_UNUSED(m); + Q_UNUSED(M); + return (r + g + b) / 3.0f; + } + + inline float fixupChroma(float c, float v) { + static const float oneThird = 1.0f / 3.0f; + + if (v >= oneThird) { + c = qMin(c, 1.5f * (1.0f - v)); + } else { + c = qMin(c, 3.0f * v); + } + + return c; + } + + inline void writeRGB(float *r, float *g, float *b, + int sextant, + float x, float c, float v) { + + static const float oneThird = 1.0f / 3.0f; + + const float m = v - oneThird * (c + x); + const float M = c + m; + + writeRGBSimple(r, g, b, sextant, x, m, M); + } +}; + +struct HCYPolicy +{ + HCYPolicy(float _rCoeff = 0.299f, float _gCoeff = 0.587f, float _bCoeff = 0.114f) + : rCoeff(_rCoeff), + gCoeff(_gCoeff), + bCoeff(_bCoeff) + { + } + + const float rCoeff = 0.299f; + const float gCoeff = 0.587f; + const float bCoeff = 0.114f; + + inline bool hasChroma(float v) { + static const float EPSILON = 1e-9f; + return v > EPSILON && v < 1.0f - EPSILON; + } + + inline float valueFromRGB(float r, float g, float b, float m, float M) { + Q_UNUSED(m); + Q_UNUSED(M); + return rCoeff * r + gCoeff * g + bCoeff * b; + } + + inline float fixupChroma(float c, float v) { + Q_UNUSED(v); + // NOTE: no sliding in HCY, because the shape of the triangle + // depends on Hue, which complicated code a lot. And it + // seems to work fine without it :) + return c; + } + + inline void writeRGB(float *r, float *g, float *b, + int sextant, + float x, float c, float v) { + + + switch (sextant) { + case 0: *r = c; *g = x; *b = 0; break; + case 1: *r = x; *g = c; *b = 0; break; + case 2: *r = 0; *g = c; *b = x; break; + case 3: *r = 0; *g = x; *b = c; break; + case 4: *r = x; *g = 0; *b = c; break; + case 5: *r = c; *g = 0; *b = x; break; + } + + const float m = v - *r * rCoeff - *g * gCoeff - *b * bCoeff; + *r += m; + *g += m; + *b += m; + } +}; + +template +void HSVTransform(float *r, float *g, float *b, float dh, float ds, float dv, ValuePolicy valuePolicy) +{ + static const float EPSILON = 1e-9f; + + float h; + + float M = qMax(*r, qMax(*g, *b)); + float m = qMin(*r, qMin(*g, *b)); + + float chroma = M - m; + + float v = valuePolicy.valueFromRGB(*r, *g, *b, m, M); + + if (!valuePolicy.hasChroma(v)) { + chroma = 0.0f; + h = 0.0f; + if (dv < 0) { + v *= dv + 1.0f; + } else { + v += dv * (1.0f - v); + } + } else { + if (chroma > EPSILON) { + if (*r == M) + h = (*g - *b) / chroma; + else if (*g == M) + h = 2 + (*b - *r) / chroma; + else + h = 4 + (*r - *g) / chroma; + + h *= 60; + h += dh * 180; + + h = normalizeAngleDegrees(h); + + if (ds > 0) { + /// approximation of a nonlinear slider: + /// ds = 0.0 -> chroma *= 1.0; + /// ds = 0.5 -> chroma *= 2.0; + /// ds = 1.0 -> chroma *= 4.0; + + chroma = qMin(1.0f, chroma * (1.0f + ds + 2.0f * pow2(ds))); + } else { + chroma *= ds + 1.0f; + } + + } else { + h = 0.0f; + } + + { + const float dstV = dv > 0.0f ? 1.0f : 0.0f; + const float vCoeff = dstV - v; + const float chromaCoeff = 0 - chroma; + const float movement = std::abs(dv); + + v += movement * vCoeff; + chroma += movement * chromaCoeff; + } + + v = qBound(0.0f, v, 1.0f); + chroma = valuePolicy.fixupChroma(chroma, v); + } + + if (v <= EPSILON) { + *r = *g = *b = 0.0; + } else { + h /= 60.0f; + const int sextant = static_cast(h); + const float fract = h - sextant; + + const float x = + sextant & 0x1 ? + chroma - chroma * fract : + chroma * fract; + + valuePolicy.writeRGB(r, g, b, sextant, x, chroma, v); + } +} + template class KisHSVAdjustment : public KoColorTransformation { typedef traits RGBTrait; typedef typename RGBTrait::Pixel RGBPixel; public: KisHSVAdjustment() : m_adj_h(0.0), m_adj_s(0.0), m_adj_v(0.0), m_lumaRed(0.0), m_lumaGreen(0.0), m_lumaBlue(0.0), m_type(0), - m_colorize(false) + m_colorize(false), + m_compatibilityMode(true) { } public: void transform(const quint8 *srcU8, quint8 *dstU8, qint32 nPixels) const override { //if (m_model="RGBA" || m_colorize) { /*It'd be nice to have LCH automatically selector for LAB in the future, but I don't know how to select LAB * */ const RGBPixel* src = reinterpret_cast(srcU8); RGBPixel* dst = reinterpret_cast(dstU8); float h, s, v; float r = 0.0; float g = 0.0; float b = 0.0; qreal lumaR, lumaG, lumaB; //Default to rec 709 when there's no coefficients given// if (m_lumaRed<=0 || m_lumaGreen<=0 || m_lumaBlue<=0) { lumaR = 0.2126; lumaG = 0.7152; lumaB = 0.0722; } else { lumaR = m_lumaRed; lumaG = m_lumaGreen; lumaB = m_lumaBlue; } while (nPixels > 0) { if (m_colorize) { h = m_adj_h * 360; if (h >= 360.0) h = 0; s = m_adj_s; r = SCALE_TO_FLOAT(src->red); g = SCALE_TO_FLOAT(src->green); b = SCALE_TO_FLOAT(src->blue); float luminance = r * lumaR + g * lumaG + b * lumaB; if (m_adj_v > 0) { luminance *= (1.0 - m_adj_v); luminance += 1.0 - (1.0 - m_adj_v); } else if (m_adj_v < 0 ){ luminance *= (m_adj_v + 1.0); } v = luminance; HSLToRGB(h, s, v, &r, &g, &b); } else { if (m_type == 0) { - RGBToHSV(SCALE_TO_FLOAT(src->red), SCALE_TO_FLOAT(src->green), SCALE_TO_FLOAT(src->blue), &h, &s, &v); - h += m_adj_h * 180; - if (h > 360) h -= 360; - if (h < 0) h += 360; - s += m_adj_s; - v += m_adj_v; - HSVToRGB(h, s, v, &r, &g, &b); + if (!m_compatibilityMode) { + r = SCALE_TO_FLOAT(src->red); + g = SCALE_TO_FLOAT(src->green); + b = SCALE_TO_FLOAT(src->blue); + HSVTransform(&r, &g, &b, m_adj_h, m_adj_s, m_adj_v, HSVPolicy()); + } else { + RGBToHSV(SCALE_TO_FLOAT(src->red), SCALE_TO_FLOAT(src->green), SCALE_TO_FLOAT(src->blue), &h, &s, &v); + h += m_adj_h * 180; + h = normalizeAngleDegrees(h); + s += m_adj_s; + v += m_adj_v; + HSVToRGB(h, s, v, &r, &g, &b); + } } else if (m_type == 1) { - RGBToHSL(SCALE_TO_FLOAT(src->red), SCALE_TO_FLOAT(src->green), SCALE_TO_FLOAT(src->blue), &h, &s, &v); - - h += m_adj_h * 180; - if (h > 360) h -= 360; - if (h < 0) h += 360; - - s *= (m_adj_s + 1.0); - if (s < 0.0) s = 0.0; - if (s > 1.0) s = 1.0; - - if (m_adj_v < 0) - v *= (m_adj_v + 1.0); - else - v += (m_adj_v * (1.0 - v)); - - - HSLToRGB(h, s, v, &r, &g, &b); + if (!m_compatibilityMode) { + r = SCALE_TO_FLOAT(src->red); + g = SCALE_TO_FLOAT(src->green); + b = SCALE_TO_FLOAT(src->blue); + HSVTransform(&r, &g, &b, m_adj_h, m_adj_s, m_adj_v, HSLPolicy()); + } else { + RGBToHSL(SCALE_TO_FLOAT(src->red), SCALE_TO_FLOAT(src->green), SCALE_TO_FLOAT(src->blue), &h, &s, &v); + + h += m_adj_h * 180; + h = normalizeAngleDegrees(h); + s *= (m_adj_s + 1.0); + if (m_adj_v < 0) { + v *= (m_adj_v + 1.0); + } else { + v += (m_adj_v * (1.0 - v)); + } + HSLToRGB(h, s, v, &r, &g, &b); + } } else if (m_type == 2) { - qreal red = SCALE_TO_FLOAT(src->red); - qreal green = SCALE_TO_FLOAT(src->green); - qreal blue = SCALE_TO_FLOAT(src->blue); - qreal hue, sat, intensity; - RGBToHCI(red, green, blue, &hue, &sat, &intensity); - - hue *=360.0; - hue += m_adj_h * 180; - //if (intensity+m_adj_v>1.0){hue+=180.0;} - if (hue < 0) hue += 360; - hue = fmod(hue, 360.0); - - sat *= (m_adj_s + 1.0); - //sat = qBound(0.0, sat, 1.0); - - intensity += (m_adj_v); - - HCIToRGB(hue/360.0, sat, intensity, &red, &green, &blue); - - r = red; - g = green; - b = blue; + if (!m_compatibilityMode) { + r = SCALE_TO_FLOAT(src->red); + g = SCALE_TO_FLOAT(src->green); + b = SCALE_TO_FLOAT(src->blue); + HSVTransform(&r, &g, &b, m_adj_h, m_adj_s, m_adj_v, HCIPolicy()); + } else { + qreal red = SCALE_TO_FLOAT(src->red); + qreal green = SCALE_TO_FLOAT(src->green); + qreal blue = SCALE_TO_FLOAT(src->blue); + qreal hue, sat, intensity; + RGBToHCI(red, green, blue, &hue, &sat, &intensity); + + hue *= 360.0; + hue += m_adj_h * 180; + hue = normalizeAngleDegrees(hue); + sat *= (m_adj_s + 1.0); + intensity += m_adj_v; + + HCIToRGB(hue/360.0, sat, intensity, &red, &green, &blue); + + r = red; + g = green; + b = blue; + } } else if (m_type == 3) { - qreal red = SCALE_TO_FLOAT(src->red); - qreal green = SCALE_TO_FLOAT(src->green); - qreal blue = SCALE_TO_FLOAT(src->blue); - qreal hue, sat, luma; - RGBToHCY(red, green, blue, &hue, &sat, &luma, lumaR, lumaG, lumaB); - - hue *=360.0; - hue += m_adj_h * 180; - //if (luma+m_adj_v>1.0){hue+=180.0;} - if (hue < 0) hue += 360; - hue = fmod(hue, 360.0); - - sat *= (m_adj_s + 1.0); - //sat = qBound(0.0, sat, 1.0); - - luma += m_adj_v; - - - HCYToRGB(hue/360.0, sat, luma, &red, &green, &blue, lumaR, lumaG, lumaB); - r = red; - g = green; - b = blue; + if (!m_compatibilityMode) { + r = SCALE_TO_FLOAT(src->red); + g = SCALE_TO_FLOAT(src->green); + b = SCALE_TO_FLOAT(src->blue); + HSVTransform(&r, &g, &b, m_adj_h, m_adj_s, m_adj_v, HCYPolicy(lumaR, lumaG, lumaB)); + } else { + qreal red = SCALE_TO_FLOAT(src->red); + qreal green = SCALE_TO_FLOAT(src->green); + qreal blue = SCALE_TO_FLOAT(src->blue); + qreal hue, sat, luma; + RGBToHCY(red, green, blue, &hue, &sat, &luma, lumaR, lumaG, lumaB); + + hue *= 360.0; + hue += m_adj_h * 180; + hue = normalizeAngleDegrees(hue); + sat *= (m_adj_s + 1.0); + luma += m_adj_v; + + HCYToRGB(hue/360.0, sat, luma, &red, &green, &blue, lumaR, lumaG, lumaB); + r = red; + g = green; + b = blue; + } } else if (m_type == 4) { qreal red = SCALE_TO_FLOAT(src->red); qreal green = SCALE_TO_FLOAT(src->green); qreal blue = SCALE_TO_FLOAT(src->blue); qreal y, cb, cr; RGBToYUV(red, green, blue, &y, &cb, &cr, lumaR, lumaG, lumaB); cb *= (m_adj_h + 1.0); - //cb = qBound(0.0, cb, 1.0); - cr *= (m_adj_s + 1.0); - //cr = qBound(0.0, cr, 1.0); - y += (m_adj_v); - YUVToRGB(y, cb, cr, &red, &green, &blue, lumaR, lumaG, lumaB); r = red; g = green; b = blue; } else { Q_ASSERT_X(false, "", "invalid type"); } } clamp< _channel_type_ >(&r, &g, &b); dst->red = SCALE_FROM_FLOAT(r); dst->green = SCALE_FROM_FLOAT(g); dst->blue = SCALE_FROM_FLOAT(b); dst->alpha = src->alpha; --nPixels; ++src; ++dst; } /*} else if (m_model="LABA"){ const LABPixel* src = reinterpret_cast(srcU8); LABPixel* dst = reinterpret_cast(dstU8); qreal lightness = SCALE_TO_FLOAT(src->L); qreal a = SCALE_TO_FLOAT(src->a); qreal b = SCALE_TO_FLOAT(src->b); qreal L, C, H; while (nPixels > 0) { if (m_type = 4) { a *= (m_adj_h + 1.0); a = qBound(0.0, a, 1.0); b *= (m_adj_s + 1.0); b = qBound(0.0, b, 1.0); if (m_adj_v < 0) lightness *= (m_adj_v + 1.0); else lightness += (m_adj_v * (1.0 - lightness)); } else {//lch LABToLCH(lightness, a, b, &L, &C, &H); H *=360; H += m_adj_h * 180; if (H > 360) h -= 360; if (H < 0) h += 360; C += m_adj_s; C = qBound(0.0,C,1.0); L += m_adj_v; L = qBound(0.0,L,1.0); LCHToLAB(L, C, H/360.0, &lightness, &a, &b); } clamp< _channel_type_ >(&lightness, &a, &b); dst->L = SCALE_FROM_FLOAT(lightness); dst->a = SCALE_FROM_FLOAT(a); dst->b = SCALE_FROM_FLOAT(b); dst->alpha = src->alpha; --nPixels; ++src; ++dst; } }*/ } QList parameters() const override { QList list; - list << "h" << "s" << "v" << "type" << "colorize" << "lumaRed" << "lumaGreen"<< "lumaBlue"; + list << "h" << "s" << "v" << "type" << "colorize" << "lumaRed" << "lumaGreen"<< "lumaBlue" << "compatibilityMode"; return list; } int parameterId(const QString& name) const override { if (name == "h") { return 0; } else if (name == "s") { return 1; } else if (name == "v") { return 2; } else if (name == "type") { return 3; } else if (name == "colorize") { return 4; } else if (name == "lumaRed") { return 5; } else if (name == "lumaGreen") { return 6; } else if (name == "lumaBlue") { return 7; + } else if (name == "compatibilityMode") { + return 8; } return -1; } /** * name - "h", "s" or "v" * (h)ue in range <-1.0, 1.0> ( for user, show as -180, 180 or 0, 360 for colorize) * (s)aturation in range <-1.0, 1.0> ( for user, show -100, 100, or 0, 100 for colorize) * (v)alue in range <-1.0, 1.0> (for user, show -100, 100) * type: 0:HSV, 1:HSL, 2:HSI, 3:HSY, 4:YUV * m_colorize: Use colorize formula instead * luma Red/Green/Blue: Used for luma calculations. */ void setParameter(int id, const QVariant& parameter) override { switch(id) { case 0: m_adj_h = parameter.toDouble(); break; case 1: m_adj_s = parameter.toDouble(); break; case 2: m_adj_v = parameter.toDouble(); break; case 3: m_type = parameter.toInt(); break; case 4: m_colorize = parameter.toBool(); break; case 5: m_lumaRed = parameter.toDouble(); break; case 6: m_lumaGreen = parameter.toDouble(); break; case 7: m_lumaBlue = parameter.toDouble(); break; + case 8: + m_compatibilityMode = parameter.toBool(); + break; default: KIS_ASSERT_RECOVER_NOOP(false && "Unknown parameter ID. Ignored!"); ; } } private: double m_adj_h, m_adj_s, m_adj_v; qreal m_lumaRed, m_lumaGreen, m_lumaBlue; int m_type; bool m_colorize; + bool m_compatibilityMode; }; template class KisHSVCurveAdjustment : public KoColorTransformation { typedef traits RGBTrait; typedef typename RGBTrait::Pixel RGBPixel; public: KisHSVCurveAdjustment() : m_lumaRed(0.0), m_lumaGreen(0.0), m_lumaBlue(0.0) {} QList parameters() const override { QList list; list << "curve" << "channel" << "driverChannel" << "relative" << "lumaRed" << "lumaGreen"<< "lumaBlue"; return list; } int parameterId(const QString& name) const override { if (name == "curve") { return PAR_CURVE; } else if (name == "channel") { return PAR_CHANNEL; } else if (name == "driverChannel") { return PAR_DRIVER_CHANNEL; } else if (name == "relative") { return PAR_RELATIVE; } else if (name == "lumaRed") { return PAR_LUMA_R; } else if (name == "lumaGreen") { return PAR_LUMA_G; } else if (name == "lumaBlue") { return PAR_LUMA_B; } return -1; } /** * curve: adjustment curve as QVector * channel: which channel to adjust. See KisHSVCurve::ColorChannel. * driverChannel: which channel to use as source for adjustments. * relative: * false: use curve for direct lookup. * true: add adjustment to original. In this mode, the curve range is mapped to -1.0 to 1.0 * luma Red/Green/Blue: Used for luma calculations. */ void setParameter(int id, const QVariant& parameter) override { switch(id) { case PAR_CURVE: m_curve = parameter.value>(); break; case PAR_CHANNEL: case PAR_DRIVER_CHANNEL: { int channel = parameter.toInt(); KIS_ASSERT_RECOVER_RETURN(0 <= channel && channel < KisHSVCurve::ChannelCount && "Invalid channel. Ignored!"); if (id == PAR_CHANNEL) { m_channel = channel; } else { m_driverChannel = channel; } } break; case PAR_RELATIVE: m_relative = parameter.toBool(); break; case PAR_LUMA_R: m_lumaRed = parameter.toDouble(); break; case PAR_LUMA_G: m_lumaGreen = parameter.toDouble(); break; case PAR_LUMA_B: m_lumaBlue = parameter.toDouble(); break; default: KIS_ASSERT_RECOVER_NOOP(false && "Unknown parameter ID. Ignored!"); } } const float SCALE_FROM_16BIT = 1.0f / 0xFFFF; void transform(const quint8 *srcU8, quint8 *dstU8, qint32 nPixels) const override { const RGBPixel* src = reinterpret_cast(srcU8); RGBPixel* dst = reinterpret_cast(dstU8); float max = m_curve.size() - 1; int driverChannel = m_relative ? m_driverChannel : m_channel; float component[KisHSVCurve::ChannelCount]; // Aliases for convenience float &h = component[KisHSVCurve::Hue]; float &s = component[KisHSVCurve::Saturation]; float &v = component[KisHSVCurve::Value]; float &r = component[KisHSVCurve::Red]; float &g = component[KisHSVCurve::Green]; float &b = component[KisHSVCurve::Blue]; float &a = component[KisHSVCurve::Alpha]; while (nPixels > 0) { r = SCALE_TO_FLOAT(src->red); g = SCALE_TO_FLOAT(src->green); b = SCALE_TO_FLOAT(src->blue); a = SCALE_TO_FLOAT(src->alpha); RGBToHSV(r, g, b, &h, &s, &v); // Normalize hue to 0.0 to 1.0 range h /= 360.0f; float adjustment = lookupComponent(component[driverChannel], max) * SCALE_FROM_16BIT; if (m_relative) { // Curve uses range 0.0 to 1.0, but for adjustment we need -1.0 to 1.0 adjustment = 2.0f * adjustment - 1.0f; if (m_channel == KisHSVCurve::AllColors) { r += adjustment; g += adjustment; b += adjustment; } else { component[m_channel] += adjustment; } } else { if (m_channel == KisHSVCurve::AllColors) { r = b = g = adjustment; } else { component[m_channel] = adjustment; } } h *= 360.0f; if (h > 360) h -= 360; if (h < 0) h += 360; if (m_channel >= KisHSVCurve::Hue) { HSVToRGB(h, s, v, &r, &g, &b); } clamp< _channel_type_ >(&r, &g, &b); FLOAT_CLAMP(&a); dst->red = SCALE_FROM_FLOAT(r); dst->green = SCALE_FROM_FLOAT(g); dst->blue = SCALE_FROM_FLOAT(b); dst->alpha = SCALE_FROM_FLOAT(a); --nPixels; ++src; ++dst; } } float lookupComponent(float x, float max) const { // No curve for this component? Pass through unmodified if (max < 2) return x; if (x < 0) return m_curve[0]; float lookup = x * max; float base = floor(lookup); float offset = lookup - base; if (base >= max) { base = max - 1.0f; offset = 1.0f; } int index = (int)base; return (1.0f - offset) * m_curve[index] + offset * m_curve[index + 1]; } private: enum ParameterID { PAR_CURVE, PAR_CHANNEL, PAR_DRIVER_CHANNEL, PAR_RELATIVE, PAR_LUMA_R, PAR_LUMA_G, PAR_LUMA_B, }; QVector m_curve; int m_channel = 0; int m_driverChannel = 0; bool m_relative = false; /* Note: the filter currently only supports HSV, so these are * unused, but will be needed once HSL, etc. */ qreal m_lumaRed, m_lumaGreen, m_lumaBlue; }; KisHSVAdjustmentFactory::KisHSVAdjustmentFactory() : KoColorTransformationFactory("hsv_adjustment") { } QList< QPair< KoID, KoID > > KisHSVAdjustmentFactory::supportedModels() const { QList< QPair< KoID, KoID > > l; l.append(QPair< KoID, KoID >(RGBAColorModelID , Integer8BitsColorDepthID)); l.append(QPair< KoID, KoID >(RGBAColorModelID , Integer16BitsColorDepthID)); l.append(QPair< KoID, KoID >(RGBAColorModelID , Float16BitsColorDepthID)); l.append(QPair< KoID, KoID >(RGBAColorModelID , Float32BitsColorDepthID)); return l; } KoColorTransformation* KisHSVAdjustmentFactory::createTransformation(const KoColorSpace* colorSpace, QHash parameters) const { KoColorTransformation * adj; if (colorSpace->colorModelId() != RGBAColorModelID) { dbgKrita << "Unsupported color space " << colorSpace->id() << " in KisHSVAdjustmentFactory::createTransformation"; return 0; } if (colorSpace->colorDepthId() == Integer8BitsColorDepthID) { adj = new KisHSVAdjustment< quint8, KoBgrTraits < quint8 > >(); } else if (colorSpace->colorDepthId() == Integer16BitsColorDepthID) { adj = new KisHSVAdjustment< quint16, KoBgrTraits < quint16 > >(); } #ifdef HAVE_OPENEXR else if (colorSpace->colorDepthId() == Float16BitsColorDepthID) { adj = new KisHSVAdjustment< half, KoRgbTraits < half > >(); } #endif else if (colorSpace->colorDepthId() == Float32BitsColorDepthID) { adj = new KisHSVAdjustment< float, KoRgbTraits < float > >(); } else { dbgKrita << "Unsupported color space " << colorSpace->id() << " in KisHSVAdjustmentFactory::createTransformation"; return 0; } adj->setParameters(parameters); return adj; } KisHSVCurveAdjustmentFactory::KisHSVCurveAdjustmentFactory() : KoColorTransformationFactory("hsv_curve_adjustment") { } QList< QPair< KoID, KoID > > KisHSVCurveAdjustmentFactory::supportedModels() const { QList< QPair< KoID, KoID > > l; l.append(QPair< KoID, KoID >(RGBAColorModelID , Integer8BitsColorDepthID)); l.append(QPair< KoID, KoID >(RGBAColorModelID , Integer16BitsColorDepthID)); l.append(QPair< KoID, KoID >(RGBAColorModelID , Float16BitsColorDepthID)); l.append(QPair< KoID, KoID >(RGBAColorModelID , Float32BitsColorDepthID)); return l; } KoColorTransformation* KisHSVCurveAdjustmentFactory::createTransformation(const KoColorSpace* colorSpace, QHash parameters) const { KoColorTransformation * adj; if (colorSpace->colorModelId() != RGBAColorModelID) { dbgKrita << "Unsupported color space " << colorSpace->id() << " in KisHSVCurveAdjustmentFactory::createTransformation"; return 0; } if (colorSpace->colorDepthId() == Integer8BitsColorDepthID) { adj = new KisHSVCurveAdjustment< quint8, KoBgrTraits < quint8 > >(); } else if (colorSpace->colorDepthId() == Integer16BitsColorDepthID) { adj = new KisHSVCurveAdjustment< quint16, KoBgrTraits < quint16 > >(); } #ifdef HAVE_OPENEXR else if (colorSpace->colorDepthId() == Float16BitsColorDepthID) { adj = new KisHSVCurveAdjustment< half, KoRgbTraits < half > >(); } #endif else if (colorSpace->colorDepthId() == Float32BitsColorDepthID) { adj = new KisHSVCurveAdjustment< float, KoRgbTraits < float > >(); } else { dbgKrita << "Unsupported color space " << colorSpace->id() << " in KisHSVCurveAdjustmentFactory::createTransformation"; return 0; } adj->setParameters(parameters); return adj; } diff --git a/plugins/color/lcms2engine/colorprofiles/LcmsColorProfileContainer.cpp b/plugins/color/lcms2engine/colorprofiles/LcmsColorProfileContainer.cpp index 8172fa50f7..14858c958c 100644 --- a/plugins/color/lcms2engine/colorprofiles/LcmsColorProfileContainer.cpp +++ b/plugins/color/lcms2engine/colorprofiles/LcmsColorProfileContainer.cpp @@ -1,569 +1,569 @@ /* * This file is part of the KDE project * Copyright (c) 2000 Matthias Elter * 2001 John Califf * 2004 Boudewijn Rempt * Copyright (c) 2007 Thomas Zander * Copyright (c) 2007 Adrian Page * * 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 "LcmsColorProfileContainer.h" #include #include #include #include #include #include "kis_debug.h" class LcmsColorProfileContainer::Private { public: cmsHPROFILE profile; cmsColorSpaceSignature colorSpaceSignature; cmsProfileClassSignature deviceClass; QString productDescription; QString manufacturer; QString copyright; QString name; float version; IccColorProfile::Data *data {0}; bool valid {false}; bool suitableForOutput {false}; bool hasColorants; bool hasTRC; bool isLinear {false}; bool adaptedFromD50; cmsCIEXYZ mediaWhitePoint; cmsCIExyY whitePoint; cmsCIEXYZTRIPLE colorants; cmsToneCurve *redTRC {0}; cmsToneCurve *greenTRC {0}; cmsToneCurve *blueTRC {0}; cmsToneCurve *grayTRC {0}; cmsToneCurve *redTRCReverse {0}; cmsToneCurve *greenTRCReverse {0}; cmsToneCurve *blueTRCReverse {0}; cmsToneCurve *grayTRCReverse {0}; cmsUInt32Number defaultIntent; bool isPerceptualCLUT; bool isRelativeCLUT; bool isAbsoluteCLUT; bool isSaturationCLUT; bool isMatrixShaper; QByteArray uniqueId; }; LcmsColorProfileContainer::LcmsColorProfileContainer() : d(new Private()) { d->profile = 0; } LcmsColorProfileContainer::LcmsColorProfileContainer(IccColorProfile::Data *data) : d(new Private()) { d->data = data; d->profile = 0; init(); } QByteArray LcmsColorProfileContainer::lcmsProfileToByteArray(const cmsHPROFILE profile) { cmsUInt32Number bytesNeeded = 0; // Make a raw data image ready for saving cmsSaveProfileToMem(profile, 0, &bytesNeeded); // calc size QByteArray rawData; rawData.resize(bytesNeeded); if (rawData.size() >= (int)bytesNeeded) { cmsSaveProfileToMem(profile, rawData.data(), &bytesNeeded); // fill buffer } else { qWarning() << "Couldn't resize the profile buffer, system is probably running out of memory."; rawData.resize(0); } return rawData; } IccColorProfile *LcmsColorProfileContainer::createFromLcmsProfile(const cmsHPROFILE profile) { IccColorProfile *iccprofile = new IccColorProfile(lcmsProfileToByteArray(profile)); cmsCloseProfile(profile); return iccprofile; } LcmsColorProfileContainer::~LcmsColorProfileContainer() { cmsCloseProfile(d->profile); delete d; } #define _BUFFER_SIZE_ 1000 bool LcmsColorProfileContainer::init() { if (d->profile) { cmsCloseProfile(d->profile); } d->profile = cmsOpenProfileFromMem((void *)d->data->rawData().constData(), d->data->rawData().size()); #ifndef NDEBUG if (d->data->rawData().size() == 4096) { qWarning() << "Profile has a size of 4096, which is suspicious and indicates a possible misuse of QIODevice::read(int), check your code."; } #endif if (d->profile) { wchar_t buffer[_BUFFER_SIZE_]; d->colorSpaceSignature = cmsGetColorSpace(d->profile); d->deviceClass = cmsGetDeviceClass(d->profile); cmsGetProfileInfo(d->profile, cmsInfoDescription, cmsNoLanguage, cmsNoCountry, buffer, _BUFFER_SIZE_); d->name = QString::fromWCharArray(buffer); //apparently this should give us a localised string??? Not sure about this. cmsGetProfileInfo(d->profile, cmsInfoModel, cmsNoLanguage, cmsNoCountry, buffer, _BUFFER_SIZE_); d->productDescription = QString::fromWCharArray(buffer); cmsGetProfileInfo(d->profile, cmsInfoManufacturer, cmsNoLanguage, cmsNoCountry, buffer, _BUFFER_SIZE_); d->manufacturer = QString::fromWCharArray(buffer); cmsGetProfileInfo(d->profile, cmsInfoCopyright, cmsNoLanguage, cmsNoCountry, buffer, _BUFFER_SIZE_); d->copyright = QString::fromWCharArray(buffer); cmsProfileClassSignature profile_class; profile_class = cmsGetDeviceClass(d->profile); d->valid = (profile_class != cmsSigNamedColorClass); //This is where obtain the whitepoint, and convert it to the actual white point of the profile in the case a Chromatic adaption tag is //present. This is necessary for profiles following the v4 spec. cmsCIEXYZ baseMediaWhitePoint;//dummy to hold copy of mediawhitepoint if this is modified by chromatic adaption. if (cmsIsTag(d->profile, cmsSigMediaWhitePointTag)) { d->mediaWhitePoint = *((cmsCIEXYZ *)cmsReadTag(d->profile, cmsSigMediaWhitePointTag)); baseMediaWhitePoint = d->mediaWhitePoint; cmsXYZ2xyY(&d->whitePoint, &d->mediaWhitePoint); if (cmsIsTag(d->profile, cmsSigChromaticAdaptationTag)) { //the chromatic adaption tag represent a matrix from the actual white point of the profile to D50. cmsCIEXYZ *CAM1 = (cmsCIEXYZ *)cmsReadTag(d->profile, cmsSigChromaticAdaptationTag); //We first put all our data into structures we can manipulate. double d3dummy [3] = {d->mediaWhitePoint.X, d->mediaWhitePoint.Y, d->mediaWhitePoint.Z}; QGenericMatrix<1, 3, double> whitePointMatrix(d3dummy); QTransform invertDummy(CAM1[0].X, CAM1[0].Y, CAM1[0].Z, CAM1[1].X, CAM1[1].Y, CAM1[1].Z, CAM1[2].X, CAM1[2].Y, CAM1[2].Z); - //we then abuse QTransform's invert function because it probably does matrix invertion 20 times better than I can program. + //we then abuse QTransform's invert function because it probably does matrix inversion 20 times better than I can program. //if the matrix is uninvertable, invertedDummy will be an identity matrix, which for us means that it won't give any noticeble //effect when we start multiplying. QTransform invertedDummy = invertDummy.inverted(); //we then put the QTransform into a generic 3x3 matrix. double d9dummy [9] = {invertedDummy.m11(), invertedDummy.m12(), invertedDummy.m13(), invertedDummy.m21(), invertedDummy.m22(), invertedDummy.m23(), invertedDummy.m31(), invertedDummy.m32(), invertedDummy.m33() }; QGenericMatrix<3, 3, double> chromaticAdaptionMatrix(d9dummy); //multiplying our inverted adaption matrix with the whitepoint gives us the right whitepoint. QGenericMatrix<1, 3, double> result = chromaticAdaptionMatrix * whitePointMatrix; //and then we pour the matrix into the whitepoint variable. Generic matrix does row/column for indices even though it //uses column/row for initialising. d->mediaWhitePoint.X = result(0, 0); d->mediaWhitePoint.Y = result(1, 0); d->mediaWhitePoint.Z = result(2, 0); cmsXYZ2xyY(&d->whitePoint, &d->mediaWhitePoint); } } //This is for RGB profiles, but it only works for matrix profiles. Need to design it to work with non-matrix profiles. if (cmsIsTag(d->profile, cmsSigRedColorantTag)) { cmsCIEXYZTRIPLE tempColorants; tempColorants.Red = *((cmsCIEXYZ *)cmsReadTag(d->profile, cmsSigRedColorantTag)); tempColorants.Green = *((cmsCIEXYZ *)cmsReadTag(d->profile, cmsSigGreenColorantTag)); tempColorants.Blue = *((cmsCIEXYZ *)cmsReadTag(d->profile, cmsSigBlueColorantTag)); //convert to d65, this is useless. cmsAdaptToIlluminant(&d->colorants.Red, &baseMediaWhitePoint, &d->mediaWhitePoint, &tempColorants.Red); cmsAdaptToIlluminant(&d->colorants.Green, &baseMediaWhitePoint, &d->mediaWhitePoint, &tempColorants.Green); cmsAdaptToIlluminant(&d->colorants.Blue, &baseMediaWhitePoint, &d->mediaWhitePoint, &tempColorants.Blue); //d->colorants = tempColorants; d->hasColorants = true; } else { //qDebug()<name<<": has no colorants"; d->hasColorants = false; } //retrieve TRC. if (cmsIsTag(d->profile, cmsSigRedTRCTag) && cmsIsTag(d->profile, cmsSigBlueTRCTag) && cmsIsTag(d->profile, cmsSigGreenTRCTag)) { d->redTRC = ((cmsToneCurve *)cmsReadTag (d->profile, cmsSigRedTRCTag)); d->greenTRC = ((cmsToneCurve *)cmsReadTag (d->profile, cmsSigGreenTRCTag)); d->blueTRC = ((cmsToneCurve *)cmsReadTag (d->profile, cmsSigBlueTRCTag)); if (d->redTRC) d->redTRCReverse = cmsReverseToneCurve(d->redTRC); if (d->greenTRC) d->greenTRCReverse = cmsReverseToneCurve(d->greenTRC); if (d->blueTRC) d->blueTRCReverse = cmsReverseToneCurve(d->blueTRC); d->hasTRC = (d->redTRC && d->greenTRC && d->blueTRC && d->redTRCReverse && d->greenTRCReverse && d->blueTRCReverse); if (d->hasTRC) d->isLinear = cmsIsToneCurveLinear(d->redTRC) && cmsIsToneCurveLinear(d->greenTRC) && cmsIsToneCurveLinear(d->blueTRC); } else if (cmsIsTag(d->profile, cmsSigGrayTRCTag)) { d->grayTRC = ((cmsToneCurve *)cmsReadTag (d->profile, cmsSigGrayTRCTag)); if (d->grayTRC) d->grayTRCReverse = cmsReverseToneCurve(d->grayTRC); d->hasTRC = (d->grayTRC && d->grayTRCReverse); if (d->hasTRC) d->isLinear = cmsIsToneCurveLinear(d->grayTRC); } else { d->hasTRC = false; } // Check if the profile can convert (something->this) d->suitableForOutput = cmsIsMatrixShaper(d->profile) || (cmsIsCLUT(d->profile, INTENT_PERCEPTUAL, LCMS_USED_AS_INPUT) && cmsIsCLUT(d->profile, INTENT_PERCEPTUAL, LCMS_USED_AS_OUTPUT)); d->version = cmsGetProfileVersion(d->profile); d->defaultIntent = cmsGetHeaderRenderingIntent(d->profile); d->isMatrixShaper = cmsIsMatrixShaper(d->profile); d->isPerceptualCLUT = cmsIsCLUT(d->profile, INTENT_PERCEPTUAL, LCMS_USED_AS_INPUT); d->isSaturationCLUT = cmsIsCLUT(d->profile, INTENT_SATURATION, LCMS_USED_AS_INPUT); d->isAbsoluteCLUT = cmsIsCLUT(d->profile, INTENT_SATURATION, LCMS_USED_AS_INPUT); d->isRelativeCLUT = cmsIsCLUT(d->profile, INTENT_RELATIVE_COLORIMETRIC, LCMS_USED_AS_INPUT); return true; } return false; } cmsHPROFILE LcmsColorProfileContainer::lcmsProfile() const { return d->profile; } cmsColorSpaceSignature LcmsColorProfileContainer::colorSpaceSignature() const { return d->colorSpaceSignature; } cmsProfileClassSignature LcmsColorProfileContainer::deviceClass() const { return d->deviceClass; } QString LcmsColorProfileContainer::manufacturer() const { return d->manufacturer; } QString LcmsColorProfileContainer::copyright() const { return d->copyright; } bool LcmsColorProfileContainer::valid() const { return d->valid; } float LcmsColorProfileContainer::version() const { return d->version; } bool LcmsColorProfileContainer::isSuitableForOutput() const { return d->suitableForOutput; } bool LcmsColorProfileContainer::isSuitableForPrinting() const { return deviceClass() == cmsSigOutputClass; } bool LcmsColorProfileContainer::isSuitableForDisplay() const { return deviceClass() == cmsSigDisplayClass; } bool LcmsColorProfileContainer::supportsPerceptual() const { return d->isPerceptualCLUT; } bool LcmsColorProfileContainer::supportsSaturation() const { return d->isSaturationCLUT; } bool LcmsColorProfileContainer::supportsAbsolute() const { return d->isAbsoluteCLUT;//LCMS2 doesn't convert matrix shapers via absolute intent, because of V4 workflow. } bool LcmsColorProfileContainer::supportsRelative() const { if (d->isRelativeCLUT || d->isMatrixShaper){ return true; } return false; } bool LcmsColorProfileContainer::hasColorants() const { return d->hasColorants; } bool LcmsColorProfileContainer::hasTRC() const { return d->hasTRC; } bool LcmsColorProfileContainer::isLinear() const { return d->isLinear; } QVector LcmsColorProfileContainer::getColorantsXYZ() const { QVector colorants(9); colorants[0] = d->colorants.Red.X; colorants[1] = d->colorants.Red.Y; colorants[2] = d->colorants.Red.Z; colorants[3] = d->colorants.Green.X; colorants[4] = d->colorants.Green.Y; colorants[5] = d->colorants.Green.Z; colorants[6] = d->colorants.Blue.X; colorants[7] = d->colorants.Blue.Y; colorants[8] = d->colorants.Blue.Z; return colorants; } QVector LcmsColorProfileContainer::getColorantsxyY() const { cmsCIEXYZ temp1; cmsCIExyY temp2; QVector colorants(9); temp1.X = d->colorants.Red.X; temp1.Y = d->colorants.Red.Y; temp1.Z = d->colorants.Red.Z; cmsXYZ2xyY(&temp2, &temp1); colorants[0] = temp2.x; colorants[1] = temp2.y; colorants[2] = temp2.Y; temp1.X = d->colorants.Green.X; temp1.Y = d->colorants.Green.Y; temp1.Z = d->colorants.Green.Z; cmsXYZ2xyY(&temp2, &temp1); colorants[3] = temp2.x; colorants[4] = temp2.y; colorants[5] = temp2.Y; temp1.X = d->colorants.Blue.X; temp1.Y = d->colorants.Blue.Y; temp1.Z = d->colorants.Blue.Z; cmsXYZ2xyY(&temp2, &temp1); colorants[6] = temp2.x; colorants[7] = temp2.y; colorants[8] = temp2.Y; return colorants; } QVector LcmsColorProfileContainer::getWhitePointXYZ() const { QVector tempWhitePoint(3); tempWhitePoint[0] = d->mediaWhitePoint.X; tempWhitePoint[1] = d->mediaWhitePoint.Y; tempWhitePoint[2] = d->mediaWhitePoint.Z; return tempWhitePoint; } QVector LcmsColorProfileContainer::getWhitePointxyY() const { QVector tempWhitePoint(3); tempWhitePoint[0] = d->whitePoint.x; tempWhitePoint[1] = d->whitePoint.y; tempWhitePoint[2] = d->whitePoint.Y; return tempWhitePoint; } QVector LcmsColorProfileContainer::getEstimatedTRC() const { QVector TRCtriplet(3); if (d->hasColorants) { if (cmsIsToneCurveLinear(d->redTRC)) { TRCtriplet[0] = 1.0; } else { TRCtriplet[0] = cmsEstimateGamma(d->redTRC, 0.01); } if (cmsIsToneCurveLinear(d->greenTRC)) { TRCtriplet[1] = 1.0; } else { TRCtriplet[1] = cmsEstimateGamma(d->greenTRC, 0.01); } if (cmsIsToneCurveLinear(d->blueTRC)) { TRCtriplet[2] = 1.0; } else { TRCtriplet[2] = cmsEstimateGamma(d->blueTRC, 0.01); } } else { if (cmsIsTag(d->profile, cmsSigGrayTRCTag)) { if (cmsIsToneCurveLinear(d->grayTRC)) { TRCtriplet.fill(1.0); } else { TRCtriplet.fill(cmsEstimateGamma(d->grayTRC, 0.01)); } } else { TRCtriplet.fill(1.0); } } return TRCtriplet; } void LcmsColorProfileContainer::LinearizeFloatValue(QVector & Value) const { if (d->hasColorants) { if (!cmsIsToneCurveLinear(d->redTRC)) { Value[0] = cmsEvalToneCurveFloat(d->redTRC, Value[0]); } if (!cmsIsToneCurveLinear(d->greenTRC)) { Value[1] = cmsEvalToneCurveFloat(d->greenTRC, Value[1]); } if (!cmsIsToneCurveLinear(d->blueTRC)) { Value[2] = cmsEvalToneCurveFloat(d->blueTRC, Value[2]); } } else { if (cmsIsTag(d->profile, cmsSigGrayTRCTag)) { Value[0] = cmsEvalToneCurveFloat(d->grayTRC, Value[0]); } } } void LcmsColorProfileContainer::DelinearizeFloatValue(QVector & Value) const { if (d->hasColorants) { if (!cmsIsToneCurveLinear(d->redTRC)) { Value[0] = cmsEvalToneCurveFloat(d->redTRCReverse, Value[0]); } if (!cmsIsToneCurveLinear(d->greenTRC)) { Value[1] = cmsEvalToneCurveFloat(d->greenTRCReverse, Value[1]); } if (!cmsIsToneCurveLinear(d->blueTRC)) { Value[2] = cmsEvalToneCurveFloat(d->blueTRCReverse, Value[2]); } } else { if (cmsIsTag(d->profile, cmsSigGrayTRCTag)) { Value[0] = cmsEvalToneCurveFloat(d->grayTRCReverse, Value[0]); } } } void LcmsColorProfileContainer::LinearizeFloatValueFast(QVector & Value) const { const qreal scale = 65535.0; const qreal invScale = 1.0 / scale; if (d->hasColorants) { //we can only reliably delinearise in the 0-1.0 range, outside of that leave the value alone. QVector TRCtriplet(3); TRCtriplet[0] = Value[0] * scale; TRCtriplet[1] = Value[1] * scale; TRCtriplet[2] = Value[2] * scale; if (!cmsIsToneCurveLinear(d->redTRC) && Value[0]<1.0) { TRCtriplet[0] = cmsEvalToneCurve16(d->redTRC, TRCtriplet[0]); Value[0] = TRCtriplet[0] * invScale; } if (!cmsIsToneCurveLinear(d->greenTRC) && Value[1]<1.0) { TRCtriplet[1] = cmsEvalToneCurve16(d->greenTRC, TRCtriplet[1]); Value[1] = TRCtriplet[1] * invScale; } if (!cmsIsToneCurveLinear(d->blueTRC) && Value[2]<1.0) { TRCtriplet[2] = cmsEvalToneCurve16(d->blueTRC, TRCtriplet[2]); Value[2] = TRCtriplet[2] * invScale; } } else { if (cmsIsTag(d->profile, cmsSigGrayTRCTag) && Value[0]<1.0) { quint16 newValue = cmsEvalToneCurve16(d->grayTRC, Value[0] * scale); Value[0] = newValue * invScale; } } } void LcmsColorProfileContainer::DelinearizeFloatValueFast(QVector & Value) const { const qreal scale = 65535.0; const qreal invScale = 1.0 / scale; if (d->hasColorants) { //we can only reliably delinearise in the 0-1.0 range, outside of that leave the value alone. QVector TRCtriplet(3); TRCtriplet[0] = Value[0] * scale; TRCtriplet[1] = Value[1] * scale; TRCtriplet[2] = Value[2] * scale; if (!cmsIsToneCurveLinear(d->redTRC) && Value[0]<1.0) { TRCtriplet[0] = cmsEvalToneCurve16(d->redTRCReverse, TRCtriplet[0]); Value[0] = TRCtriplet[0] * invScale; } if (!cmsIsToneCurveLinear(d->greenTRC) && Value[1]<1.0) { TRCtriplet[1] = cmsEvalToneCurve16(d->greenTRCReverse, TRCtriplet[1]); Value[1] = TRCtriplet[1] * invScale; } if (!cmsIsToneCurveLinear(d->blueTRC) && Value[2]<1.0) { TRCtriplet[2] = cmsEvalToneCurve16(d->blueTRCReverse, TRCtriplet[2]); Value[2] = TRCtriplet[2] * invScale; } } else { if (cmsIsTag(d->profile, cmsSigGrayTRCTag) && Value[0]<1.0) { quint16 newValue = cmsEvalToneCurve16(d->grayTRCReverse, Value[0] * scale); Value[0] = newValue * invScale; } } } QString LcmsColorProfileContainer::name() const { return d->name; } QString LcmsColorProfileContainer::info() const { return d->productDescription; } QByteArray LcmsColorProfileContainer::getProfileUniqueId() const { if (d->uniqueId.isEmpty() && d->profile) { QByteArray id(sizeof(cmsProfileID), 0); cmsGetHeaderProfileID(d->profile, (quint8*)id.data()); bool isNull = std::all_of(id.constBegin(), id.constEnd(), [](char c) {return c == 0;}); if (isNull) { if (cmsMD5computeID(d->profile)) { cmsGetHeaderProfileID(d->profile, (quint8*)id.data()); isNull = false; } } if (!isNull) { d->uniqueId = id; } } return d->uniqueId; } diff --git a/plugins/color/lcms2engine/colorspaces/rgb_f32/RgbF32ColorSpace.cpp b/plugins/color/lcms2engine/colorspaces/rgb_f32/RgbF32ColorSpace.cpp index e8308bbf41..623a80ffad 100644 --- a/plugins/color/lcms2engine/colorspaces/rgb_f32/RgbF32ColorSpace.cpp +++ b/plugins/color/lcms2engine/colorspaces/rgb_f32/RgbF32ColorSpace.cpp @@ -1,114 +1,116 @@ /* * Copyright (c) 2006 Cyrille Berger * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; 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 * Library 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 "RgbF32ColorSpace.h" #include #include #include "compositeops/KoCompositeOps.h" #include "compositeops/RgbCompositeOps.h" #include +#include +#include RgbF32ColorSpace::RgbF32ColorSpace(const QString &name, KoColorProfile *p) : LcmsColorSpace(colorSpaceId(), name, TYPE_RGBA_FLT, cmsSigRgbData, p) { const IccColorProfile *icc_p = dynamic_cast(p); Q_ASSERT(icc_p); QVector uiRanges(icc_p->getFloatUIMinMax()); Q_ASSERT(uiRanges.size() == 3); addChannel(new KoChannelInfo(i18n("Red"), 0 * sizeof(float), 0, KoChannelInfo::COLOR, KoChannelInfo::FLOAT32, 4, QColor(255, 0, 0), uiRanges[0])); addChannel(new KoChannelInfo(i18n("Green"), 1 * sizeof(float), 1, KoChannelInfo::COLOR, KoChannelInfo::FLOAT32, 4, QColor(0, 255, 0), uiRanges[1])); addChannel(new KoChannelInfo(i18n("Blue"), 2 * sizeof(float), 2, KoChannelInfo::COLOR, KoChannelInfo::FLOAT32, 4, QColor(0, 0, 255), uiRanges[2])); addChannel(new KoChannelInfo(i18n("Alpha"), 3 * sizeof(float), 3, KoChannelInfo::ALPHA, KoChannelInfo::FLOAT32, 4)); init(); addStandardCompositeOps(this); addCompositeOp(new RgbCompositeOpIn(this)); addCompositeOp(new RgbCompositeOpOut(this)); addCompositeOp(new RgbCompositeOpBumpmap(this)); } bool RgbF32ColorSpace::willDegrade(ColorSpaceIndependence independence) const { if (independence == TO_RGBA16) { return true; } else { return false; } } KoColorSpace *RgbF32ColorSpace::clone() const { return new RgbF32ColorSpace(name(), profile()->clone()); } void RgbF32ColorSpace::colorToXML(const quint8 *pixel, QDomDocument &doc, QDomElement &colorElt) const { const KoRgbF32Traits::Pixel *p = reinterpret_cast(pixel); QDomElement labElt = doc.createElement("RGB"); labElt.setAttribute("r", KisDomUtils::toString(KoColorSpaceMaths< KoRgbF32Traits::channels_type, qreal>::scaleToA(p->red))); labElt.setAttribute("g", KisDomUtils::toString(KoColorSpaceMaths< KoRgbF32Traits::channels_type, qreal>::scaleToA(p->green))); labElt.setAttribute("b", KisDomUtils::toString(KoColorSpaceMaths< KoRgbF32Traits::channels_type, qreal>::scaleToA(p->blue))); labElt.setAttribute("space", profile()->name()); colorElt.appendChild(labElt); } void RgbF32ColorSpace::colorFromXML(quint8 *pixel, const QDomElement &elt) const { KoRgbF32Traits::Pixel *p = reinterpret_cast(pixel); p->red = KoColorSpaceMaths< qreal, KoRgbF32Traits::channels_type >::scaleToA(KisDomUtils::toDouble(elt.attribute("r"))); p->green = KoColorSpaceMaths< qreal, KoRgbF32Traits::channels_type >::scaleToA(KisDomUtils::toDouble(elt.attribute("g"))); p->blue = KoColorSpaceMaths< qreal, KoRgbF32Traits::channels_type >::scaleToA(KisDomUtils::toDouble(elt.attribute("b"))); p->alpha = 1.0; } void RgbF32ColorSpace::toHSY(const QVector &channelValues, qreal *hue, qreal *sat, qreal *luma) const { RGBToHSY(channelValues[0],channelValues[1],channelValues[2], hue, sat, luma, lumaCoefficients()[0], lumaCoefficients()[1], lumaCoefficients()[2]); } QVector RgbF32ColorSpace::fromHSY(qreal *hue, qreal *sat, qreal *luma) const { QVector channelValues(4); HSYToRGB(*hue, *sat, *luma, &channelValues[0],&channelValues[1],&channelValues[2], lumaCoefficients()[0], lumaCoefficients()[1], lumaCoefficients()[2]); channelValues[3]=1.0; return channelValues; } void RgbF32ColorSpace::toYUV(const QVector &channelValues, qreal *y, qreal *u, qreal *v) const { RGBToYUV(channelValues[0],channelValues[1],channelValues[2], y, u, v, lumaCoefficients()[0], lumaCoefficients()[1], lumaCoefficients()[2]); } QVector RgbF32ColorSpace::fromYUV(qreal *y, qreal *u, qreal *v) const { QVector channelValues(4); YUVToRGB(*y, *u, *v, &channelValues[0],&channelValues[1],&channelValues[2], lumaCoefficients()[0], lumaCoefficients()[1], lumaCoefficients()[2]); channelValues[3]=1.0; return channelValues; } diff --git a/plugins/color/lcms2engine/colorspaces/rgb_u16/RgbU16ColorSpace.cpp b/plugins/color/lcms2engine/colorspaces/rgb_u16/RgbU16ColorSpace.cpp index 873980caad..981910f385 100644 --- a/plugins/color/lcms2engine/colorspaces/rgb_u16/RgbU16ColorSpace.cpp +++ b/plugins/color/lcms2engine/colorspaces/rgb_u16/RgbU16ColorSpace.cpp @@ -1,107 +1,108 @@ /* * Copyright (c) 2006 Cyrille Berger * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; 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 * Library 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 "RgbU16ColorSpace.h" #include #include #include "compositeops/KoCompositeOps.h" #include "compositeops/RgbCompositeOps.h" #include "kis_dom_utils.h" +#include RgbU16ColorSpace::RgbU16ColorSpace(const QString &name, KoColorProfile *p) : LcmsColorSpace(colorSpaceId(), name, TYPE_BGRA_16, cmsSigRgbData, p) { addChannel(new KoChannelInfo(i18n("Blue"), 0 * sizeof(quint16), 2, KoChannelInfo::COLOR, KoChannelInfo::UINT16, 2, QColor(0, 0, 255))); addChannel(new KoChannelInfo(i18n("Green"), 1 * sizeof(quint16), 1, KoChannelInfo::COLOR, KoChannelInfo::UINT16, 2, QColor(0, 255, 0))); addChannel(new KoChannelInfo(i18n("Red"), 2 * sizeof(quint16), 0, KoChannelInfo::COLOR, KoChannelInfo::UINT16, 2, QColor(255, 0, 0))); addChannel(new KoChannelInfo(i18n("Alpha"), 3 * sizeof(quint16), 3, KoChannelInfo::ALPHA, KoChannelInfo::UINT16, 2)); init(); addStandardCompositeOps(this); addCompositeOp(new RgbCompositeOpIn(this)); addCompositeOp(new RgbCompositeOpOut(this)); addCompositeOp(new RgbCompositeOpBumpmap(this)); } bool RgbU16ColorSpace::willDegrade(ColorSpaceIndependence independence) const { if (independence == TO_RGBA8) { return true; } else { return false; } } KoColorSpace *RgbU16ColorSpace::clone() const { return new RgbU16ColorSpace(name(), profile()->clone()); } void RgbU16ColorSpace::colorToXML(const quint8 *pixel, QDomDocument &doc, QDomElement &colorElt) const { const KoBgrU16Traits::Pixel *p = reinterpret_cast(pixel); QDomElement labElt = doc.createElement("RGB"); labElt.setAttribute("r", KisDomUtils::toString(KoColorSpaceMaths< KoBgrU16Traits::channels_type, qreal>::scaleToA(p->red))); labElt.setAttribute("g", KisDomUtils::toString(KoColorSpaceMaths< KoBgrU16Traits::channels_type, qreal>::scaleToA(p->green))); labElt.setAttribute("b", KisDomUtils::toString(KoColorSpaceMaths< KoBgrU16Traits::channels_type, qreal>::scaleToA(p->blue))); labElt.setAttribute("space", profile()->name()); colorElt.appendChild(labElt); } void RgbU16ColorSpace::colorFromXML(quint8 *pixel, const QDomElement &elt) const { KoBgrU16Traits::Pixel *p = reinterpret_cast(pixel); p->red = KoColorSpaceMaths< qreal, KoBgrU16Traits::channels_type >::scaleToA(KisDomUtils::toDouble(elt.attribute("r"))); p->green = KoColorSpaceMaths< qreal, KoBgrU16Traits::channels_type >::scaleToA(KisDomUtils::toDouble(elt.attribute("g"))); p->blue = KoColorSpaceMaths< qreal, KoBgrU16Traits::channels_type >::scaleToA(KisDomUtils::toDouble(elt.attribute("b"))); p->alpha = KoColorSpaceMathsTraits::max; } void RgbU16ColorSpace::toHSY(const QVector &channelValues, qreal *hue, qreal *sat, qreal *luma) const { RGBToHSY(channelValues[0],channelValues[1],channelValues[2], hue, sat, luma, lumaCoefficients()[0], lumaCoefficients()[1], lumaCoefficients()[2]); } QVector RgbU16ColorSpace::fromHSY(qreal *hue, qreal *sat, qreal *luma) const { QVector channelValues(4); HSYToRGB(*hue, *sat, *luma, &channelValues[0],&channelValues[1],&channelValues[2], lumaCoefficients()[0], lumaCoefficients()[1], lumaCoefficients()[2]); channelValues[3]=1.0; return channelValues; } void RgbU16ColorSpace::toYUV(const QVector &channelValues, qreal *y, qreal *u, qreal *v) const { RGBToYUV(channelValues[0],channelValues[1],channelValues[2], y, u, v, lumaCoefficients()[0], lumaCoefficients()[1], lumaCoefficients()[2]); } QVector RgbU16ColorSpace::fromYUV(qreal *y, qreal *u, qreal *v) const { QVector channelValues(4); YUVToRGB(*y, *u, *v, &channelValues[0],&channelValues[1],&channelValues[2], lumaCoefficients()[0], lumaCoefficients()[1], lumaCoefficients()[2]); channelValues[3]=1.0; return channelValues; } diff --git a/plugins/color/lcms2engine/compositeops/RgbCompositeOpClear.h b/plugins/color/lcms2engine/compositeops/RgbCompositeOpClear.h deleted file mode 100644 index b372013237..0000000000 --- a/plugins/color/lcms2engine/compositeops/RgbCompositeOpClear.h +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (c) 2006 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 const; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program const; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#ifndef RGBCOMPOSITEOPCLEAR_H -#define RGBCOMPOSITEOPCLEAR_H - -#include - -template -class RgbCompositeOpClear : public KoCompositeOp -{ - typedef typename _CSTraits::channels_type channels_type; - typedef typename KoColorSpaceMathsTraits::compositetype compositetype; - -public: - - RgbCompositeOpClear(KoColorSpace *cs) - : KoCompositeOp(cs, COMPOSITE_CLEAR, i18n("Clear"), "") - { - } - - using KoCompositeOp::composite; - - void composite(quint8 *dstRowStart, qint32 dstRowStride, - const quint8 *srcRowStart, qint32 srcRowStride, - const quint8 *maskRowStart, qint32 maskRowStride, - qint32 rows, qint32 numColumns, - quint8 opacity, - const QBitArray &channelFlags) const override - { - Q_UNUSED(opacity); - Q_UNUSED(srcRowStart); - Q_UNUSED(srcRowStride); - Q_UNUSED(maskRowStart); - Q_UNUSED(maskRowStride); - - qint32 channelSize = sizeof(channels_type); - qint32 linesize = _CSTraits::channels_nb * channelSize * numColumns; - - if (channelFlags.isEmpty()) { - quint8 *d = dstRowStart; - while (rows-- > 0) { - memset(d, 0, linesize); - d += dstRowStride; - } - } else { - channels_type *d = reinterpret_cast(dstRowStart); - while (rows-- > 0) { - for (int channel = 0; channel < MAX_CHANNEL_RGB; channel++) { - if (channelFlags.testBit(channel)) { - memset(d, 0, channelSize); - } - d++; - } - } - } - } - -}; - -#endif diff --git a/plugins/color/lcms2engine/compositeops/RgbCompositeOpColor.h b/plugins/color/lcms2engine/compositeops/RgbCompositeOpColor.h deleted file mode 100644 index 826680a048..0000000000 --- a/plugins/color/lcms2engine/compositeops/RgbCompositeOpColor.h +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (c) 2006 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 const; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program const; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#ifndef RGBCOMPOSITEOPCOLOR_H -#define RGBCOMPOSITEOPCOLOR_H - -#include "KoColorSpaceMaths.h" -#include "KoColorConversions.h" -#include - -#define SCALE_TO_FLOAT( v ) KoColorSpaceMaths< channels_type, float>::scaleToA( v ) -#define SCALE_FROM_FLOAT( v ) KoColorSpaceMaths< float, channels_type>::scaleToA( v ) - -template -class RgbCompositeOpColor : public KoCompositeOpAlphaBase<_CSTraits, RgbCompositeOpColor<_CSTraits>, true > -{ - typedef typename _CSTraits::channels_type channels_type; - typedef typename KoColorSpaceMathsTraits::compositetype compositetype; - -public: - - RgbCompositeOpColor(KoColorSpace *cs) - : KoCompositeOpAlphaBase<_CSTraits, RgbCompositeOpColor<_CSTraits>, true >(cs, COMPOSITE_COLOR, i18n("Color"), KoCompositeOp::categoryMisc()) - { - } - - inline static channels_type selectAlpha(channels_type srcAlpha, channels_type dstAlpha) - { - return qMin(srcAlpha, dstAlpha); - } - - inline static void composeColorChannels(channels_type srcBlend, - const channels_type *src, - channels_type *dst, - bool allChannelFlags, - const QBitArray &channelFlags) - { - - float dstRed = SCALE_TO_FLOAT(dst[_CSTraits::red_pos]); - float dstGreen = SCALE_TO_FLOAT(dst[_CSTraits::green_pos]); - float dstBlue = SCALE_TO_FLOAT(dst[_CSTraits::blue_pos]); - - float srcHue; - float srcSaturation; - float srcLightness; - float dstHue; - float dstSaturation; - float dstLightness; - - float srcRed = SCALE_TO_FLOAT(src[_CSTraits::red_pos]); - float srcGreen = SCALE_TO_FLOAT(src[_CSTraits::green_pos]); - float srcBlue = SCALE_TO_FLOAT(src[_CSTraits::blue_pos]); - - RGBToHSL(srcRed, srcGreen, srcBlue, &srcHue, &srcSaturation, &srcLightness); - RGBToHSL(dstRed, dstGreen, dstBlue, &dstHue, &dstSaturation, &dstLightness); - HSLToRGB(srcHue, srcSaturation, dstLightness, &srcRed, &srcGreen, &srcBlue); - - if (allChannelFlags || channelFlags.testBit(_CSTraits::red_pos)) { - dst[_CSTraits::red_pos] = KoColorSpaceMaths::blend(SCALE_FROM_FLOAT(srcRed), SCALE_FROM_FLOAT(dstRed), srcBlend); - } - if (allChannelFlags || channelFlags.testBit(_CSTraits::green_pos)) { - dst[_CSTraits::green_pos] = KoColorSpaceMaths::blend(SCALE_FROM_FLOAT(srcGreen), SCALE_FROM_FLOAT(dstGreen), srcBlend); - } - if (allChannelFlags || channelFlags.testBit(_CSTraits::blue_pos)) { - dst[_CSTraits::blue_pos] = KoColorSpaceMaths::blend(SCALE_FROM_FLOAT(srcBlue), SCALE_FROM_FLOAT(dstBlue), srcBlend); - } - } -}; - -#endif diff --git a/plugins/color/lcms2engine/compositeops/RgbCompositeOpDarken.h b/plugins/color/lcms2engine/compositeops/RgbCompositeOpDarken.h deleted file mode 100644 index 3487beddeb..0000000000 --- a/plugins/color/lcms2engine/compositeops/RgbCompositeOpDarken.h +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (c) 2006 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 const; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program const; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#ifndef RGBCOMPOSITEOPDARKEN_H -#define RGBCOMPOSITEOPDARKEN_H - -#include - -template -class RgbCompositeOpDarken : public KoCompositeOp -{ - typedef typename _CSTraits::channels_type channels_type; - typedef typename KoColorSpaceMathsTraits::compositetype compositetype; - -public: - - RgbCompositeOpDarken(KoColorSpace *cs) - : KoCompositeOp(cs, COMPOSITE_DARKEN, i18n("Darken"), "") - { - } - - using KoCompositeOp::composite; - - void composite(quint8 *dstRowStart, qint32 dstRowStride, - const quint8 *srcRowStart, qint32 srcRowStride, - const quint8 *maskRowStart, qint32 maskRowStride, - qint32 rows, qint32 numColumns, - quint8 opacity, - const QBitArray &channelFlags) const override - { - while (rows > 0) { - const quint8 *mask = maskRowStart; - const channels_type *src = reinterpret_cast(srcRowStart); - channels_type *dst = reinterpret_cast(dstRowStart); - - for (int i = numColumns; i > 0; --i) { - channels_type srcAlpha = src[_CSTraits::alpha_pos]; - channels_type dstAlpha = dst[_CSTraits::alpha_pos]; - - srcAlpha = qMin(srcAlpha, dstAlpha); - - // apply the alphamask - if (mask != 0) { - if (*mask != OPACITY_OPAQUE_U8) { - channels_type tmpOpacity = KoColorSpaceMaths::scaleToA(*mask); - srcAlpha = KoColorSpaceMaths::multiply(srcAlpha, tmpOpacity); - } - mask++; - } - - if (srcAlpha != NATIVE_OPACITY_TRANSPARENT) { - if (opacity != OPACITY_OPAQUE_U8) { - channels_type tmpOpacity = KoColorSpaceMaths::scaleToA(opacity); - srcAlpha = KoColorSpaceMaths::multiply(src[_CSTraits::alpha_pos], tmpOpacity); - } - - channels_type srcBlend; - - if (dstAlpha == NATIVE_OPACITY_OPAQUE) { - srcBlend = srcAlpha; - } else { - channels_type newAlpha = dstAlpha + KoColorSpaceMaths::multiply(NATIVE_OPACITY_OPAQUE - dstAlpha, srcAlpha); - dst[KoBgrU8Traits::alpha_pos] = newAlpha; - - if (newAlpha != 0) { - srcBlend = KoColorSpaceMaths::divide(srcAlpha, newAlpha); - } else { - srcBlend = srcAlpha; - } - } - - for (int channel = 0; channel < MAX_CHANNEL_RGB; channel++) { - if (channelFlags.isEmpty() || channelFlags.testBit(_CSTraits::alpha_pos)) { - channels_type srcColor = src[channel]; - channels_type dstColor = dst[channel]; - - srcColor = qMin(srcColor, dstColor); - - channels_type newColor = KoColorSpaceMaths::blend(srcColor, dstColor, srcBlend); - - dst[channel] = newColor; - } - } - } - - src += _CSTraits::channels_nb; - dst += _CSTraits::channels_nb; - } - - rows--; - srcRowStart += srcRowStride; - dstRowStart += dstRowStride; - if (maskRowStart) { - maskRowStart += maskRowStride; - } - } - } -}; - -#endif diff --git a/plugins/color/lcms2engine/compositeops/RgbCompositeOpDiff.h b/plugins/color/lcms2engine/compositeops/RgbCompositeOpDiff.h deleted file mode 100644 index 22ba6c010a..0000000000 --- a/plugins/color/lcms2engine/compositeops/RgbCompositeOpDiff.h +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (c) 2006 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 const; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program const; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#ifndef RGBCOMPOSITEOPDIFF_H -#define RGBCOMPOSITEOPDIFF_H - -#include - -#define AbsoluteValue(x) ((x) < 0 ? -(x) : (x)) - -template -class RgbCompositeOpDiff : public KoCompositeOpAlphaBase<_CSTraits, RgbCompositeOpDiff<_CSTraits>, true > -{ - typedef typename _CSTraits::channels_type channels_type; - typedef typename KoColorSpaceMathsTraits::compositetype compositetype; - -public: - - RgbCompositeOpDiff(KoColorSpace *cs) - : KoCompositeOpAlphaBase<_CSTraits, RgbCompositeOpDiff<_CSTraits>, true >(cs, COMPOSITE_DIFF, i18n("Diff"), KoCompositeOp::categoryMisc()) - { - } - - inline static channels_type selectAlpha(channels_type srcAlpha, channels_type dstAlpha) - { - return qMin(srcAlpha, dstAlpha); - } - - inline static void composeColorChannels(channels_type srcBlend, - const channels_type *src, - channels_type *dst, - bool allChannelFlags, - const QBitArray &channelFlags) - { - for (uint i = 0; i < _CSTraits::channels_nb; i++) { - if ((int)i != _CSTraits::alpha_pos && (allChannelFlags || channelFlags.testBit(i))) { - - channels_type diff = (channels_type) - AbsoluteValue(src[i] - (compositetype) dst[i]); - - dst[i] = KoColorSpaceMaths::blend(diff, dst[i], srcBlend); - } - } - } - -}; - -#endif diff --git a/plugins/color/lcms2engine/compositeops/RgbCompositeOpDissolve.h b/plugins/color/lcms2engine/compositeops/RgbCompositeOpDissolve.h deleted file mode 100644 index f29f2475bd..0000000000 --- a/plugins/color/lcms2engine/compositeops/RgbCompositeOpDissolve.h +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (c) 2006 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 const; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program const; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#ifndef RGBCOMPOSITEOPDISSOLVE_H -#define RGBCOMPOSITEOPDISSOLVE_H - -#include - -template -class RgbCompositeOpDissolve : public KoCompositeOp -{ - typedef typename _CSTraits::channels_type channels_type; - typedef typename KoColorSpaceMathsTraits::compositetype compositetype; - -public: - - RgbCompositeOpDissolve(KoColorSpace *cs) - : KoCompositeOp(cs, COMPOSITE_DISSOLVE, i18n("Dissolve"), "") - { - } - - using KoCompositeOp::composite; - - void composite(quint8 *dstRowStart, qint32 dstRowStride, - const quint8 *srcRowStart, qint32 srcRowStride, - const quint8 *maskRowStart, qint32 maskRowStride, - qint32 rows, qint32 numColumns, - quint8 opacity, - const QBitArray &channelFlags) const override - { - Q_UNUSED(maskRowStart); - Q_UNUSED(maskRowStride); - - if (opacity == OPACITY_TRANSPARENT_U8) { - return; - } - - channels_type *d; - const channels_type *s; - - qint32 i; - - qreal sAlpha, dAlpha; - - while (rows-- > 0) { - d = reinterpret_cast(dstRowStart); - s = reinterpret_cast(srcRowStart); - for (i = numColumns; i > 0; i--, d += _CSTraits::channels_nb, s += _CSTraits::channels_nb) { - // XXX: correct? - if (s[_CSTraits::alpha_pos] == NATIVE_OPACITY_TRANSPARENT) { - continue; - } - - sAlpha = NATIVE_OPACITY_OPAQUE - s[_CSTraits::alpha_pos]; - dAlpha = NATIVE_OPACITY_OPAQUE - d[_CSTraits::alpha_pos]; - - if (channelFlags.isEmpty() || channelFlags.testBit(_CSTraits::red_pos)) - d[_CSTraits::red_pos] = (channels_type)(((qreal) sAlpha * s[_CSTraits::red_pos] + - (NATIVE_OPACITY_OPAQUE - sAlpha) * d[_CSTraits::red_pos]) / NATIVE_OPACITY_OPAQUE + 0.5); - - if (channelFlags.isEmpty() || channelFlags.testBit(_CSTraits::green_pos)) - d[_CSTraits::green_pos] = (channels_type)(((qreal) sAlpha * s[_CSTraits::green_pos] + - (NATIVE_OPACITY_OPAQUE - sAlpha) * d[_CSTraits::green_pos]) / NATIVE_OPACITY_OPAQUE + 0.5); - - if (channelFlags.isEmpty() || channelFlags.testBit(_CSTraits::blue_pos)) - d[_CSTraits::blue_pos] = (channels_type)(((qreal) sAlpha * s[_CSTraits::blue_pos] + - (NATIVE_OPACITY_OPAQUE - sAlpha) * d[_CSTraits::blue_pos]) / NATIVE_OPACITY_OPAQUE + 0.5); - - if (channelFlags.isEmpty() || channelFlags.testBit(_CSTraits::alpha_pos)) { - d[_CSTraits::alpha_pos] = NATIVE_OPACITY_OPAQUE; - } - - } - dstRowStart += dstRowStride; - srcRowStart += srcRowStride; - - } - } - -}; - -#endif diff --git a/plugins/color/lcms2engine/compositeops/RgbCompositeOpHue.h b/plugins/color/lcms2engine/compositeops/RgbCompositeOpHue.h deleted file mode 100644 index 35e4f8920f..0000000000 --- a/plugins/color/lcms2engine/compositeops/RgbCompositeOpHue.h +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright (c) 2006 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 const; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program const; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#ifndef RGBCOMPOSITEOPHUE_H -#define RGBCOMPOSITEOPHUE_H - -#include "KoColorConversions.h" -#include - -#define SCALE_TO_FLOAT( v ) KoColorSpaceMaths< channels_type, float>::scaleToA( v ) -#define SCALE_FROM_FLOAT( v ) KoColorSpaceMaths< float, channels_type>::scaleToA( v ) - -template -class RgbCompositeOpHue : public KoCompositeOp -{ - typedef typename _CSTraits::channels_type channels_type; - typedef typename KoColorSpaceMathsTraits::compositetype compositetype; - -public: - - RgbCompositeOpHue(KoColorSpace *cs) - : KoCompositeOp(cs, COMPOSITE_HUE, i18n("Hue"), "") - { - } - - using KoCompositeOp::composite; - - void composite(quint8 *dstRowStart, qint32 dstRowStride, - const quint8 *srcRowStart, qint32 srcRowStride, - const quint8 *maskRowStart, qint32 maskRowStride, - qint32 rows, qint32 numColumns, - quint8 opacity, - const QBitArray &channelFlags) const override - { - channels_type *dst; - const channels_type *src; - - while (rows > 0) { - const quint8 *mask = maskRowStart; - src = reinterpret_cast(srcRowStart); - dst = reinterpret_cast(dstRowStart); - - for (int i = numColumns; i > 0; --i) { - channels_type srcAlpha = src[_CSTraits::alpha_pos]; - channels_type dstAlpha = dst[_CSTraits::alpha_pos]; - - srcAlpha = qMin(srcAlpha, dstAlpha); - - // apply the alphamask - if (mask != 0) { - if (*mask != OPACITY_OPAQUE_U8) { - channels_type tmpOpacity = KoColorSpaceMaths::scaleToA(*mask); - srcAlpha = KoColorSpaceMaths::multiply(srcAlpha, tmpOpacity); - } - mask++; - } - - if (srcAlpha != NATIVE_OPACITY_TRANSPARENT) { - - if (opacity != OPACITY_OPAQUE_U8) { - channels_type tmpOpacity = KoColorSpaceMaths::scaleToA(opacity); - srcAlpha = KoColorSpaceMaths::multiply(src[_CSTraits::alpha_pos], tmpOpacity); - } - - channels_type srcBlend; - - if (dstAlpha == NATIVE_OPACITY_OPAQUE) { - srcBlend = srcAlpha; - } else { - channels_type newAlpha = dstAlpha + KoColorSpaceMaths::multiply(NATIVE_OPACITY_OPAQUE - dstAlpha, srcAlpha); - dst[_CSTraits::alpha_pos] = newAlpha; - - if (newAlpha != 0) { - srcBlend = KoColorSpaceMaths::divide(srcAlpha, newAlpha); - } else { - srcBlend = srcAlpha; - } - } - - float dstRed = SCALE_TO_FLOAT(dst[_CSTraits::red_pos]); - float dstGreen = SCALE_TO_FLOAT(dst[_CSTraits::green_pos]); - float dstBlue = SCALE_TO_FLOAT(dst[_CSTraits::blue_pos]); - - float srcHue; - float srcSaturation; - float srcValue; - float dstHue; - float dstSaturation; - float dstValue; - - float srcRed = SCALE_TO_FLOAT(src[_CSTraits::red_pos]); - float srcGreen = SCALE_TO_FLOAT(src[_CSTraits::green_pos]); - float srcBlue = SCALE_TO_FLOAT(src[_CSTraits::blue_pos]); - - RGBToHSV(srcRed, srcGreen, srcBlue, &srcHue, &srcSaturation, &srcValue); - RGBToHSV(dstRed, dstGreen, dstBlue, &dstHue, &dstSaturation, &dstValue); - - HSVToRGB(srcHue, dstSaturation, dstValue, &srcRed, &srcGreen, &srcBlue); - - if (channelFlags.isEmpty() || channelFlags.testBit(_CSTraits::red_pos)) { - dst[_CSTraits::red_pos] = KoColorSpaceMaths::blend(SCALE_FROM_FLOAT(srcRed), SCALE_FROM_FLOAT(dstRed), srcBlend); - } - if (channelFlags.isEmpty() || channelFlags.testBit(_CSTraits::green_pos)) { - dst[_CSTraits::green_pos] = KoColorSpaceMaths::blend(SCALE_FROM_FLOAT(srcGreen), SCALE_FROM_FLOAT(dstGreen), srcBlend); - } - if (channelFlags.isEmpty() || channelFlags.testBit(_CSTraits::blue_pos)) { - dst[_CSTraits::blue_pos] = KoColorSpaceMaths::blend(SCALE_FROM_FLOAT(srcBlue), SCALE_FROM_FLOAT(dstBlue), srcBlend); - } - } - - src += _CSTraits::channels_nb; - dst += _CSTraits::channels_nb; - } - - rows--; - srcRowStart += srcRowStride; - dstRowStart += dstRowStride; - if (maskRowStart) { - maskRowStart += maskRowStride; - } - } - } -}; - -#endif diff --git a/plugins/color/lcms2engine/compositeops/RgbCompositeOpLighten.h b/plugins/color/lcms2engine/compositeops/RgbCompositeOpLighten.h deleted file mode 100644 index 1d560241fd..0000000000 --- a/plugins/color/lcms2engine/compositeops/RgbCompositeOpLighten.h +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright (c) 2006 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 const; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program const; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#ifndef RGBCOMPOSITEOPLIGHTEN_H -#define RGBCOMPOSITEOPLIGHTEN_H - -#include - -template -class RgbCompositeOpLighten : public KoCompositeOp -{ - typedef typename _CSTraits::channels_type channels_type; - typedef typename KoColorSpaceMathsTraits::compositetype compositetype; - -public: - - RgbCompositeOpLighten(KoColorSpace *cs) - : KoCompositeOp(cs, COMPOSITE_LIGHTEN, i18n("Lighten"), "") - { - } - - using KoCompositeOp::composite; - - void composite(quint8 *dstRowStart, qint32 dstRowStride, - const quint8 *srcRowStart, qint32 srcRowStride, - const quint8 *maskRowStart, qint32 maskRowStride, - qint32 rows, qint32 numColumns, - quint8 opacity, - const QBitArray &channelFlags) const override - { - - while (rows > 0) { - const quint8 *mask = maskRowStart; - const channels_type *src = reinterpret_cast(srcRowStart); - channels_type *dst = reinterpret_cast(dstRowStart); - - for (int i = numColumns; i > 0; --i) { - channels_type srcAlpha = src[_CSTraits::alpha_pos]; - channels_type dstAlpha = dst[_CSTraits::alpha_pos]; - - srcAlpha = qMin(srcAlpha, dstAlpha); - - // apply the alphamask - if (mask != 0) { - if (*mask != OPACITY_OPAQUE_U8) { - channels_type tmpOpacity = KoColorSpaceMaths::scaleToA(*mask); - srcAlpha = KoColorSpaceMaths::multiply(srcAlpha, tmpOpacity); - } - mask++; - } - - if (srcAlpha != NATIVE_OPACITY_TRANSPARENT) { - - if (opacity != OPACITY_OPAQUE_U8) { - channels_type tmpOpacity = KoColorSpaceMaths::scaleToA(opacity); - srcAlpha = KoColorSpaceMaths::multiply(src[_CSTraits::alpha_pos], tmpOpacity); - } - - channels_type srcBlend; - - if (dstAlpha == NATIVE_OPACITY_OPAQUE) { - srcBlend = srcAlpha; - } else { - channels_type newAlpha = dstAlpha + KoColorSpaceMaths::multiply(NATIVE_OPACITY_OPAQUE - dstAlpha, srcAlpha); - dst[_CSTraits::alpha_pos] = newAlpha; - - if (newAlpha != 0) { - srcBlend = KoColorSpaceMaths::divide(srcAlpha, newAlpha); - } else { - srcBlend = srcAlpha; - } - } - - for (int channel = 0; channel < MAX_CHANNEL_RGB; channel++) { - if (channelFlags.isEmpty() || channelFlags.testBit(_CSTraits::alpha_pos)) { - channels_type srcColor = src[channel]; - channels_type dstColor = dst[channel]; - - srcColor = qMax(srcColor, dstColor); - - channels_type newColor = KoColorSpaceMaths::blend(srcColor, dstColor, srcBlend); - - dst[channel] = newColor; - } - } - } - - src += _CSTraits::channels_nb; - dst += _CSTraits::channels_nb; - } - - rows--; - srcRowStart += srcRowStride; - dstRowStart += dstRowStride; - if (maskRowStart) { - maskRowStart += maskRowStride; - } - } - } -}; - -#endif diff --git a/plugins/color/lcms2engine/compositeops/RgbCompositeOpSaturation.h b/plugins/color/lcms2engine/compositeops/RgbCompositeOpSaturation.h deleted file mode 100644 index 4dba82616b..0000000000 --- a/plugins/color/lcms2engine/compositeops/RgbCompositeOpSaturation.h +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright (c) 2006 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 const; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program const; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#ifndef RGBCOMPOSITEOPSATURATION_H -#define RGBCOMPOSITEOPSATURATION_H - -#include "KoColorConversions.h" -#include - -#define SCALE_TO_FLOAT( v ) KoColorSpaceMaths< channels_type, float>::scaleToA( v ) -#define SCALE_FROM_FLOAT( v ) KoColorSpaceMaths< float, channels_type>::scaleToA( v ) - -template -class RgbCompositeOpSaturation: public KoCompositeOp -{ - typedef typename _CSTraits::channels_type channels_type; - typedef typename KoColorSpaceMathsTraits::compositetype compositetype; - -public: - - RgbCompositeOpSaturation(KoColorSpace *cs) - : KoCompositeOp(cs, COMPOSITE_SATURATION, i18n("Saturation"), "") - { - } - - using KoCompositeOp::composite; - - void composite(quint8 *dstRowStart, qint32 dstRowStride, - const quint8 *srcRowStart, qint32 srcRowStride, - const quint8 *maskRowStart, qint32 maskRowStride, - qint32 rows, qint32 numColumns, - quint8 opacity, - const QBitArray &channelFlags) const override - { - - while (rows > 0) { - const quint8 *mask = maskRowStart; - const channels_type *src = reinterpret_cast(srcRowStart); - channels_type *dst = reinterpret_cast(dstRowStart); - - for (int i = numColumns; i > 0; --i) { - channels_type srcAlpha = src[_CSTraits::alpha_pos]; - channels_type dstAlpha = dst[_CSTraits::alpha_pos]; - - srcAlpha = qMin(srcAlpha, dstAlpha); - - // apply the alphamask - if (mask != 0) { - if (*mask != OPACITY_OPAQUE_U8) { - channels_type tmpOpacity = KoColorSpaceMaths::scaleToA(*mask); - srcAlpha = KoColorSpaceMaths::multiply(srcAlpha, tmpOpacity); - } - mask++; - } - - if (srcAlpha != NATIVE_OPACITY_TRANSPARENT) { - - if (opacity != OPACITY_OPAQUE_U8) { - channels_type tmpOpacity = KoColorSpaceMaths::scaleToA(opacity); - srcAlpha = KoColorSpaceMaths::multiply(src[_CSTraits::alpha_pos], tmpOpacity); - } - - channels_type srcBlend; - - if (dstAlpha == NATIVE_OPACITY_OPAQUE) { - srcBlend = srcAlpha; - } else { - channels_type newAlpha = dstAlpha + KoColorSpaceMaths::multiply(NATIVE_OPACITY_OPAQUE - dstAlpha, srcAlpha); - dst[_CSTraits::alpha_pos] = newAlpha; - - if (newAlpha != 0) { - srcBlend = KoColorSpaceMaths::divide(srcAlpha, newAlpha); - } else { - srcBlend = srcAlpha; - } - } - - float dstRed = SCALE_TO_FLOAT(dst[_CSTraits::red_pos]); - float dstGreen = SCALE_TO_FLOAT(dst[_CSTraits::green_pos]); - float dstBlue = SCALE_TO_FLOAT(dst[_CSTraits::blue_pos]); - - float srcHue; - float srcSaturation; - float srcValue; - float dstHue; - float dstSaturation; - float dstValue; - - float srcRed = SCALE_TO_FLOAT(src[_CSTraits::red_pos]); - float srcGreen = SCALE_TO_FLOAT(src[_CSTraits::green_pos]); - float srcBlue = SCALE_TO_FLOAT(src[_CSTraits::blue_pos]); - - RGBToHSV(srcRed, srcGreen, srcBlue, &srcHue, &srcSaturation, &srcValue); - RGBToHSV(dstRed, dstGreen, dstBlue, &dstHue, &dstSaturation, &dstValue); - - HSVToRGB(dstHue, srcSaturation, dstValue, &srcRed, &srcGreen, &srcBlue); - - if (channelFlags.isEmpty() || channelFlags.testBit(_CSTraits::red_pos)) { - dst[_CSTraits::red_pos] = KoColorSpaceMaths::blend(SCALE_FROM_FLOAT(srcRed), SCALE_FROM_FLOAT(dstRed), srcBlend); - } - if (channelFlags.isEmpty() || channelFlags.testBit(_CSTraits::green_pos)) { - dst[_CSTraits::green_pos] = KoColorSpaceMaths::blend(SCALE_FROM_FLOAT(srcGreen), SCALE_FROM_FLOAT(dstGreen), srcBlend); - } - if (channelFlags.isEmpty() || channelFlags.testBit(_CSTraits::blue_pos)) { - dst[_CSTraits::blue_pos] = KoColorSpaceMaths::blend(SCALE_FROM_FLOAT(srcBlue), SCALE_FROM_FLOAT(dstBlue), srcBlend); - } - } - - src += _CSTraits::channels_nb; - dst += _CSTraits::channels_nb; - } - - rows--; - srcRowStart += srcRowStride; - dstRowStart += dstRowStride; - if (maskRowStart) { - maskRowStart += maskRowStride; - } - } - } -}; - -#endif diff --git a/plugins/color/lcms2engine/compositeops/RgbCompositeOpValue.h b/plugins/color/lcms2engine/compositeops/RgbCompositeOpValue.h deleted file mode 100644 index fc1426ad9d..0000000000 --- a/plugins/color/lcms2engine/compositeops/RgbCompositeOpValue.h +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright (c) 2006 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 const; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program const; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#ifndef RGBCOMPOSITEOPVALUE_H -#define RGBCOMPOSITEOPVALUE_H - -#include "KoColorConversions.h" -#include - -#define SCALE_TO_FLOAT( v ) KoColorSpaceMaths< channels_type, float>::scaleToA( v ) -#define SCALE_FROM_FLOAT( v ) KoColorSpaceMaths< float, channels_type>::scaleToA( v ) - -template -class RgbCompositeOpValue: public KoCompositeOp -{ - typedef typename _CSTraits::channels_type channels_type; - typedef typename KoColorSpaceMathsTraits::compositetype compositetype; - -public: - - RgbCompositeOpValue(KoColorSpace *cs) - : KoCompositeOp(cs, COMPOSITE_VALUE, i18nc("HSV Value","Value"), "") - { - } - - using KoCompositeOp::composite; - - void composite(quint8 *dstRowStart, qint32 dstRowStride, - const quint8 *srcRowStart, qint32 srcRowStride, - const quint8 *maskRowStart, qint32 maskRowStride, - qint32 rows, qint32 numColumns, - quint8 opacity, - const QBitArray &channelFlags) const override - { - - while (rows > 0) { - const quint8 *mask = maskRowStart; - const channels_type *src = reinterpret_cast(srcRowStart); - channels_type *dst = reinterpret_cast(dstRowStart); - - for (int i = numColumns; i > 0; --i) { - channels_type srcAlpha = src[_CSTraits::alpha_pos]; - channels_type dstAlpha = dst[_CSTraits::alpha_pos]; - - srcAlpha = qMin(srcAlpha, dstAlpha); - - // apply the alphamask - if (mask != 0) { - if (*mask != OPACITY_OPAQUE_U8) { - channels_type tmpOpacity = KoColorSpaceMaths::scaleToA(*mask); - srcAlpha = KoColorSpaceMaths::multiply(srcAlpha, tmpOpacity); - } - mask++; - } - - if (srcAlpha != NATIVE_OPACITY_TRANSPARENT) { - - if (opacity != OPACITY_OPAQUE_U8) { - channels_type tmpOpacity = KoColorSpaceMaths::scaleToA(opacity); - srcAlpha = KoColorSpaceMaths::multiply(src[_CSTraits::alpha_pos], tmpOpacity); - } - - channels_type srcBlend; - - if (dstAlpha == NATIVE_OPACITY_OPAQUE) { - srcBlend = srcAlpha; - } else { - channels_type newAlpha = dstAlpha + KoColorSpaceMaths::multiply(NATIVE_OPACITY_OPAQUE - dstAlpha, srcAlpha); - dst[_CSTraits::alpha_pos] = newAlpha; - - if (newAlpha != 0) { - srcBlend = KoColorSpaceMaths::divide(srcAlpha, newAlpha); - } else { - srcBlend = srcAlpha; - } - } - - float dstRed = SCALE_TO_FLOAT(dst[_CSTraits::red_pos]); - float dstGreen = SCALE_TO_FLOAT(dst[_CSTraits::green_pos]); - float dstBlue = SCALE_TO_FLOAT(dst[_CSTraits::blue_pos]); - - float srcHue; - float srcSaturation; - float srcValue; - float dstHue; - float dstSaturation; - float dstValue; - - float srcRed = SCALE_TO_FLOAT(src[_CSTraits::red_pos]); - float srcGreen = SCALE_TO_FLOAT(src[_CSTraits::green_pos]); - float srcBlue = SCALE_TO_FLOAT(src[_CSTraits::blue_pos]); - - RGBToHSV(srcRed, srcGreen, srcBlue, &srcHue, &srcSaturation, &srcValue); - RGBToHSV(dstRed, dstGreen, dstBlue, &dstHue, &dstSaturation, &dstValue); - - HSVToRGB(dstHue, dstSaturation, srcValue, &srcRed, &srcGreen, &srcBlue); - - if (channelFlags.isEmpty() || channelFlags.testBit(_CSTraits::red_pos)) { - dst[_CSTraits::red_pos] = KoColorSpaceMaths::blend(SCALE_FROM_FLOAT(srcRed), SCALE_FROM_FLOAT(dstRed), srcBlend); - } - if (channelFlags.isEmpty() || channelFlags.testBit(_CSTraits::green_pos)) { - dst[_CSTraits::green_pos] = KoColorSpaceMaths::blend(SCALE_FROM_FLOAT(srcGreen), SCALE_FROM_FLOAT(dstGreen), srcBlend); - } - if (channelFlags.isEmpty() || channelFlags.testBit(_CSTraits::blue_pos)) { - dst[_CSTraits::blue_pos] = KoColorSpaceMaths::blend(SCALE_FROM_FLOAT(srcBlue), SCALE_FROM_FLOAT(dstBlue), srcBlend); - } - } - - src += _CSTraits::channels_nb; - dst += _CSTraits::channels_nb; - } - - rows--; - srcRowStart += srcRowStride; - dstRowStart += dstRowStride; - if (maskRowStart) { - maskRowStart += maskRowStride; - } - } - } -}; - -#endif diff --git a/plugins/color/lcms2engine/compositeops/RgbCompositeOps.h b/plugins/color/lcms2engine/compositeops/RgbCompositeOps.h index c4ac1b266a..c1b801029d 100644 --- a/plugins/color/lcms2engine/compositeops/RgbCompositeOps.h +++ b/plugins/color/lcms2engine/compositeops/RgbCompositeOps.h @@ -1,77 +1,68 @@ /* * Copyright (c) 2006 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 const; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program const; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Some code is derived from GraphicsMagick/magick/composite.c and is subject to the following license and copyright: Copyright (C) 2002 GraphicsMagick Group, an organization dedicated to making software imaging solutions freely available. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files ("GraphicsMagick"), to deal in GraphicsMagick without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of GraphicsMagick, and to permit persons to whom GraphicsMagick is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of GraphicsMagick. The software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose and noninfringement. In no event shall GraphicsMagick Group be liable for any claim, damages or other liability, whether in an action of contract, tort or otherwise, arising from, out of or in connection with GraphicsMagick or the use or other dealings in GraphicsMagick. Except as contained in this notice, the name of the GraphicsMagick Group shall not be used in advertising or otherwise to promote the sale, use or other dealings in GraphicsMagick without prior written authorization from the GraphicsMagick Group. Other code is derived from gwenview/src/qxcfi.* - this is released under the terms of the LGPL */ #ifndef RGBCOMPOSITEOPS_H #define RGBCOMPOSITEOPS_H #include "KoColorSpaceConstants.h" #define NATIVE_OPACITY_OPAQUE KoColorSpaceMathsTraits::unitValue #define NATIVE_OPACITY_TRANSPARENT KoColorSpaceMathsTraits::zeroValue const qint32 MAX_CHANNEL_RGB = 3; #include "RgbCompositeOpIn.h" #include "RgbCompositeOpOut.h" -#include "RgbCompositeOpDiff.h" #include "RgbCompositeOpBumpmap.h" -#include "RgbCompositeOpClear.h" -#include "RgbCompositeOpDissolve.h" -#include "RgbCompositeOpDarken.h" -#include "RgbCompositeOpLighten.h" -#include "RgbCompositeOpHue.h" -#include "RgbCompositeOpSaturation.h" -#include "RgbCompositeOpValue.h" -#include "RgbCompositeOpColor.h" #endif diff --git a/plugins/dockers/CMakeLists.txt b/plugins/dockers/CMakeLists.txt index add898a795..db5e216577 100644 --- a/plugins/dockers/CMakeLists.txt +++ b/plugins/dockers/CMakeLists.txt @@ -1,36 +1,36 @@ add_subdirectory(layerdocker) if(HAVE_OPENEXR) add_subdirectory(smallcolorselector) endif() add_subdirectory(specificcolorselector) add_subdirectory(digitalmixer) add_subdirectory(advancedcolorselector) add_subdirectory(presetdocker) add_subdirectory(historydocker) add_subdirectory(channeldocker) add_subdirectory(artisticcolorselector) add_subdirectory(tasksetdocker) add_subdirectory(compositiondocker) add_subdirectory(patterndocker) add_subdirectory(griddocker) add_subdirectory(arrangedocker) if(HAVE_OCIO) add_subdirectory(lut) endif() add_subdirectory(overview) add_subdirectory(palettedocker) add_subdirectory(animation) add_subdirectory(presethistory) add_subdirectory(svgcollectiondocker) add_subdirectory(histogram) add_subdirectory(gamutmask) if(NOT APPLE AND HAVE_QT_QUICK) add_subdirectory(touchdocker) option(ENABLE_CPU_THROTTLE "Build the CPU Throttle Docker" OFF) if (ENABLE_CPU_THROTTLE) add_subdirectory(throttle) endif() endif() add_subdirectory(logdocker) -add_subdirectory(snapshotdocker) \ No newline at end of file +add_subdirectory(snapshotdocker) diff --git a/plugins/dockers/advancedcolorselector/kis_color_selector_container.cpp b/plugins/dockers/advancedcolorselector/kis_color_selector_container.cpp index 1366fc68e8..050074edb1 100644 --- a/plugins/dockers/advancedcolorselector/kis_color_selector_container.cpp +++ b/plugins/dockers/advancedcolorselector/kis_color_selector_container.cpp @@ -1,251 +1,229 @@ /* * Copyright (c) 2010 Adam Celarek * * 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 */ #include "kis_color_selector_container.h" #include "kis_color_selector.h" #include "kis_my_paint_shade_selector.h" #include "kis_minimal_shade_selector.h" #include #include #include #include #include #include #include #include #include #include #include "KisViewManager.h" #include "kis_canvas2.h" #include "kis_canvas_resource_provider.h" #include "kis_node_manager.h" #include "kis_node.h" #include "kis_paint_device.h" #include "kis_action_registry.h" KisColorSelectorContainer::KisColorSelectorContainer(QWidget *parent) : QWidget(parent), m_colorSelector(new KisColorSelector(this)), m_myPaintShadeSelector(new KisMyPaintShadeSelector(this)), m_minimalShadeSelector(new KisMinimalShadeSelector(this)), m_shadeSelector(m_myPaintShadeSelector), m_gamutMaskToolbar(new KisGamutMaskToolbar(this)), m_showColorSelector(true), m_canvas(0) { m_widgetLayout = new QBoxLayout(QBoxLayout::TopToBottom, this); m_widgetLayout->setSpacing(0); m_widgetLayout->setMargin(0); m_gamutMaskToolbar->setContentsMargins(0, 0, 0, 5); m_gamutMaskToolbar->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); m_colorSelector->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); m_myPaintShadeSelector->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); m_minimalShadeSelector->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); m_widgetLayout->addWidget(m_gamutMaskToolbar); m_widgetLayout->addWidget(m_colorSelector); m_widgetLayout->addWidget(m_myPaintShadeSelector); m_widgetLayout->addWidget(m_minimalShadeSelector); m_gamutMaskToolbar->hide(); m_myPaintShadeSelector->hide(); m_minimalShadeSelector->hide(); connect(m_colorSelector,SIGNAL(settingsButtonClicked()), SIGNAL(openSettings())); connect(this, SIGNAL(settingsChanged()), m_colorSelector, SLOT(updateSettings())); connect(this, SIGNAL(settingsChanged()), m_myPaintShadeSelector, SLOT(updateSettings())); connect(this, SIGNAL(settingsChanged()), this, SLOT(updateSettings())); connect(this, SIGNAL(settingsChanged()), m_minimalShadeSelector, SLOT(updateSettings())); m_colorSelAction = KisActionRegistry::instance()->makeQAction("show_color_selector", this); connect(m_colorSelAction, SIGNAL(triggered()), m_colorSelector, SLOT(showPopup()), Qt::UniqueConnection); m_mypaintAction = KisActionRegistry::instance()->makeQAction("show_mypaint_shade_selector", this); connect(m_mypaintAction, SIGNAL(triggered()), m_myPaintShadeSelector, SLOT(showPopup()), Qt::UniqueConnection); m_minimalAction = KisActionRegistry::instance()->makeQAction("show_minimal_shade_selector", this); connect(m_minimalAction, SIGNAL(triggered()), m_minimalShadeSelector, SLOT(showPopup()), Qt::UniqueConnection); } void KisColorSelectorContainer::unsetCanvas() { m_colorSelector->hasAtLeastOneDocument(doesAtleastOneDocumentExist()); m_colorSelector->unsetCanvas(); m_myPaintShadeSelector->unsetCanvas(); m_minimalShadeSelector->unsetCanvas(); m_canvas = 0; } bool KisColorSelectorContainer::doesAtleastOneDocumentExist() { if (m_canvas && m_canvas->viewManager() && m_canvas->viewManager()->document() ) { if (m_canvas->viewManager()->document()->image()->height() == 0) { return false; } else { return true; } } else { return false; } } void KisColorSelectorContainer::slotUpdateIcons() { m_colorSelector->updateIcons(); } void KisColorSelectorContainer::setCanvas(KisCanvas2* canvas) { if (m_canvas) { m_canvas->disconnectCanvasObserver(this); m_canvas->viewManager()->nodeManager()->disconnect(this); KActionCollection *ac = m_canvas->viewManager()->actionCollection(); ac->takeAction(ac->action("show_color_selector")); ac->takeAction(ac->action("show_mypaint_shade_selector")); ac->takeAction(ac->action("show_minimal_shade_selector")); } m_canvas = canvas; m_colorSelector->setCanvas(canvas); m_myPaintShadeSelector->setCanvas(canvas); m_minimalShadeSelector->setCanvas(canvas); m_colorSelector->hasAtLeastOneDocument(doesAtleastOneDocumentExist()); if (m_canvas && m_canvas->viewManager()) { - if (m_canvas->viewManager()->nodeManager()) { - connect(m_canvas->viewManager()->nodeManager(), SIGNAL(sigLayerActivated(KisLayerSP)), SLOT(reactOnLayerChange()), Qt::UniqueConnection); - } connect(m_canvas->viewManager()->canvasResourceProvider(), SIGNAL(sigGamutMaskChanged(KoGamutMask*)), m_colorSelector, SLOT(slotGamutMaskSet(KoGamutMask*)), Qt::UniqueConnection); connect(m_canvas->viewManager()->canvasResourceProvider(), SIGNAL(sigGamutMaskUnset()), m_colorSelector, SLOT(slotGamutMaskUnset()), Qt::UniqueConnection); connect(m_canvas->viewManager()->canvasResourceProvider(), SIGNAL(sigGamutMaskPreviewUpdate()), m_colorSelector, SLOT(slotGamutMaskPreviewUpdate()), Qt::UniqueConnection); connect(m_canvas->viewManager()->canvasResourceProvider(), SIGNAL(sigGamutMaskDeactivated()), m_colorSelector, SLOT(slotGamutMaskDeactivate()), Qt::UniqueConnection); m_gamutMaskToolbar->connectMaskSignals(m_canvas->viewManager()->canvasResourceProvider()); KActionCollection* actionCollection = canvas->viewManager()->actionCollection(); actionCollection->addAction("show_color_selector", m_colorSelAction); actionCollection->addAction("show_mypaint_shade_selector", m_mypaintAction); actionCollection->addAction("show_minimal_shade_selector", m_minimalAction); } } void KisColorSelectorContainer::updateSettings() { KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector"); m_onDockerResizeSetting = (int)cfg.readEntry("onDockerResize", 0); m_showColorSelector = (bool) cfg.readEntry("showColorSelector", true); if (m_showColorSelector) { m_colorSelector->show(); if (m_colorSelector->configuration().mainType == KisColorSelectorConfiguration::Wheel) { m_gamutMaskToolbar->show(); } else { m_gamutMaskToolbar->hide(); } } else { m_colorSelector->hide(); m_gamutMaskToolbar->hide(); } QString type = cfg.readEntry("shadeSelectorType", "Minimal"); QWidget* newShadeSelector; if(type=="MyPaint") newShadeSelector = m_myPaintShadeSelector; else if (type=="Minimal") newShadeSelector = m_minimalShadeSelector; else newShadeSelector = 0; if(m_shadeSelector!=newShadeSelector && m_shadeSelector!=0) { m_shadeSelector->hide(); } m_shadeSelector=newShadeSelector; if(m_shadeSelector!=0) m_shadeSelector->show(); } -void KisColorSelectorContainer::reactOnLayerChange() -{ - if (m_canvas) { - KisNodeSP node = m_canvas->viewManager()->canvasResourceProvider()->currentNode(); - if (node) { - KisPaintDeviceSP device = node->paintDevice(); - if (device) { - m_colorSelAction->setEnabled(true); - m_mypaintAction->setEnabled(true); - m_minimalAction->setEnabled(true); - } - else { - // m_colorSelAction->setEnabled(false); - // m_mypaintAction->setEnabled(false); - // m_minimalAction->setEnabled(false); - } - } - } -} void KisColorSelectorContainer::resizeEvent(QResizeEvent * e) { if(m_shadeSelector!=0) { int minimumHeightForBothWidgets = m_colorSelector->minimumHeight()+m_shadeSelector->minimumHeight()+30; //+30 for the buttons (temporarily) if(height()hide(); } else { m_shadeSelector->show(); } // m_onDockerResizeSetting==0 is allow horizontal layout if(height() < width() && m_onDockerResizeSetting==0 && m_shadeSelector!=m_minimalShadeSelector) { m_widgetLayout->setDirection(QBoxLayout::LeftToRight); } else { m_widgetLayout->setDirection(QBoxLayout::TopToBottom); } } QWidget::resizeEvent(e); } diff --git a/plugins/dockers/advancedcolorselector/kis_color_selector_container.h b/plugins/dockers/advancedcolorselector/kis_color_selector_container.h index 0892b74a95..5d95cf561c 100644 --- a/plugins/dockers/advancedcolorselector/kis_color_selector_container.h +++ b/plugins/dockers/advancedcolorselector/kis_color_selector_container.h @@ -1,76 +1,75 @@ /* * Copyright (c) 2010 Adam Celarek * * 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 */ #ifndef KIS_COLOR_SELECTOR_CONTAINER_H #define KIS_COLOR_SELECTOR_CONTAINER_H #include #include #include class KisColorSelector; class KisMyPaintShadeSelector; class KisMinimalShadeSelector; class QBoxLayout; class QAction; class KisGamutMaskToolbar; class KisColorSelectorContainer : public QWidget { Q_OBJECT public: explicit KisColorSelectorContainer(QWidget *parent = 0); void setCanvas(KisCanvas2* canvas); void unsetCanvas(); bool doesAtleastOneDocumentExist(); enum ShadeSelectorType{MyPaintSelector, MinimalSelector, NoSelector}; public Q_SLOTS: void slotUpdateIcons(); Q_SIGNALS: void openSettings(); void settingsChanged(); protected Q_SLOTS: void updateSettings(); - void reactOnLayerChange(); protected: void resizeEvent(QResizeEvent *) override; private: KisColorSelector* m_colorSelector; KisMyPaintShadeSelector* m_myPaintShadeSelector; KisMinimalShadeSelector* m_minimalShadeSelector; QWidget* m_shadeSelector; KisGamutMaskToolbar* m_gamutMaskToolbar; int m_onDockerResizeSetting; bool m_showColorSelector; QBoxLayout* m_widgetLayout; QAction * m_colorSelAction; QAction * m_mypaintAction; QAction * m_minimalAction; QPointer m_canvas; }; #endif // KIS_COLOR_SELECTOR_CONTAINER_H diff --git a/plugins/dockers/advancedcolorselector/kis_color_selector_ng_docker_widget.cpp b/plugins/dockers/advancedcolorselector/kis_color_selector_ng_docker_widget.cpp index 494747a628..ea1e2fb3e6 100644 --- a/plugins/dockers/advancedcolorselector/kis_color_selector_ng_docker_widget.cpp +++ b/plugins/dockers/advancedcolorselector/kis_color_selector_ng_docker_widget.cpp @@ -1,286 +1,269 @@ /* * Copyright (c) 2010 Adam Celarek * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_color_selector_ng_docker_widget.h" #include "ui_wdg_color_selector_settings.h" #include #include #include #include #include #include #include #include #include #include "kis_canvas2.h" #include "KisViewManager.h" #include "kis_node_manager.h" #include "kis_canvas_resource_provider.h" #include "kis_color_space_selector.h" #include "kis_preference_set_registry.h" #include "kis_node.h" #include "kis_paint_device.h" #include "kis_color_history.h" #include "kis_common_colors.h" #include "kis_color_selector_settings.h" #include "kis_color_selector_container.h" #include "kis_action_registry.h" KisColorSelectorNgDockerWidget::KisColorSelectorNgDockerWidget(QWidget *parent) : QWidget(parent), m_colorHistoryAction(0), m_commonColorsAction(0), m_widgetLayout(0), m_mainLayout(0), m_horizontalPatchesContainer(0), m_sidebarLayout(0), m_verticalColorPatchesLayout(0), m_horizontalColorPatchesLayout(0), m_fallbackSettingsButton(new QToolButton(this)), m_canvas(0) { setAutoFillBackground(true); m_colorSelectorContainer = new KisColorSelectorContainer(this); m_colorHistoryWidget = new KisColorHistory(this); m_commonColorsWidget = new KisCommonColors(this); //default settings //remember to also change the default in the ui file //shade selector // fallback settings button when the color selector is disabled m_fallbackSettingsButton->setIcon(KisIconUtils::loadIcon("configure")); m_fallbackSettingsButton->setIconSize(QSize(22,22)); m_fallbackSettingsButton->setAutoRaise(true); m_fallbackSettingsButton->hide(); //layout m_widgetLayout = new QHBoxLayout(); m_widgetLayout->setSpacing(0); m_widgetLayout->setMargin(0); m_mainLayout = new QVBoxLayout(); m_mainLayout->setSpacing(0); m_mainLayout->setMargin(0); m_horizontalPatchesContainer = new QHBoxLayout(); m_horizontalPatchesContainer->setSpacing(0); m_horizontalPatchesContainer->setMargin(0); m_sidebarLayout = new QVBoxLayout(); m_sidebarLayout->setSpacing(0); m_sidebarLayout->setMargin(0); m_verticalColorPatchesLayout = new QHBoxLayout(); m_verticalColorPatchesLayout->setSpacing(0); m_verticalColorPatchesLayout->setMargin(0); m_horizontalColorPatchesLayout = new QVBoxLayout(); m_horizontalColorPatchesLayout->setSpacing(0); m_horizontalColorPatchesLayout->setMargin(0); m_horizontalPatchesContainer->addLayout(m_horizontalColorPatchesLayout); m_mainLayout->addWidget(m_colorSelectorContainer); m_mainLayout->addLayout(m_horizontalPatchesContainer); m_sidebarLayout->addLayout(m_verticalColorPatchesLayout); m_widgetLayout->addLayout(m_mainLayout); m_widgetLayout->addLayout(m_sidebarLayout); setLayout(m_widgetLayout); updateLayout(); connect(m_colorSelectorContainer, SIGNAL(openSettings()), this, SLOT(openSettings())); //emit settingsChanged() if the settings are changed in krita preferences KisPreferenceSetRegistry *preferenceSetRegistry = KisPreferenceSetRegistry::instance(); KisColorSelectorSettingsFactory* factory = dynamic_cast(preferenceSetRegistry->get("KisColorSelectorSettingsFactory")); Q_ASSERT(factory); connect(&(factory->repeater), SIGNAL(settingsUpdated()), this, SIGNAL(settingsChanged()), Qt::UniqueConnection); connect(this, SIGNAL(settingsChanged()), this, SLOT(updateLayout()), Qt::UniqueConnection); connect(this, SIGNAL(settingsChanged()), m_commonColorsWidget, SLOT(updateSettings()), Qt::UniqueConnection); connect(this, SIGNAL(settingsChanged()), m_colorHistoryWidget, SLOT(updateSettings()), Qt::UniqueConnection); connect(this, SIGNAL(settingsChanged()), m_colorSelectorContainer, SIGNAL(settingsChanged()), Qt::UniqueConnection); connect(this, SIGNAL(settingsChanged()), this, SLOT(update()), Qt::UniqueConnection); emit settingsChanged(); m_colorHistoryAction = KisActionRegistry::instance()->makeQAction("show_color_history", this); connect(m_colorHistoryAction, SIGNAL(triggered()), m_colorHistoryWidget, SLOT(showPopup()), Qt::UniqueConnection); m_commonColorsAction = KisActionRegistry::instance()->makeQAction("show_common_colors", this); connect(m_commonColorsAction, SIGNAL(triggered()), m_commonColorsWidget, SLOT(showPopup()), Qt::UniqueConnection); connect(m_fallbackSettingsButton, SIGNAL(clicked()), this, SLOT(openSettings())); } void KisColorSelectorNgDockerWidget::unsetCanvas() { m_canvas = 0; m_commonColorsWidget->unsetCanvas(); m_colorHistoryWidget->unsetCanvas(); m_colorSelectorContainer->unsetCanvas(); } void KisColorSelectorNgDockerWidget::setCanvas(KisCanvas2 *canvas) { if (m_canvas) { m_canvas->disconnect(this); KActionCollection *ac = m_canvas->viewManager()->actionCollection(); ac->takeAction(ac->action("show_color_history")); ac->takeAction(ac->action("show_common_colors")); } m_canvas = canvas; m_commonColorsWidget->setCanvas(canvas); m_colorHistoryWidget->setCanvas(canvas); m_colorSelectorContainer->setCanvas(canvas); if (m_canvas && m_canvas->viewManager()) { - if (m_canvas->viewManager()->nodeManager()) { - connect(m_canvas->viewManager()->nodeManager(), SIGNAL(sigLayerActivated(KisLayerSP)), SLOT(reactOnLayerChange()), Qt::UniqueConnection); - } KActionCollection* actionCollection = canvas->viewManager()->actionCollection(); actionCollection->addAction("show_color_history", m_colorHistoryAction); actionCollection->addAction("show_common_colors", m_commonColorsAction); connect(m_canvas->viewManager()->mainWindow(), SIGNAL(themeChanged()), m_colorSelectorContainer, SLOT(slotUpdateIcons()), Qt::UniqueConnection); } - reactOnLayerChange(); } void KisColorSelectorNgDockerWidget::openSettings() { if (!m_canvas) return; KisColorSelectorSettingsDialog settings; if(settings.exec()==QDialog::Accepted) { emit settingsChanged(); } } void KisColorSelectorNgDockerWidget::updateLayout() { KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector"); bool showColorSelector = (bool) cfg.readEntry("showColorSelector", true); //color patches bool m_lastColorsShow = cfg.readEntry("lastUsedColorsShow", true); KisColorPatches::Direction m_lastColorsDirection; if(cfg.readEntry("lastUsedColorsAlignment", false)) m_lastColorsDirection=KisColorPatches::Vertical; else m_lastColorsDirection=KisColorPatches::Horizontal; bool m_commonColorsShow = cfg.readEntry("commonColorsShow", true); KisColorPatches::Direction m_commonColorsDirection; if(cfg.readEntry("commonColorsAlignment", false)) m_commonColorsDirection=KisColorPatches::Vertical; else m_commonColorsDirection=KisColorPatches::Horizontal; m_verticalColorPatchesLayout->removeWidget(m_colorHistoryWidget); m_verticalColorPatchesLayout->removeWidget(m_commonColorsWidget); m_horizontalColorPatchesLayout->removeWidget(m_colorHistoryWidget); m_horizontalColorPatchesLayout->removeWidget(m_commonColorsWidget); m_sidebarLayout->removeWidget(m_fallbackSettingsButton); m_mainLayout->removeWidget(m_fallbackSettingsButton); if(m_lastColorsShow==false) m_colorHistoryWidget->hide(); else m_colorHistoryWidget->show(); if(m_commonColorsShow==false) { m_commonColorsWidget->hide(); } else { m_commonColorsWidget->show(); } bool fallbackSettingsButtonVertical = true; if(m_lastColorsShow && m_lastColorsDirection==KisColorPatches::Vertical) { m_verticalColorPatchesLayout->addWidget(m_colorHistoryWidget); } if(m_commonColorsShow && m_commonColorsDirection==KisColorPatches::Vertical) { m_verticalColorPatchesLayout->addWidget(m_commonColorsWidget); } if(m_lastColorsShow && m_lastColorsDirection==KisColorPatches::Horizontal) { m_horizontalColorPatchesLayout->addWidget(m_colorHistoryWidget); fallbackSettingsButtonVertical = false; } if(m_commonColorsShow && m_commonColorsDirection==KisColorPatches::Horizontal) { m_horizontalColorPatchesLayout->addWidget(m_commonColorsWidget); fallbackSettingsButtonVertical = false; } // prefer the vertical column, if patch components have different layout if (m_commonColorsDirection != m_lastColorsDirection) { fallbackSettingsButtonVertical = true; } if (!showColorSelector) { if (fallbackSettingsButtonVertical) { m_sidebarLayout->addWidget(m_fallbackSettingsButton); } else { m_horizontalPatchesContainer->addWidget(m_fallbackSettingsButton); } m_fallbackSettingsButton->show(); } else { m_fallbackSettingsButton->hide(); } updateGeometry(); } - -void KisColorSelectorNgDockerWidget::reactOnLayerChange() -{ - /** - * Trigger the update for the case if some legacy code needs it. - * Now the node's color space is managed by the - * KisDisplayColorConverter and KisColorSelectorBase objects, so - * technically this call is not needed anymore. Please remove it - * when you are totally sure this will not break something. - */ - - emit settingsChanged(); -} diff --git a/plugins/dockers/advancedcolorselector/kis_color_selector_ng_docker_widget.h b/plugins/dockers/advancedcolorselector/kis_color_selector_ng_docker_widget.h index e9d51d3c06..38255bfebf 100644 --- a/plugins/dockers/advancedcolorselector/kis_color_selector_ng_docker_widget.h +++ b/plugins/dockers/advancedcolorselector/kis_color_selector_ng_docker_widget.h @@ -1,76 +1,75 @@ /* * Copyright (c) 2010 Adam Celarek * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_COLOR_SELECTOR_NG_DOCKER_WIDGET_H #define KIS_COLOR_SELECTOR_NG_DOCKER_WIDGET_H #include #include #include #include class QAction; class KisCommonColors; class KisColorHistory; class KisColorSelectorContainer; class QVBoxLayout; class QHBoxLayout; class KisColorSelectorNgDockerWidget : public QWidget { Q_OBJECT public: explicit KisColorSelectorNgDockerWidget(QWidget *parent = 0); void setCanvas(KisCanvas2* canvas); void unsetCanvas(); public Q_SLOTS: void openSettings(); Q_SIGNALS: void settingsChanged(); protected Q_SLOTS: void updateLayout(); - void reactOnLayerChange(); private: KisColorSelectorContainer* m_colorSelectorContainer; KisColorHistory* m_colorHistoryWidget; KisCommonColors* m_commonColorsWidget; QAction * m_colorHistoryAction; QAction * m_commonColorsAction; QHBoxLayout* m_widgetLayout; QVBoxLayout* m_mainLayout; QHBoxLayout* m_horizontalPatchesContainer; QVBoxLayout* m_sidebarLayout; QHBoxLayout* m_verticalColorPatchesLayout; // vertical color patches should be added here QVBoxLayout* m_horizontalColorPatchesLayout;//horizontal ----------"---------------------- QToolButton* m_fallbackSettingsButton; QPointer m_canvas; }; #endif diff --git a/plugins/dockers/compositiondocker/compositiondocker_dock.cpp b/plugins/dockers/compositiondocker/compositiondocker_dock.cpp index 9c8ad3594b..fdfaebcc94 100644 --- a/plugins/dockers/compositiondocker/compositiondocker_dock.cpp +++ b/plugins/dockers/compositiondocker/compositiondocker_dock.cpp @@ -1,305 +1,315 @@ /* * Copyright (c) 2012 Sven Langkamp * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "compositiondocker_dock.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "compositionmodel.h" -CompositionDockerDock::CompositionDockerDock( ) : QDockWidget(i18n("Compositions")), m_canvas(0) +CompositionDockerDock::CompositionDockerDock( ) + : QDockWidget(i18n("Compositions")) + , m_canvas(0) { QWidget* widget = new QWidget(this); setupUi(widget); m_model = new CompositionModel(this); compositionView->setModel(m_model); compositionView->installEventFilter(this); deleteButton->setIcon(KisIconUtils::loadIcon("edit-delete")); saveButton->setIcon(KisIconUtils::loadIcon("list-add")); exportButton->setIcon(KisIconUtils::loadIcon("document-export")); deleteButton->setToolTip(i18n("Delete Composition")); saveButton->setToolTip(i18n("New Composition")); exportButton->setToolTip(i18n("Export Composition")); setWidget(widget); connect( compositionView, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(activated(QModelIndex)) ); compositionView->setContextMenuPolicy(Qt::CustomContextMenu); connect( compositionView, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(customContextMenuRequested(QPoint))); connect( deleteButton, SIGNAL(clicked(bool)), this, SLOT(deleteClicked())); connect( saveButton, SIGNAL(clicked(bool)), this, SLOT(saveClicked())); connect( exportButton, SIGNAL(clicked(bool)), this, SLOT(exportClicked())); saveNameEdit->setPlaceholderText(i18n("Insert Name")); QScroller *scroller = KisKineticScroller::createPreconfiguredScroller(compositionView); if (scroller) { connect(scroller, SIGNAL(stateChanged(QScroller::State)), this, SLOT(slotScrollerStateChanged(QScroller::State))); } } CompositionDockerDock::~CompositionDockerDock() { } void CompositionDockerDock::setCanvas(KoCanvasBase * canvas) { if (m_canvas && m_canvas->viewManager()) { Q_FOREACH (KisAction *action, m_actions) { m_canvas->viewManager()->actionManager()->takeAction(action); } } unsetCanvas(); setEnabled(canvas != 0); m_canvas = dynamic_cast(canvas); if (m_canvas && m_canvas->viewManager()) { if (m_actions.isEmpty()) { KisAction *updateAction = m_canvas->viewManager()->actionManager()->createAction("update_composition"); connect(updateAction, SIGNAL(triggered()), this, SLOT(updateComposition())); m_actions.append(updateAction); KisAction *renameAction = m_canvas->viewManager()->actionManager()->createAction("rename_composition"); connect(renameAction, SIGNAL(triggered()), this, SLOT(renameComposition())); m_actions.append(renameAction); } else { Q_FOREACH (KisAction *action, m_actions) { m_canvas->viewManager()->actionManager()->addAction(action->objectName(), action); } } updateModel(); } } void CompositionDockerDock::unsetCanvas() { setEnabled(false); m_canvas = 0; m_model->setCompositions(QList()); } void CompositionDockerDock::activated(const QModelIndex& index) { KisLayerCompositionSP composition = m_model->compositionFromIndex(index); composition->apply(); } void CompositionDockerDock::deleteClicked() { QModelIndex index = compositionView->currentIndex(); if (m_canvas && m_canvas->viewManager() && m_canvas->viewManager()->image() && index.isValid()) { KisLayerCompositionSP composition = m_model->compositionFromIndex(index); m_canvas->viewManager()->image()->removeComposition(composition); updateModel(); } } void CompositionDockerDock::saveClicked() { KisImageWSP image = m_canvas->viewManager()->image(); if (!image) return; // format as 001, 002 ... QString name = saveNameEdit->text(); if (name.isEmpty()) { bool found = false; int i = 1; do { name = QString("%1").arg(i, 3, 10, QChar('0')); found = false; Q_FOREACH (KisLayerCompositionSP composition, m_canvas->viewManager()->image()->compositions()) { if (composition->name() == name) { found = true; break; } } i++; } while(found && i < 1000); } KisLayerCompositionSP composition(new KisLayerComposition(image, name)); composition->store(); image->addComposition(composition); saveNameEdit->clear(); updateModel(); compositionView->setCurrentIndex(m_model->index(image->compositions().count()-1, 0)); image->setModified(); } void CompositionDockerDock::updateModel() { if (m_model && m_canvas && m_canvas->viewManager() && m_canvas->viewManager()->image()) { m_model->setCompositions(m_canvas->viewManager()->image()->compositions()); } } void CompositionDockerDock::exportClicked() { if (m_canvas && m_canvas->viewManager() && m_canvas->viewManager()->image()) { QString path; KoFileDialog dialog(0, KoFileDialog::OpenDirectory, "compositiondockerdock"); dialog.setCaption(i18n("Select a Directory")); dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::HomeLocation)); path = dialog.filename(); if (path.isNull()) return; if (!path.endsWith('/')) { path.append('/'); } - KisImageWSP image = m_canvas->viewManager()->image(); + KisImageSP image = m_canvas->viewManager()->image(); QString filename = m_canvas->viewManager()->document()->localFilePath(); if (!filename.isEmpty()) { QFileInfo info(filename); path += info.completeBaseName() + '_'; } - Q_FOREACH (KisLayerCompositionSP composition, m_canvas->viewManager()->image()->compositions()) { + KisLayerCompositionSP currentComposition = toQShared(new KisLayerComposition(image, "temp")); + currentComposition->store(); + + Q_FOREACH (KisLayerCompositionSP composition, image->compositions()) { if (!composition->isExportEnabled()) { continue; } composition->apply(); - image->lock(); + image->waitForDone(); + image->refreshGraph(); QRect r = image->bounds(); KisDocument *d = KisPart::instance()->createDocument(); KisImageSP dst = new KisImage(d->createUndoStore(), r.width(), r.height(), image->colorSpace(), composition->name()); dst->setResolution(image->xRes(), image->yRes()); d->setCurrentImage(dst); KisPaintLayer* paintLayer = new KisPaintLayer(dst, "projection", OPACITY_OPAQUE_U8); KisPainter gc(paintLayer->paintDevice()); gc.bitBlt(QPoint(0, 0), image->rootLayer()->projection(), r); dst->addNode(paintLayer, dst->rootLayer(), KisLayerSP(0)); - dst->initialRefreshGraph(); + dst->refreshGraph(); + dst->waitForDone(); d->setFileBatchMode(true); d->exportDocumentSync(QUrl::fromLocalFile(path + composition->name() + ".png"), "image/png"); d->deleteLater(); - image->unlock(); } + currentComposition->apply(); + image->waitForDone(); + image->refreshGraph(); } + } bool CompositionDockerDock::eventFilter(QObject* obj, QEvent* event) { if (event->type() == QEvent::KeyPress ) { QKeyEvent *keyEvent = static_cast(event); if (keyEvent->key() == Qt::Key_Up || keyEvent->key() == Qt::Key_Down) { // new index will be set after the method is called QTimer::singleShot(0, this, SLOT(activateCurrentIndex())); } return false; } else { return QObject::eventFilter(obj, event); } } void CompositionDockerDock::activateCurrentIndex() { QModelIndex index = compositionView->currentIndex(); if (index.isValid()) { activated(index); } } void CompositionDockerDock::customContextMenuRequested(QPoint pos) { if (m_actions.isEmpty()) return; QMenu menu; Q_FOREACH (KisAction *action, m_actions) { menu.addAction(action); } menu.exec(compositionView->mapToGlobal(pos)); } void CompositionDockerDock::updateComposition() { QModelIndex index = compositionView->currentIndex(); if (m_canvas && m_canvas->viewManager() && m_canvas->viewManager()->image() && index.isValid()) { KisLayerCompositionSP composition = m_model->compositionFromIndex(index); composition->store(); m_canvas->image()->setModified(); } } void CompositionDockerDock::renameComposition() { dbgKrita << "rename"; QModelIndex index = compositionView->currentIndex(); if (m_canvas && m_canvas->viewManager() && m_canvas->viewManager()->image() && index.isValid()) { KisLayerCompositionSP composition = m_model->compositionFromIndex(index); bool ok; QString name = QInputDialog::getText(this, i18n("Rename Composition"), i18n("New Name:"), QLineEdit::Normal, composition->name(), &ok); if (ok && !name.isEmpty()) { composition->setName(name); m_canvas->image()->setModified(); } } } diff --git a/plugins/dockers/smallcolorselector/KisClickableGLImageWidget.cpp b/plugins/dockers/smallcolorselector/KisClickableGLImageWidget.cpp index ad19466b6e..90d3fd39f3 100644 --- a/plugins/dockers/smallcolorselector/KisClickableGLImageWidget.cpp +++ b/plugins/dockers/smallcolorselector/KisClickableGLImageWidget.cpp @@ -1,158 +1,158 @@ /* * Copyright (c) 2019 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KisClickableGLImageWidget.h" #include #include #include "kis_algebra_2d.h" #include KisClickableGLImageWidget::KisClickableGLImageWidget(QWidget *parent) : KisGLImageWidget(parent) { } KisClickableGLImageWidget::KisClickableGLImageWidget(KisSurfaceColorSpace colorSpace, QWidget *parent) : KisGLImageWidget(colorSpace, parent) { } KisClickableGLImageWidget::~KisClickableGLImageWidget() { } void KisClickableGLImageWidget::setHandlePaintingStrategy(HandlePaintingStrategy *strategy) { m_handleStrategy.reset(strategy); } void KisClickableGLImageWidget::setUseHandleOpacity(bool value) { m_useHandleOpacity = value; update(); } QPointF KisClickableGLImageWidget::normalizedPos() const { return m_normalizedClickPoint; } void KisClickableGLImageWidget::setNormalizedPos(const QPointF &pos, bool update) { m_normalizedClickPoint = KisAlgebra2D::clampPoint(pos, QRectF(0,0,1.0,1.0)); if (update) { this->update(); } } void KisClickableGLImageWidget::paintEvent(QPaintEvent *event) { KisGLImageWidget::paintEvent(event); if (m_handleStrategy) { QPainter p(this); m_handleStrategy->drawHandle(&p, m_normalizedClickPoint, rect(), m_useHandleOpacity); } } void KisClickableGLImageWidget::mousePressEvent(QMouseEvent *event) { KisGLImageWidget::mousePressEvent(event); if (!event->isAccepted()) { event->accept(); m_normalizedClickPoint = normalizePoint(event->localPos()); emit selected(m_normalizedClickPoint); if (m_handleStrategy) { update(); } } } void KisClickableGLImageWidget::mouseReleaseEvent(QMouseEvent *event) { KisGLImageWidget::mouseReleaseEvent(event); if (!event->isAccepted()) { event->accept(); m_normalizedClickPoint = normalizePoint(event->localPos()); emit selected(m_normalizedClickPoint); if (m_handleStrategy) { update(); } } } void KisClickableGLImageWidget::mouseMoveEvent(QMouseEvent *event) { KisGLImageWidget::mouseMoveEvent(event); if (!event->isAccepted()) { event->accept(); m_normalizedClickPoint = normalizePoint(event->localPos()); emit selected(m_normalizedClickPoint); if (m_handleStrategy) { update(); } } } QPointF KisClickableGLImageWidget::normalizePoint(const QPointF &pos) const { const QPointF croppedPoint = KisAlgebra2D::clampPoint(pos, rect()); return QPointF(croppedPoint.x() / width(), croppedPoint.y() / height()); } namespace { QPen outerHandlePen(bool useOpacity) { - // opacity works inexpectedly in HDR mode, so let the user switch it off + // opacity works unexpectedly in HDR mode, so let the user switch it off return QPen(QColor(0, 0, 0, useOpacity ? 180 : 255), 0); } QPen innerHandlePen(bool useOpacity) { - // opacity works inexpectedly in HDR mode, so let the user switch it off + // opacity works unexpectedly in HDR mode, so let the user switch it off return QPen(QColor(255, 255, 255, useOpacity ? 180 : 255), 0); } } void KisClickableGLImageWidget::VerticalLineHandleStrategy::drawHandle(QPainter *p, const QPointF &normalizedPoint, const QRect &rect, bool useOpacity) { const QPointF pos = KisAlgebra2D::relativeToAbsolute(normalizedPoint, rect); const int x = std::floor(pos.x()); p->setPen(outerHandlePen(useOpacity)); p->drawLine(x, rect.top(), x, rect.bottom()); p->setPen(innerHandlePen(useOpacity)); p->drawLine(x + 1, rect.top(), x + 1, rect.bottom()); } void KisClickableGLImageWidget::CircularHandleStrategy::drawHandle(QPainter *p, const QPointF &normalizedPoint, const QRect &rect, bool useOpacity) { const QPointF pos = KisAlgebra2D::relativeToAbsolute(normalizedPoint, rect); p->setRenderHint(QPainter::Antialiasing); p->setPen(outerHandlePen(useOpacity)); p->drawEllipse(pos, 5, 5); p->setPen(innerHandlePen(useOpacity)); p->drawEllipse(pos, 4, 4); } diff --git a/plugins/dockers/snapshotdocker/CMakeLists.txt b/plugins/dockers/snapshotdocker/CMakeLists.txt index 0d3c5ba7fa..1e2de57f5b 100644 --- a/plugins/dockers/snapshotdocker/CMakeLists.txt +++ b/plugins/dockers/snapshotdocker/CMakeLists.txt @@ -1,10 +1,10 @@ set(kritasnapshotdocker_SOURCES KisSnapshotModel.cpp SnapshotDocker.cpp SnapshotPlugin.cpp KisSnapshotView.cpp ) add_library(kritasnapshotdocker MODULE ${kritasnapshotdocker_SOURCES}) target_link_libraries(kritasnapshotdocker kritaimage kritaui) -install(TARGETS kritasnapshotdocker DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) \ No newline at end of file +install(TARGETS kritasnapshotdocker DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) diff --git a/plugins/extensions/animationrenderer/video_saver.h b/plugins/extensions/animationrenderer/video_saver.h index e9cb05e217..adcb5a7293 100644 --- a/plugins/extensions/animationrenderer/video_saver.h +++ b/plugins/extensions/animationrenderer/video_saver.h @@ -1,73 +1,70 @@ /* * 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. */ #ifndef VIDEO_SAVER_H_ #define VIDEO_SAVER_H_ #include #include "kis_types.h" -#include "KisImageBuilderResult.h" #include class KisFFMpegRunner; -/* The KisImageBuilder_Result definitions come from kis_png_converter.h here */ - class KisDocument; class KisAnimationRenderingOptions; class VideoSaver : public QObject { Q_OBJECT public: /** * @brief VideoSaver * This is the object that takes an animation document and config and tells ffmpeg * to render it. Log files are generated here too. * @param doc the document to use for rendering. * @param ffmpegPath the path to the ffmpeg executable. * @param batchMode whether Krita is in batchmde and we can thus not show gui widgets. */ VideoSaver(KisDocument* doc, bool batchMode); ~VideoSaver() override; /** * @brief image * @return get the image used by this exporter. */ KisImageSP image(); /** * @brief encode the main encoding function. * This in turn calls runFFMpeg, which is a private function inside this class. * @param filename the filename to which to render the animation. * @param configuration the configuration * @return whether it is successful or had another failure. */ KisImportExportErrorCode encode(const QString &savedFilesMask, const KisAnimationRenderingOptions &options); static KisImportExportErrorCode convert(KisDocument *document, const QString &savedFilesMask, const KisAnimationRenderingOptions &options, bool batchMode); private: KisImageSP m_image; KisDocument* m_doc; bool m_batchMode; }; #endif diff --git a/plugins/extensions/colorrange/dlg_colorrange.cc b/plugins/extensions/colorrange/dlg_colorrange.cc index 2ab9836660..6a4548fbe9 100644 --- a/plugins/extensions/colorrange/dlg_colorrange.cc +++ b/plugins/extensions/colorrange/dlg_colorrange.cc @@ -1,275 +1,275 @@ /* * dlg_colorrange.cc - part of KimageShop^WKrayon^WKrita * * 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 "dlg_colorrange.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_iterator_ng.h" #include "kis_selection_tool_helper.h" #include DlgColorRange::DlgColorRange(KisViewManager *viewManager, QWidget *parent) : KoDialog(parent) , m_selectionCommandsAdded(0) , m_viewManager(viewManager) { setCaption(i18n("Color Range")); setButtons(Ok | Cancel); setDefaultButton(Ok); m_page = new WdgColorRange(this); Q_CHECK_PTR(m_page); m_page->setObjectName("color_range"); setCaption(i18n("Color Range")); setMainWidget(m_page); resize(m_page->sizeHint()); m_page->intFuzziness->setObjectName("fuzziness"); m_page->intFuzziness->setRange(0, 200); m_page->intFuzziness->setSingleStep(10); m_page->intFuzziness->setValue(100); m_invert = false; m_mode = SELECTION_ADD; m_currentAction = REDS; connect(this, SIGNAL(okClicked()), this, SLOT(okClicked())); connect(this, SIGNAL(cancelClicked()), this, SLOT(cancelClicked())); connect(m_page->chkInvert, SIGNAL(clicked()), this, SLOT(slotInvertClicked())); connect(m_page->cmbSelect, SIGNAL(activated(int)), this, SLOT(slotSelectionTypeChanged(int))); connect(m_page->radioAdd, SIGNAL(toggled(bool)), this, SLOT(slotAdd(bool))); connect(m_page->radioSubtract, SIGNAL(toggled(bool)), this, SLOT(slotSubtract(bool))); connect(m_page->bnSelect, SIGNAL(clicked()), this, SLOT(slotSelectClicked())); connect(m_page->bnDeselect, SIGNAL(clicked()), this, SLOT(slotDeselectClicked())); m_page->bnDeselect->setEnabled(false); } DlgColorRange::~DlgColorRange() { delete m_page; } void DlgColorRange::okClicked() { accept(); } void DlgColorRange::cancelClicked() { if (!m_viewManager) return; if (!m_viewManager->image()) return; for (int i = 0; i < m_selectionCommandsAdded; i++) { m_viewManager->undoAdapter()->undoLastCommand(); } m_viewManager->canvas()->update(); reject(); } void DlgColorRange::slotInvertClicked() { m_invert = m_page->chkInvert->isChecked(); } void DlgColorRange::slotSelectionTypeChanged(int index) { m_currentAction = (enumAction)index; } void DlgColorRange::slotSubtract(bool on) { if (on) m_mode = SELECTION_SUBTRACT; } void DlgColorRange::slotAdd(bool on) { if (on) m_mode = SELECTION_ADD; } void DlgColorRange::slotSelectClicked() { KisPaintDeviceSP device = m_viewManager->activeDevice(); KIS_ASSERT_RECOVER_RETURN(device); QRect rc = m_viewManager->image()->bounds(); if (rc.isEmpty()) return; QApplication::setOverrideCursor(KisCursor::waitCursor()); qint32 x, y, w, h; rc.getRect(&x, &y, &w, &h); const KoColorSpace *cs = m_viewManager->activeDevice()->colorSpace(); const KoColorSpace *lab = KoColorSpaceRegistry::instance()->lab16(); KoColor match; switch (m_currentAction) { case REDS: match = KoColor(QColor(Qt::red), cs); break; case YELLOWS: match = KoColor(QColor(Qt::yellow), cs); break; case GREENS: match = KoColor(QColor(Qt::green), cs); break; case CYANS: match = KoColor(QColor(Qt::cyan), cs); break; case BLUES: match = KoColor(QColor(Qt::blue), cs); break; case MAGENTAS: match = KoColor(QColor(Qt::magenta), cs); break; default: ; }; int fuzziness = m_page->intFuzziness->value(); - KisSelectionSP selection = new KisSelection(new KisSelectionDefaultBounds(m_viewManager->activeDevice(), m_viewManager->image())); + KisSelectionSP selection = new KisSelection(new KisSelectionDefaultBounds(m_viewManager->activeDevice())); KisHLineConstIteratorSP hiter = m_viewManager->activeDevice()->createHLineConstIteratorNG(x, y, w); KisHLineIteratorSP selIter = selection->pixelSelection()->createHLineIteratorNG(x, y, w); for (int row = y; row < h - y; ++row) { do { // Don't try to select transparent pixels. if (cs->opacityU8(hiter->oldRawData()) > OPACITY_TRANSPARENT_U8) { bool selected = false; KoColor c(hiter->oldRawData(), cs); if (m_currentAction > MAGENTAS) { c.convertTo(lab); quint8 L = lab->scaleToU8(c.data(), 0); switch (m_currentAction) { case HIGHLIGHTS: selected = (L > MAX_SELECTED - fuzziness); break; case MIDTONES: selected = (L > MAX_SELECTED / 2 - fuzziness && L < MAX_SELECTED / 2 + fuzziness); break; case SHADOWS: selected = (L < MIN_SELECTED + fuzziness); break; default: ; } } else { quint8 difference = cs->difference(match.data(), c.data()); selected = (difference <= fuzziness); } if (selected) { if (!m_invert) { if (m_mode == SELECTION_ADD) { *(selIter->rawData()) = MAX_SELECTED; } else if (m_mode == SELECTION_SUBTRACT) { *(selIter->rawData()) = MIN_SELECTED; } } else { if (m_mode == SELECTION_ADD) { *(selIter->rawData()) = MIN_SELECTED; } else if (m_mode == SELECTION_SUBTRACT) { *(selIter->rawData()) = MAX_SELECTED; } } } } } while (hiter->nextPixel() && selIter->nextPixel()); hiter->nextRow(); selIter->nextRow(); } selection->pixelSelection()->invalidateOutlineCache(); KisSelectionToolHelper helper(m_viewManager->canvasBase(), kundo2_i18n("Color Range Selection")); helper.selectPixelSelection(selection->pixelSelection(), m_mode); m_page->bnDeselect->setEnabled(true); m_selectionCommandsAdded++; QApplication::restoreOverrideCursor(); } void DlgColorRange::slotDeselectClicked() { if (!m_viewManager) return; m_viewManager->undoAdapter()->undoLastCommand(); m_selectionCommandsAdded--; if (!m_selectionCommandsAdded) { m_page->bnDeselect->setEnabled(false); } } diff --git a/plugins/extensions/separate_channels/dlg_separate.cc b/plugins/extensions/separate_channels/dlg_separate.cc index 582e0c28ac..04fc2a374d 100644 --- a/plugins/extensions/separate_channels/dlg_separate.cc +++ b/plugins/extensions/separate_channels/dlg_separate.cc @@ -1,124 +1,123 @@ /* * dlg_separate.cc - part of KimageShop^WKrayon^WKrita * * 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 "dlg_separate.h" #include #include #include #include +#include + #include #include DlgSeparate::DlgSeparate(const QString & imageCS, const QString & layerCS, QWidget * parent, const char * name) : KoDialog(parent) , m_imageCS(imageCS) , m_layerCS(layerCS) { setObjectName(name); setCaption(i18n("Separate Image")); setButtons(Ok | Cancel); setDefaultButton(Ok); m_page = new WdgSeparations(this); Q_CHECK_PTR(m_page); setMainWidget(m_page); resize(m_page->sizeHint()); m_page->lblColormodel->setText(layerCS); - m_page->grpOutput->hide(); connect(m_page->radioCurrentLayer, SIGNAL(toggled(bool)), this, SLOT(slotSetColorSpaceLabel())); connect(m_page->radioAllLayers, SIGNAL(toggled(bool)), this, SLOT(slotSetColorSpaceLabel())); - connect(m_page->chkColors, SIGNAL(toggled(bool)), this, SLOT(disableDownScaleIfPossible(bool))); + connect(m_page->chkColors, SIGNAL(toggled(bool)), this, SLOT(separateToColorActivated(bool))); connect(this, SIGNAL(okClicked()), this, SLOT(okClicked())); + + KisDialogStateSaver::restoreState(m_page, "krita/separate channels"); } DlgSeparate::~DlgSeparate() { + KisDialogStateSaver::saveState(m_page, "krita/separate channels"); delete m_page; } enumSepAlphaOptions DlgSeparate::getAlphaOptions() { if (m_page->radioCopyAlpha->isChecked()) return COPY_ALPHA_TO_SEPARATIONS; if (m_page->radioDiscardAlpha->isChecked()) return DISCARD_ALPHA; if (m_page->radioSeparateAlpha->isChecked()) return CREATE_ALPHA_SEPARATION; return COPY_ALPHA_TO_SEPARATIONS; } enumSepSource DlgSeparate::getSource() { if (m_page->radioCurrentLayer->isChecked()) return CURRENT_LAYER; if (m_page->radioAllLayers->isChecked()) return ALL_LAYERS; - //if (XXX) return VISIBLE_LAYERS; - return CURRENT_LAYER; } -enumSepOutput DlgSeparate::getOutput() -{ - if (m_page->radioLayers->isChecked()) return TO_LAYERS; - if (m_page->radioImages->isChecked()) return TO_IMAGES; - - return TO_LAYERS; -} - - bool DlgSeparate::getDownscale() { return m_page->chkDownscale->isChecked(); } bool DlgSeparate::getToColor() { return m_page->chkColors->isChecked(); } +bool DlgSeparate::getActivateCurrentChannel() +{ + return m_page->chkActivateCurrentChannel->isChecked(); +} + void DlgSeparate::okClicked() { accept(); } -void DlgSeparate::disableDownScaleIfPossible(bool disable) +void DlgSeparate::separateToColorActivated(bool disable) { if (m_canDownScale) { m_page->chkDownscale->setDisabled(disable); } + m_page->chkActivateCurrentChannel->setDisabled(!disable); } void DlgSeparate::slotSetColorSpaceLabel() { if (m_page->radioCopyAlpha->isChecked()) { m_page->lblColormodel->setText(m_layerCS); } else if (m_page->radioAllLayers->isChecked()) { m_page->lblColormodel->setText(m_imageCS); } } void DlgSeparate::enableDownscale(bool enable) { m_canDownScale = enable; m_page->chkDownscale->setEnabled(enable); } diff --git a/plugins/extensions/separate_channels/dlg_separate.h b/plugins/extensions/separate_channels/dlg_separate.h index 29acc922ea..c86d00e667 100644 --- a/plugins/extensions/separate_channels/dlg_separate.h +++ b/plugins/extensions/separate_channels/dlg_separate.h @@ -1,77 +1,77 @@ /* * dlg_imagesize.h -- part of KimageShop^WKrayon^WKrita * * Copyright (c) 2005 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 DLG_SEPARATE #define DLG_SEPARATE #include #include #include "ui_wdg_separations.h" class WdgSeparations : public QWidget, public Ui::WdgSeparations { public: WdgSeparations(QWidget *parent) : QWidget(parent) { setupUi(this); } }; /** * This dialog allows the user to configure the decomposition of an image * into layers: one layer for each color channel. */ class DlgSeparate: public KoDialog { Q_OBJECT public: DlgSeparate(const QString & imageCS, const QString & layerCS, QWidget * parent = 0, const char* name = 0); ~DlgSeparate() override; public: enumSepAlphaOptions getAlphaOptions(); enumSepSource getSource(); - enumSepOutput getOutput(); bool getDownscale(); void enableDownscale(bool enable); bool getToColor(); + bool getActivateCurrentChannel(); private Q_SLOTS: void slotSetColorSpaceLabel(); void okClicked(); - void disableDownScaleIfPossible(bool disable); + void separateToColorActivated(bool disable); private: WdgSeparations * m_page; QString m_imageCS; QString m_layerCS; bool m_canDownScale {true}; }; #endif // DLG_SEPARATE diff --git a/plugins/extensions/separate_channels/kis_channel_separator.cc b/plugins/extensions/separate_channels/kis_channel_separator.cc index ffbc1fb45e..cd1a5677a6 100644 --- a/plugins/extensions/separate_channels/kis_channel_separator.cc +++ b/plugins/extensions/separate_channels/kis_channel_separator.cc @@ -1,272 +1,237 @@ /* * This file is part of Krita * * Copyright (c) 2005 Michael Thaler * * ported from Gimp, Copyright (C) 1997 Eiichi Takamori * original pixelize.c for GIMP 0.54 by Tracy Scott * * 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_channel_separator.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_iterator_ng.h" #include #include #include #include #include #include KisChannelSeparator::KisChannelSeparator(KisViewManager * view) - : m_viewManager(view) + : m_viewManager(view) { } -void KisChannelSeparator::separate(KoUpdater * progressUpdater, enumSepAlphaOptions alphaOps, enumSepSource sourceOps, enumSepOutput outputOps, bool downscale, bool toColor) +void KisChannelSeparator::separate(KoUpdater * progressUpdater, enumSepAlphaOptions alphaOps, enumSepSource sourceOps, bool downscale, bool toColor, bool activateCurrentChannel) { KisImageSP image = m_viewManager->image(); if (!image) return; KisPaintDeviceSP src; // Use the flattened image, if required switch (sourceOps) { case ALL_LAYERS: // the content will be locked later src = image->projection(); break; case CURRENT_LAYER: src = m_viewManager->activeDevice(); break; default: break; } if (!src) return; progressUpdater->setProgress(1); const KoColorSpace * dstCs = 0; quint32 numberOfChannels = src->channelCount(); const KoColorSpace * srcCs = src->colorSpace(); QList channels = srcCs->channels(); - vKisPaintDeviceSP layers; - - QList::const_iterator begin = channels.constBegin(); - QList::const_iterator end = channels.constEnd(); + vKisPaintDeviceSP paintDevices; QRect rect = src->exactBounds(); image->lock(); int i = 0; - for (QList::const_iterator it = begin; it != end; ++it) { - - KoChannelInfo * ch = (*it); + for (QList::const_iterator it = channels.constBegin(); it != channels.constEnd(); ++it) { + KoChannelInfo *ch = (*it); if (ch->channelType() == KoChannelInfo::ALPHA && alphaOps != CREATE_ALPHA_SEPARATION) { continue; } qint32 channelSize = ch->size(); qint32 channelPos = ch->pos(); qint32 destSize = 1; KisPaintDeviceSP dev; if (toColor) { // We don't downscale if we separate to color channels dev = new KisPaintDevice(srcCs); - } else { + } + else { if (channelSize == 1 || downscale) { dev = new KisPaintDevice(KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), Integer8BitsColorDepthID.id(), 0)); } else { dev = new KisPaintDevice(KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), Integer16BitsColorDepthID.id(), 0)); destSize = 2; } } dstCs = dev->colorSpace(); - layers.push_back(dev); + paintDevices.push_back(dev); KisHLineConstIteratorSP srcIt = src->createHLineConstIteratorNG(rect.x(), rect.y(), rect.width()); KisHLineIteratorSP dstIt = dev->createHLineIteratorNG(rect.x(), rect.y(), rect.width()); for (qint32 row = 0; row < rect.height(); ++row) { do { if (toColor) { dstCs->singleChannelPixel(dstIt->rawData(), srcIt->oldRawData(), channelPos); if (alphaOps == COPY_ALPHA_TO_SEPARATIONS) { //dstCs->setAlpha(dstIt->rawData(), srcIt->oldRawData()[srcAlphaPos], 1); dstCs->setOpacity(dstIt->rawData(), srcCs->opacityU8(srcIt->oldRawData()), 1); } else { dstCs->setOpacity(dstIt->rawData(), OPACITY_OPAQUE_U8, 1); } } else { // To grayscale // Decide whether we need downscaling if (channelSize == 1 && destSize == 1) { // Both 8-bit channels dstIt->rawData()[0] = srcIt->oldRawData()[channelPos]; if (alphaOps == COPY_ALPHA_TO_SEPARATIONS) { dstCs->setOpacity(dstIt->rawData(), srcCs->opacityU8(srcIt->oldRawData()), 1); } else { dstCs->setOpacity(dstIt->rawData(), OPACITY_OPAQUE_U8, 1); } } else if (channelSize == 2 && destSize == 2) { // Both 16-bit dstIt->rawData()[0] = srcIt->oldRawData()[channelPos]; dstIt->rawData()[1] = srcIt->oldRawData()[channelPos + 1]; if (alphaOps == COPY_ALPHA_TO_SEPARATIONS) { dstCs->setOpacity(dstIt->rawData(), srcCs->opacityU8(srcIt->oldRawData()), 1); } else { dstCs->setOpacity(dstIt->rawData(), OPACITY_OPAQUE_U8, 1); } } else if (channelSize != 1 && destSize == 1) { // Downscale memset(dstIt->rawData(), srcCs->scaleToU8(srcIt->oldRawData(), channelPos), 1); // XXX: Do alpha dstCs->setOpacity(dstIt->rawData(), OPACITY_OPAQUE_U8, 1); } else if (channelSize != 2 && destSize == 2) { // Upscale dstIt->rawData()[0] = srcCs->scaleToU8(srcIt->oldRawData(), channelPos); // XXX: Do alpha dstCs->setOpacity(dstIt->rawData(), OPACITY_OPAQUE_U8, 1); } } } while (dstIt->nextPixel() && srcIt->nextPixel()); dstIt->nextRow(); srcIt->nextRow(); } ++i; progressUpdater->setProgress((i * 100) / numberOfChannels); if (progressUpdater->interrupted()) { break; } } - vKisPaintDeviceSP_it deviceIt = layers.begin(); - - progressUpdater->setProgress(100); + vKisPaintDeviceSP::const_iterator paintDeviceIterator = paintDevices.cbegin(); if (!progressUpdater->interrupted()) { - - KisUndoAdapter * undo = image->undoAdapter(); - if (outputOps == TO_LAYERS) { - undo->beginMacro(kundo2_i18n("Separate Image")); - } - - // Flatten the image if required - switch (sourceOps) { - case(ALL_LAYERS): - image->flatten(0); - break; - default: - break; - } KisNodeCommandsAdapter adapter(m_viewManager); + adapter.beginMacro(kundo2_i18n("Separate Image")); - for (QList::const_iterator it = begin; it != end; ++it) { - - KoChannelInfo * ch = (*it); + for (QList::const_iterator it = channels.constBegin(); it != channels.constEnd(); ++it) { + KoChannelInfo *ch = (*it); if (ch->channelType() == KoChannelInfo::ALPHA && alphaOps != CREATE_ALPHA_SEPARATION) { // Don't make an separate separation of the alpha channel if the user didn't ask for it. continue; } - if (outputOps == TO_LAYERS) { - KisPaintLayerSP l = KisPaintLayerSP(new KisPaintLayer(image.data(), ch->name(), OPACITY_OPAQUE_U8, *deviceIt)); - adapter.addNode(l.data(), image->rootLayer(), 0); - } - else { - KoFileDialog dialog(m_viewManager->mainWindow(), KoFileDialog::SaveFile, "OpenDocument"); - dialog.setCaption(i18n("Export Layer") + '(' + ch->name() + ')'); - dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)); - dialog.setMimeTypeFilters(KisImportExportManager::supportedMimeTypes(KisImportExportManager::Export)); - QUrl url = QUrl::fromUserInput(dialog.filename()); - - if (url.isEmpty()) - return; - - const QString mimeType = KisMimeDatabase::mimeTypeForFile(url.toLocalFile(), false); - - KisPaintLayerSP l = KisPaintLayerSP(new KisPaintLayer(image.data(), ch->name(), OPACITY_OPAQUE_U8, *deviceIt)); - QRect r = l->exactBounds(); - - KisDocument *d = KisPart::instance()->createDocument(); - - KisImageWSP dst = KisImageWSP(new KisImage(d->createUndoStore(), r.width(), r.height(), (*deviceIt)->colorSpace(), l->name())); - d->setCurrentImage(dst); - dst->addNode(l->clone().data(), dst->rootLayer()); - - d->exportDocumentSync(url, mimeType.toLatin1()); - - delete d; + KisPaintLayerSP l = new KisPaintLayer(image, ch->name(), OPACITY_OPAQUE_U8, *paintDeviceIterator); + if (toColor && activateCurrentChannel) { + QBitArray channelFlags(channels.count()); + int i = 0; + for (QList::const_iterator it2 = channels.constBegin(); it2 != channels.constEnd(); ++it2) { + channelFlags.setBit(i, (it == it2)); + i++; + } + l->setChannelFlags(channelFlags); } - ++deviceIt; + adapter.addNode(l.data(), image->rootLayer(), 0); + ++paintDeviceIterator; } - if (outputOps == TO_LAYERS) { - undo->endMacro(); - } - image->unlock(); + adapter.endMacro(); image->setModified(); } + image->unlock(); + + progressUpdater->setProgress(100); + + } diff --git a/plugins/extensions/separate_channels/kis_channel_separator.h b/plugins/extensions/separate_channels/kis_channel_separator.h index 12ff238835..7c5305e266 100644 --- a/plugins/extensions/separate_channels/kis_channel_separator.h +++ b/plugins/extensions/separate_channels/kis_channel_separator.h @@ -1,61 +1,56 @@ /* * This file is part of the KDE project * * Copyright (c) Michael Thaler * * 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_CHANNEL_SEPARATOR_H_ #define _KIS_CHANNEL_SEPARATOR_H_ class KoUpdater; class KisViewManager; enum enumSepAlphaOptions { COPY_ALPHA_TO_SEPARATIONS = 0, DISCARD_ALPHA = 1, CREATE_ALPHA_SEPARATION = 2 }; enum enumSepSource { CURRENT_LAYER = 0, ALL_LAYERS = 1, VISIBLE_LAYERS = 2 }; -enum enumSepOutput { - TO_LAYERS = 0, - TO_IMAGES = 1 -}; - class KisChannelSeparator { public: KisChannelSeparator(KisViewManager * view); virtual ~KisChannelSeparator() {} - void separate(KoUpdater * progress, enumSepAlphaOptions alphaOps, enumSepSource sourceOps, enumSepOutput outputOps, bool downscale, bool toColor); + void separate(KoUpdater * progress, enumSepAlphaOptions alphaOps, enumSepSource sourceOps, bool downscale, bool toColor, bool activateCurrentChannel); private: KisViewManager *m_viewManager; }; #endif diff --git a/plugins/extensions/separate_channels/kis_separate_channels_plugin.cc b/plugins/extensions/separate_channels/kis_separate_channels_plugin.cc index 545d1ffb71..a227fd8b75 100644 --- a/plugins/extensions/separate_channels/kis_separate_channels_plugin.cc +++ b/plugins/extensions/separate_channels/kis_separate_channels_plugin.cc @@ -1,99 +1,99 @@ /* * This file is part of the KDE project * * Copyright (c) 2005 Michael Thaler * * 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_separate_channels_plugin.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_channel_separator.h" #include "dlg_separate.h" K_PLUGIN_FACTORY_WITH_JSON(KisSeparateChannelsPluginFactory, "kritaseparatechannels.json", registerPlugin();) KisSeparateChannelsPlugin::KisSeparateChannelsPlugin(QObject *parent, const QVariantList &) : KisActionPlugin(parent) { KisAction *action = createAction("separate"); connect(action, SIGNAL(triggered(bool)), SLOT(slotSeparate())); } KisSeparateChannelsPlugin::~KisSeparateChannelsPlugin() { } void KisSeparateChannelsPlugin::slotSeparate() { KisImageSP image = viewManager()->image(); if (!image) return; KisLayerSP l = viewManager()->nodeManager()->activeLayer(); if (!l) return; KisPaintDeviceSP dev = l->paintDevice(); if (!dev) return; DlgSeparate * dlgSeparate = new DlgSeparate(dev->colorSpace()->name(), image->colorSpace()->name(), viewManager()->mainWindow(), "Separate"); Q_CHECK_PTR(dlgSeparate); dlgSeparate->setCaption(i18n("Separate Image")); // If we're 8-bits, disable the downscale option if (dev->pixelSize() == dev->channelCount()) { dlgSeparate->enableDownscale(false); } if (dlgSeparate->exec() == QDialog::Accepted) { QApplication::setOverrideCursor(Qt::BusyCursor); KisChannelSeparator separator(viewManager()); separator.separate(viewManager()->createUnthreadedUpdater(i18n("Separate Image")), dlgSeparate->getAlphaOptions(), dlgSeparate->getSource(), - dlgSeparate->getOutput(), dlgSeparate->getDownscale(), - dlgSeparate->getToColor()); + dlgSeparate->getToColor(), + dlgSeparate->getActivateCurrentChannel()); QApplication::restoreOverrideCursor(); } delete dlgSeparate; } #include "kis_separate_channels_plugin.moc" diff --git a/plugins/extensions/separate_channels/wdg_separations.ui b/plugins/extensions/separate_channels/wdg_separations.ui index 47860f8e16..c85dad9d89 100644 --- a/plugins/extensions/separate_channels/wdg_separations.ui +++ b/plugins/extensions/separate_channels/wdg_separations.ui @@ -1,183 +1,166 @@ WdgSeparations 0 0 - 570 + 498 392 Current color model: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter false 0 0 false 0 0 Source Current layer true Flatten all layers before separation - - - - Output - - - - - - To layers - - - true - - - - - - - To images - - - - - - Alpha Options Copy alpha channel to each separated channel as an alpha channel Discard alpha channel true Create separate separation from alpha channel Downscale to 8-bit before separating Output to color, not grayscale + + + + Activate only the current channel for the new node, deactivate all other channels. + + + Activate the current channel + + + Qt::Vertical QSizePolicy::Expanding 20 16 radioCurrentLayer - radioLayers radioDiscardAlpha chkDownscale chkColors diff --git a/plugins/filters/asccdl/kis_asccdl_filter.cpp b/plugins/filters/asccdl/kis_asccdl_filter.cpp index d0ba94eff3..4a8326721b 100644 --- a/plugins/filters/asccdl/kis_asccdl_filter.cpp +++ b/plugins/filters/asccdl/kis_asccdl_filter.cpp @@ -1,130 +1,131 @@ /* * 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. */ #include "kis_asccdl_filter.h" #include "kis_wdg_asccdl.h" #include #include #include #include #include #include K_PLUGIN_FACTORY_WITH_JSON(KritaASCCDLFactory, "kritaasccdl.json", registerPlugin();) KritaASCCDL::KritaASCCDL(QObject *parent, const QVariantList &) : QObject(parent) { KisFilterRegistry::instance()->add(KisFilterSP(new KisFilterASCCDL())); } KritaASCCDL::~KritaASCCDL() { } KisFilterASCCDL::KisFilterASCCDL(): KisColorTransformationFilter(id(), FiltersCategoryAdjustId, i18n("&Slope, Offset, Power...")) { setSupportsPainting(true); setSupportsAdjustmentLayers(true); setSupportsLevelOfDetail(true); setSupportsThreading(true); setColorSpaceIndependence(FULLY_INDEPENDENT); setShowConfigurationWidget(true); } KoColorTransformation *KisFilterASCCDL::createTransformation(const KoColorSpace *cs, const KisFilterConfigurationSP config) const { KoColor black(Qt::black, cs); + KoColor white(Qt::white, cs); return new KisASCCDLTransformation(cs, config->getColor("slope", black), - config->getColor("offset", black), + config->getColor("offset", white), config->getColor("power", black)); } KisConfigWidget *KisFilterASCCDL::createConfigurationWidget(QWidget *parent, const KisPaintDeviceSP dev, bool) const { return new KisASCCDLConfigWidget(parent, dev->colorSpace()); } bool KisFilterASCCDL::needsTransparentPixels(const KisFilterConfigurationSP config, const KoColorSpace *cs) const { KoColor black(Qt::black, cs); KoColor offset = config->getColor("offset", black); offset.convertTo(cs); if (cs->difference(black.data(), offset.data())>0) { return true; } return false; } -KisFilterConfigurationSP KisFilterASCCDL::factoryConfiguration() const +KisFilterConfigurationSP KisFilterASCCDL::defaultConfiguration() const { - KisColorTransformationConfigurationSP config = new KisColorTransformationConfiguration(id().id(), 0); + KisFilterConfigurationSP config = factoryConfiguration(); QVariant colorVariant("KoColor"); KoColor black; black.fromQColor(QColor(Qt::black)); KoColor white; white.fromQColor(QColor(Qt::white)); colorVariant.setValue(white); config->setProperty( "slope", colorVariant); config->setProperty( "power", colorVariant); colorVariant.setValue(black); config->setProperty("offset", colorVariant); return config; } KisASCCDLTransformation::KisASCCDLTransformation(const KoColorSpace *cs, KoColor slope, KoColor offset, KoColor power) { QVector slopeN(cs->channelCount()); slope.convertTo(cs); slope.colorSpace()->normalisedChannelsValue(slope.data(), slopeN); m_slope = slopeN; offset.convertTo(cs); QVector offsetN(cs->channelCount()); offset.colorSpace()->normalisedChannelsValue(offset.data(), offsetN); m_offset = offsetN; power.convertTo(cs); QVector powerN(cs->channelCount()); power.colorSpace()->normalisedChannelsValue(power.data(), powerN); m_power = powerN; m_cs = cs; } void KisASCCDLTransformation::transform(const quint8 *src, quint8 *dst, qint32 nPixels) const { QVector normalised(m_cs->channelCount()); const int pixelSize = m_cs->pixelSize(); while (nPixels--) { m_cs->normalisedChannelsValue(src, normalised); for (uint c=0; cchannelCount(); c++){ if (m_cs->channels().at(c)->channelType()!=KoChannelInfo::ALPHA) { normalised[c] = qPow( (normalised.at(c)*m_slope.at(c))+m_offset.at(c), m_power.at(c)); } } m_cs->fromNormalisedChannelsValue(dst, normalised); src += pixelSize; dst += pixelSize; } } #include "kis_asccdl_filter.moc" diff --git a/plugins/filters/asccdl/kis_asccdl_filter.h b/plugins/filters/asccdl/kis_asccdl_filter.h index 5e818f9812..096b2ed14c 100644 --- a/plugins/filters/asccdl/kis_asccdl_filter.h +++ b/plugins/filters/asccdl/kis_asccdl_filter.h @@ -1,61 +1,61 @@ /* * 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_ASCCDL_FILTER_H #define KIS_ASCCDL_FILTER_H #include #include "filter/kis_color_transformation_filter.h" class KritaASCCDL : public QObject { Q_OBJECT public: KritaASCCDL(QObject *parent, const QVariantList &); ~KritaASCCDL() override; }; class KisFilterASCCDL: public KisColorTransformationFilter { public: KisFilterASCCDL(); public: static inline KoID id() { return KoID("asc-cdl", i18n("Slope, Offset, Power(ASC-CDL)")); } KoColorTransformation *createTransformation(const KoColorSpace* cs, const KisFilterConfigurationSP config) const override; KisConfigWidget *createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP dev, bool useForMasks) const override; bool needsTransparentPixels(const KisFilterConfigurationSP config, const KoColorSpace *cs) const override; protected: - KisFilterConfigurationSP factoryConfiguration() const override; + KisFilterConfigurationSP defaultConfiguration() const override; }; class KisASCCDLTransformation : public KoColorTransformation { public: KisASCCDLTransformation(const KoColorSpace *cs, KoColor slope, KoColor offset, KoColor power); void transform(const quint8* src, quint8* dst, qint32 nPixels) const override; private: QVector m_slope; QVector m_offset; QVector m_power; const KoColorSpace *m_cs; }; #endif // KIS_ASCCDL_H diff --git a/plugins/filters/blur/kis_blur_filter.cpp b/plugins/filters/blur/kis_blur_filter.cpp index 2dc522a7ec..183165b751 100644 --- a/plugins/filters/blur/kis_blur_filter.cpp +++ b/plugins/filters/blur/kis_blur_filter.cpp @@ -1,139 +1,139 @@ /* * This file is part of Krita * * Copyright (c) 2006 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 //MSVC requires that Vc come first #include "kis_blur_filter.h" #include #include #include #include "kis_wdg_blur.h" #include "ui_wdgblur.h" #include #include #include #include #include #include "kis_mask_generator.h" #include "kis_lod_transform.h" KisBlurFilter::KisBlurFilter() : KisFilter(id(), FiltersCategoryBlurId, i18n("&Blur...")) { setSupportsPainting(true); setSupportsAdjustmentLayers(true); setSupportsLevelOfDetail(true); setColorSpaceIndependence(FULLY_INDEPENDENT); } KisConfigWidget * KisBlurFilter::createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP, bool) const { return new KisWdgBlur(parent); } -KisFilterConfigurationSP KisBlurFilter::factoryConfiguration() const +KisFilterConfigurationSP KisBlurFilter::defaultConfiguration() const { - KisFilterConfigurationSP config = new KisFilterConfiguration(id().id(), 1); + KisFilterConfigurationSP config = factoryConfiguration(); config->setProperty("halfWidth", 5); config->setProperty("halfHeight", 5); config->setProperty("rotate", 0); config->setProperty("strength", 0); config->setProperty("shape", 0); return config; } void KisBlurFilter::processImpl(KisPaintDeviceSP device, const QRect& rect, const KisFilterConfigurationSP _config, KoUpdater* progressUpdater ) const { QPoint srcTopLeft = rect.topLeft(); Q_ASSERT(device != 0); KisFilterConfigurationSP config = _config ? _config : new KisFilterConfiguration(id().id(), 1); KisLodTransformScalar t(device); QVariant value; const uint halfWidth = t.scale((config->getProperty("halfWidth", value)) ? value.toUInt() : 5); const uint halfHeight = t.scale((config->getProperty("halfHeight", value)) ? value.toUInt() : 5); int shape = (config->getProperty("shape", value)) ? value.toInt() : 0; uint width = 2 * halfWidth + 1; uint height = 2 * halfHeight + 1; float aspectRatio = (float) width / height; int rotate = (config->getProperty("rotate", value)) ? value.toInt() : 0; int strength = 100 - (config->getProperty("strength", value) ? value.toUInt() : 0); int hFade = (halfWidth * strength) / 100; int vFade = (halfHeight * strength) / 100; KisMaskGenerator* kas; dbgKrita << width << "" << height << "" << hFade << "" << vFade; switch (shape) { case 1: kas = new KisRectangleMaskGenerator(width, aspectRatio, hFade, vFade, 2, true); break; case 0: default: kas = new KisCircleMaskGenerator(width, aspectRatio, hFade, vFade, 2, true); break; } QBitArray channelFlags; if (config) { channelFlags = config->channelFlags(); } if (channelFlags.isEmpty() || !config) { channelFlags = QBitArray(device->colorSpace()->channelCount(), true); } KisConvolutionKernelSP kernel = KisConvolutionKernel::fromMaskGenerator(kas, rotate * M_PI / 180.0); delete kas; KisConvolutionPainter painter(device); painter.setChannelFlags(channelFlags); painter.setProgress(progressUpdater); painter.applyMatrix(kernel, device, srcTopLeft, srcTopLeft, rect.size(), BORDER_REPEAT); } QRect KisBlurFilter::neededRect(const QRect & rect, const KisFilterConfigurationSP _config, int lod) const { KisLodTransformScalar t(lod); QVariant value; const int halfWidth = t.scale(_config->getProperty("halfWidth", value) ? value.toUInt() : 5); const int halfHeight = t.scale(_config->getProperty("halfHeight", value) ? value.toUInt() : 5); return rect.adjusted(-halfWidth * 2, -halfHeight * 2, halfWidth * 2, halfHeight * 2); } QRect KisBlurFilter::changedRect(const QRect & rect, const KisFilterConfigurationSP _config, int lod) const { KisLodTransformScalar t(lod); QVariant value; const int halfWidth = t.scale(_config->getProperty("halfWidth", value) ? value.toUInt() : 5); const int halfHeight = t.scale(_config->getProperty("halfHeight", value) ? value.toUInt() : 5); return rect.adjusted(-halfWidth, -halfHeight, halfWidth, halfHeight); } diff --git a/plugins/filters/blur/kis_blur_filter.h b/plugins/filters/blur/kis_blur_filter.h index 14097419c9..fbed93efaf 100644 --- a/plugins/filters/blur/kis_blur_filter.h +++ b/plugins/filters/blur/kis_blur_filter.h @@ -1,48 +1,48 @@ /* * This file is part of Krita * * Copyright (c) 2006 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_BLUR_FILTER_H #define KIS_BLUR_FILTER_H #include "filter/kis_filter.h" class KisBlurFilter : public KisFilter { public: KisBlurFilter(); public: void processImpl(KisPaintDeviceSP device, const QRect& size, const KisFilterConfigurationSP config, KoUpdater* progressUpdater ) const override; static inline KoID id() { return KoID("blur", i18n("Blur")); } - KisFilterConfigurationSP factoryConfiguration() const override; + KisFilterConfigurationSP defaultConfiguration() const override; public: KisConfigWidget * createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP dev, bool useForMasks) const override; QRect neededRect(const QRect & rect, const KisFilterConfigurationSP _config, int lod) const override; QRect changedRect(const QRect & rect, const KisFilterConfigurationSP _config, int lod) const override; }; #endif diff --git a/plugins/filters/blur/kis_gaussian_blur_filter.cpp b/plugins/filters/blur/kis_gaussian_blur_filter.cpp index 601d8aa8d2..45ecf787bf 100644 --- a/plugins/filters/blur/kis_gaussian_blur_filter.cpp +++ b/plugins/filters/blur/kis_gaussian_blur_filter.cpp @@ -1,148 +1,148 @@ /* * This file is part of Krita * * Copyright (c) 2009 Edward Apap * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_gaussian_blur_filter.h" #include "kis_wdg_gaussian_blur.h" #include #include #include #include #include "ui_wdg_gaussian_blur.h" #include #include #include #include #include #include "kis_lod_transform.h" #include KisGaussianBlurFilter::KisGaussianBlurFilter() : KisFilter(id(), FiltersCategoryBlurId, i18n("&Gaussian Blur...")) { setSupportsPainting(true); setSupportsAdjustmentLayers(true); setSupportsLevelOfDetail(true); setColorSpaceIndependence(FULLY_INDEPENDENT); } KisConfigWidget * KisGaussianBlurFilter::createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP, bool usedForMasks) const { return new KisWdgGaussianBlur(usedForMasks, parent); } -KisFilterConfigurationSP KisGaussianBlurFilter::factoryConfiguration() const +KisFilterConfigurationSP KisGaussianBlurFilter::defaultConfiguration() const { - KisFilterConfigurationSP config = new KisFilterConfiguration(id().id(), 1); + KisFilterConfigurationSP config = factoryConfiguration(); config->setProperty("horizRadius", 5); config->setProperty("vertRadius", 5); config->setProperty("lockAspect", true); return config; } void KisGaussianBlurFilter::processImpl(KisPaintDeviceSP device, const QRect& rect, const KisFilterConfigurationSP _config, KoUpdater* progressUpdater ) const { Q_ASSERT(device != 0); KisFilterConfigurationSP config = _config ? _config : new KisFilterConfiguration(id().id(), 1); KisLodTransformScalar t(device); QVariant value; config->getProperty("horizRadius", value); float horizontalRadius = t.scale(value.toFloat()); config->getProperty("vertRadius", value); float verticalRadius = t.scale(value.toFloat()); QBitArray channelFlags; if (config) { channelFlags = config->channelFlags(); } if (channelFlags.isEmpty() || !config) { channelFlags = QBitArray(device->colorSpace()->channelCount(), true); } KisGaussianKernel::applyGaussian(device, rect, horizontalRadius, verticalRadius, channelFlags, progressUpdater); } QRect KisGaussianBlurFilter::neededRect(const QRect & rect, const KisFilterConfigurationSP _config, int lod) const { KisLodTransformScalar t(lod); QVariant value; /** * NOTE: integer division by two is done on purpose, * because the kernel size is always odd */ const int halfWidth = _config->getProperty("horizRadius", value) ? KisGaussianKernel::kernelSizeFromRadius(t.scale(value.toFloat())) / 2 : 5; const int halfHeight = _config->getProperty("vertRadius", value) ? KisGaussianKernel::kernelSizeFromRadius(t.scale(value.toFloat())) / 2 : 5; return rect.adjusted(-halfWidth * 2, -halfHeight * 2, halfWidth * 2, halfHeight * 2); } QRect KisGaussianBlurFilter::changedRect(const QRect & rect, const KisFilterConfigurationSP _config, int lod) const { KisLodTransformScalar t(lod); QVariant value; const int halfWidth = _config->getProperty("horizRadius", value) ? KisGaussianKernel::kernelSizeFromRadius(t.scale(value.toFloat())) / 2 : 5; const int halfHeight = _config->getProperty("vertRadius", value) ? KisGaussianKernel::kernelSizeFromRadius(t.scale(value.toFloat())) / 2 : 5; return rect.adjusted( -halfWidth, -halfHeight, halfWidth, halfHeight); } bool KisGaussianBlurFilter::configurationAllowedForMask(KisFilterConfigurationSP config) const { //ENTER_FUNCTION() << config->getFloat("horizRadius", 5.0) << config->getFloat("vertRadius", 5.0); const float maxRadiusForMask = 100.0; return config->getFloat("horizRadius", 5.0) <= maxRadiusForMask && config->getFloat("vertRadius", 5.0) <= maxRadiusForMask; } void KisGaussianBlurFilter::fixLoadedFilterConfigurationForMasks(KisFilterConfigurationSP config) const { ENTER_FUNCTION(); const float maxRadiusForMask = 100.0; if (config->getFloat("horizRadius", 5.0) > maxRadiusForMask) { config->setProperty("horizRadius", maxRadiusForMask); } if (config->getFloat("vertRadius", 5.0) > maxRadiusForMask) { config->setProperty("vertRadius", maxRadiusForMask); } } diff --git a/plugins/filters/blur/kis_gaussian_blur_filter.h b/plugins/filters/blur/kis_gaussian_blur_filter.h index c7ec27f318..1bbadb734f 100644 --- a/plugins/filters/blur/kis_gaussian_blur_filter.h +++ b/plugins/filters/blur/kis_gaussian_blur_filter.h @@ -1,53 +1,53 @@ /* This file is part of Krita * * Copyright (c) 2009 Edward Apap * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_GAUSSIAN_BLUR_FILTER_H #define KIS_GAUSSIAN_BLUR_FILTER_H #include "filter/kis_filter.h" #include "ui_wdg_gaussian_blur.h" #include class KisGaussianBlurFilter : public KisFilter { public: KisGaussianBlurFilter(); public: void processImpl(KisPaintDeviceSP device, const QRect& rect, const KisFilterConfigurationSP config, KoUpdater* progressUpdater ) const override; static inline KoID id() { return KoID("gaussian blur", i18n("Gaussian Blur")); } - KisFilterConfigurationSP factoryConfiguration() const override; + KisFilterConfigurationSP defaultConfiguration() const override; public: KisConfigWidget * createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP dev, bool useForMasks) const override; QRect neededRect(const QRect & rect, const KisFilterConfigurationSP _config, int lod) const override; QRect changedRect(const QRect & rect, const KisFilterConfigurationSP _config, int lod) const override; bool configurationAllowedForMask(KisFilterConfigurationSP config) const override; void fixLoadedFilterConfigurationForMasks(KisFilterConfigurationSP config) const override; }; #endif diff --git a/plugins/filters/blur/kis_lens_blur_filter.cpp b/plugins/filters/blur/kis_lens_blur_filter.cpp index dd786047ac..7b1ef6d55b 100644 --- a/plugins/filters/blur/kis_lens_blur_filter.cpp +++ b/plugins/filters/blur/kis_lens_blur_filter.cpp @@ -1,205 +1,205 @@ /* * This file is part of Krita * * Copyright (c) 2010 Edward Apap * * 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_lens_blur_filter.h" #include "kis_wdg_lens_blur.h" #include #include #include #include "ui_wdg_lens_blur.h" #include #include #include #include #include #include "kis_lod_transform.h" #include #include KisLensBlurFilter::KisLensBlurFilter() : KisFilter(id(), FiltersCategoryBlurId, i18n("&Lens Blur...")) { setSupportsPainting(true); setSupportsAdjustmentLayers(true); setSupportsLevelOfDetail(true); setColorSpaceIndependence(FULLY_INDEPENDENT); } KisConfigWidget * KisLensBlurFilter::createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP, bool) const { return new KisWdgLensBlur(parent); } QSize KisLensBlurFilter::getKernelHalfSize(const KisFilterConfigurationSP config, int lod) { QPolygonF iris = getIrisPolygon(config, lod); QRect rect = iris.boundingRect().toAlignedRect(); int w = std::ceil(qreal(rect.width()) / 2.0); int h = std::ceil(qreal(rect.height()) / 2.0); return QSize(w, h); } -KisFilterConfigurationSP KisLensBlurFilter::factoryConfiguration() const +KisFilterConfigurationSP KisLensBlurFilter::defaultConfiguration() const { - KisFilterConfigurationSP config = new KisFilterConfiguration(id().id(), 1); + KisFilterConfigurationSP config = factoryConfiguration(); config->setProperty("irisShape", "Pentagon (5)"); config->setProperty("irisRadius", 5); config->setProperty("irisRotation", 0); QSize halfSize = getKernelHalfSize(config, 0); config->setProperty("halfWidth", halfSize.width()); config->setProperty("halfHeight", halfSize.height()); return config; } QPolygonF KisLensBlurFilter::getIrisPolygon(const KisFilterConfigurationSP config, int lod) { KIS_ASSERT_RECOVER(config) { return QPolygonF(); } KisLodTransformScalar t(lod); QVariant value; config->getProperty("irisShape", value); QString irisShape = value.toString(); config->getProperty("irisRadius", value); uint irisRadius = t.scale(value.toUInt()); config->getProperty("irisRotation", value); uint irisRotation = value.toUInt(); if (irisRadius < 1) return QPolygon(); QPolygonF irisShapePoly; int sides = 1; qreal angle = 0; if (irisShape == "Triangle") sides = 3; else if (irisShape == "Quadrilateral (4)") sides = 4; else if (irisShape == "Pentagon (5)") sides = 5; else if (irisShape == "Hexagon (6)") sides = 6; else if (irisShape == "Heptagon (7)") sides = 7; else if (irisShape == "Octagon (8)") sides = 8; else return QPolygonF(); for (int i = 0; i < sides; ++i) { irisShapePoly << QPointF(0.5 * cos(angle), 0.5 * sin(angle)); angle += 2 * M_PI / sides; } QTransform transform; transform.rotate(irisRotation); transform.scale(irisRadius * 2, irisRadius * 2); QPolygonF transformedIris = transform.map(irisShapePoly); return transformedIris; } void KisLensBlurFilter::processImpl(KisPaintDeviceSP device, const QRect& rect, const KisFilterConfigurationSP _config, KoUpdater* progressUpdater ) const { QPoint srcTopLeft = rect.topLeft(); Q_ASSERT(device != 0); KisFilterConfigurationSP config = _config ? _config : new KisFilterConfiguration(id().id(), 1); QBitArray channelFlags; if (config) { channelFlags = config->channelFlags(); } if (channelFlags.isEmpty() || !config) { channelFlags = QBitArray(device->colorSpace()->channelCount(), true); } const int lod = device->defaultBounds()->currentLevelOfDetail(); QPolygonF transformedIris = getIrisPolygon(config, lod); if (transformedIris.isEmpty()) return; QRectF boundingRect = transformedIris.boundingRect(); int kernelWidth = boundingRect.toAlignedRect().width(); int kernelHeight = boundingRect.toAlignedRect().height(); QImage kernelRepresentation(kernelWidth, kernelHeight, QImage::Format_RGB32); kernelRepresentation.fill(0); QPainter imagePainter(&kernelRepresentation); imagePainter.setRenderHint(QPainter::Antialiasing); imagePainter.setBrush(QColor::fromRgb(255, 255, 255)); QTransform offsetTransform; offsetTransform.translate(-boundingRect.x(), -boundingRect.y()); imagePainter.setTransform(offsetTransform); imagePainter.drawPolygon(transformedIris, Qt::WindingFill); // construct kernel from image Eigen::Matrix irisKernel(kernelHeight, kernelWidth); for (int j = 0; j < kernelHeight; ++j) { for (int i = 0; i < kernelWidth; ++i) { irisKernel(j, i) = qRed(kernelRepresentation.pixel(i, j)); } } // apply convolution KisConvolutionPainter painter(device); painter.setChannelFlags(channelFlags); painter.setProgress(progressUpdater); KisConvolutionKernelSP kernel = KisConvolutionKernel::fromMatrix(irisKernel, 0, irisKernel.sum()); painter.applyMatrix(kernel, device, srcTopLeft, srcTopLeft, rect.size(), BORDER_REPEAT); } QRect KisLensBlurFilter::neededRect(const QRect & rect, const KisFilterConfigurationSP _config, int lod) const { KisLodTransformScalar t(lod); QVariant value; const int halfWidth = t.scale(_config->getProperty("halfWidth", value) ? value.toUInt() : 5); const int halfHeight = t.scale(_config->getProperty("halfHeight", value) ? value.toUInt() : 5); return rect.adjusted(-halfWidth * 2, -halfHeight * 2, halfWidth * 2, halfHeight * 2); } QRect KisLensBlurFilter::changedRect(const QRect & rect, const KisFilterConfigurationSP _config, int lod) const { KisLodTransformScalar t(lod); QVariant value; const int halfWidth = t.scale(_config->getProperty("halfWidth", value) ? value.toUInt() : 5); const int halfHeight = t.scale(_config->getProperty("halfHeight", value) ? value.toUInt() : 5); return rect.adjusted(-halfWidth, -halfHeight, halfWidth, halfHeight); } diff --git a/plugins/filters/blur/kis_lens_blur_filter.h b/plugins/filters/blur/kis_lens_blur_filter.h index f072bf2ebe..ef53307f9f 100644 --- a/plugins/filters/blur/kis_lens_blur_filter.h +++ b/plugins/filters/blur/kis_lens_blur_filter.h @@ -1,59 +1,59 @@ /* * This file is part of Krita * * Copyright (c) 2010 Edward Apap * * 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_LENS_BLUR_FILTER_H #define KIS_LENS_BLUR_FILTER_H #include "filter/kis_filter.h" #include "ui_wdg_lens_blur.h" #include class KisLensBlurFilter : public KisFilter { public: KisLensBlurFilter(); public: void processImpl(KisPaintDeviceSP src, const QRect& size, const KisFilterConfigurationSP config, KoUpdater* progressUpdater ) const override; static inline KoID id() { return KoID("lens blur", i18n("Lens Blur")); } - KisFilterConfigurationSP factoryConfiguration() const override; + KisFilterConfigurationSP defaultConfiguration() const override; static QSize getKernelHalfSize(const KisFilterConfigurationSP config, int lod); QRect neededRect(const QRect & rect, const KisFilterConfigurationSP _config, int lod) const override; QRect changedRect(const QRect & rect, const KisFilterConfigurationSP _config, int lod) const override; public: KisConfigWidget * createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP dev, bool useForMasks) const override; private: static QPolygonF getIrisPolygon(const KisFilterConfigurationSP config, int lod); }; #endif diff --git a/plugins/filters/blur/kis_motion_blur_filter.cpp b/plugins/filters/blur/kis_motion_blur_filter.cpp index 60ee8ef7f9..903070cb1c 100644 --- a/plugins/filters/blur/kis_motion_blur_filter.cpp +++ b/plugins/filters/blur/kis_motion_blur_filter.cpp @@ -1,174 +1,174 @@ /* * This file is part of Krita * * Copyright (c) 2010 Edward Apap * * 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_motion_blur_filter.h" #include "kis_wdg_motion_blur.h" #include #include #include #include "ui_wdg_motion_blur.h" #include #include #include #include #include #include "kis_lod_transform.h" #include #include KisMotionBlurFilter::KisMotionBlurFilter() : KisFilter(id(), FiltersCategoryBlurId, i18n("&Motion Blur...")) { setSupportsPainting(true); setSupportsAdjustmentLayers(true); setSupportsLevelOfDetail(true); setColorSpaceIndependence(FULLY_INDEPENDENT); } KisConfigWidget * KisMotionBlurFilter::createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP, bool) const { return new KisWdgMotionBlur(parent); } -KisFilterConfigurationSP KisMotionBlurFilter::factoryConfiguration() const +KisFilterConfigurationSP KisMotionBlurFilter::defaultConfiguration() const { - KisFilterConfigurationSP config = new KisFilterConfiguration(id().id(), 1); + KisFilterConfigurationSP config = factoryConfiguration(); config->setProperty("blurAngle", 0); config->setProperty("blurLength", 5); return config; } void KisMotionBlurFilter::processImpl(KisPaintDeviceSP device, const QRect& rect, const KisFilterConfigurationSP _config, KoUpdater* progressUpdater ) const { QPoint srcTopLeft = rect.topLeft(); Q_ASSERT(device); KisFilterConfigurationSP config = _config ? _config : new KisFilterConfiguration(id().id(), 1); QVariant value; uint blurAngle = 0; if (config->getProperty("blurAngle", value)) { blurAngle = value.toUInt(); } KisLodTransformScalar t(device); uint blurLength = 0; if (config->getProperty("blurLength", value)) { blurLength = t.scale(value.toUInt()); } if (blurLength == 0) { return; } QBitArray channelFlags; if (config) { channelFlags = config->channelFlags(); } if (channelFlags.isEmpty() || !config) { channelFlags = QBitArray(device->colorSpace()->channelCount(), true); } // convert angle to radians qreal angleRadians = blurAngle / 360.0 * 2 * M_PI; // construct image qreal halfWidth = blurLength / 2.0 * cos(angleRadians); qreal halfHeight = blurLength / 2.0 * sin(angleRadians); int kernelWidth = ceil(fabs(halfWidth)) * 2; int kernelHeight = ceil(fabs(halfHeight)) * 2; // check for zero dimensions (vertical/horizontal motion vectors) kernelWidth = (kernelWidth == 0) ? 1 : kernelWidth; kernelHeight = (kernelHeight == 0) ? 1 : kernelHeight; QImage kernelRepresentation(kernelWidth, kernelHeight, QImage::Format_RGB32); kernelRepresentation.fill(0); QPainter imagePainter(&kernelRepresentation); imagePainter.setRenderHint(QPainter::Antialiasing); imagePainter.setPen(QPen(QColor::fromRgb(255, 255, 255), 1.0)); imagePainter.drawLine(QPointF(kernelWidth / 2 - halfWidth, kernelHeight / 2 + halfHeight), QPointF(kernelWidth / 2 + halfWidth, kernelHeight / 2 - halfHeight)); // construct kernel from image Eigen::Matrix motionBlurKernel(kernelHeight, kernelWidth); for (int j = 0; j < kernelHeight; ++j) { for (int i = 0; i < kernelWidth; ++i) { motionBlurKernel(j, i) = qRed(kernelRepresentation.pixel(i, j)); } } // apply convolution KisConvolutionPainter painter(device); painter.setChannelFlags(channelFlags); painter.setProgress(progressUpdater); KisConvolutionKernelSP kernel = KisConvolutionKernel::fromMatrix(motionBlurKernel, 0, motionBlurKernel.sum()); painter.applyMatrix(kernel, device, srcTopLeft, srcTopLeft, rect.size(), BORDER_REPEAT); } QRect KisMotionBlurFilter::neededRect(const QRect & rect, const KisFilterConfigurationSP _config, int lod) const { KisLodTransformScalar t(lod); QVariant value; uint blurAngle = _config->getProperty("blurAngle", value) ? value.toUInt() : 0; uint blurLength = t.scale(_config->getProperty("blurLength", value) ? value.toUInt() : 5); qreal angleRadians = blurAngle / 360.0 * 2 * M_PI; const int halfWidth = ceil(fabs(blurLength / 2.0 * cos(angleRadians))); const int halfHeight = ceil(fabs(blurLength / 2.0 * cos(angleRadians))); return rect.adjusted(-halfWidth * 2, -halfHeight * 2, halfWidth * 2, halfHeight * 2); } QRect KisMotionBlurFilter::changedRect(const QRect & rect, const KisFilterConfigurationSP _config, int lod) const { KisLodTransformScalar t(lod); QVariant value; uint blurAngle = _config->getProperty("blurAngle", value) ? value.toUInt() : 0; uint blurLength = t.scale(_config->getProperty("blurLength", value) ? value.toUInt() : 5); qreal angleRadians = blurAngle / 360.0 * 2 * M_PI; const int halfWidth = ceil(fabs(blurLength * cos(angleRadians))); const int halfHeight = ceil(fabs(blurLength * cos(angleRadians))); return rect.adjusted(-halfWidth * 2, -halfHeight * 2, halfWidth * 2, halfHeight * 2); } diff --git a/plugins/filters/blur/kis_motion_blur_filter.h b/plugins/filters/blur/kis_motion_blur_filter.h index d455d50c4f..84748a70fe 100644 --- a/plugins/filters/blur/kis_motion_blur_filter.h +++ b/plugins/filters/blur/kis_motion_blur_filter.h @@ -1,52 +1,52 @@ /* * This file is part of Krita * * Copyright (c) 2010 Edward Apap * * 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_MOTION_BLUR_FILTER_H #define KIS_MOTION_BLUR_FILTER_H #include "filter/kis_filter.h" #include "ui_wdg_motion_blur.h" #include class KisMotionBlurFilter : public KisFilter { public: KisMotionBlurFilter(); public: void processImpl(KisPaintDeviceSP src, const QRect& size, const KisFilterConfigurationSP config, KoUpdater* progressUpdater ) const override; static inline KoID id() { return KoID("motion blur", i18n("Motion Blur")); } - KisFilterConfigurationSP factoryConfiguration() const override; + KisFilterConfigurationSP defaultConfiguration() const override; public: KisConfigWidget * createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP dev, bool useForMasks) const override; QRect neededRect(const QRect & rect, const KisFilterConfigurationSP _config, int lod) const override; QRect changedRect(const QRect & rect, const KisFilterConfigurationSP _config, int lod) const override; }; #endif diff --git a/plugins/filters/colors/kis_color_to_alpha.cpp b/plugins/filters/colors/kis_color_to_alpha.cpp index b6d8b12b34..21173c2179 100644 --- a/plugins/filters/colors/kis_color_to_alpha.cpp +++ b/plugins/filters/colors/kis_color_to_alpha.cpp @@ -1,189 +1,189 @@ /* * This file is part of Krita * * Copyright (c) 2006 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_color_to_alpha.h" #include #include #include #include #include #include "kis_progress_update_helper.h" #include #include #include #include #include #include "ui_wdgcolortoalphabase.h" #include "kis_wdg_color_to_alpha.h" #include #include KisFilterColorToAlpha::KisFilterColorToAlpha() : KisFilter(id(), FiltersCategoryColorId, i18n("&Color to Alpha...")) { setSupportsPainting(true); setSupportsAdjustmentLayers(true); setSupportsLevelOfDetail(true); setColorSpaceIndependence(FULLY_INDEPENDENT); } KisConfigWidget * KisFilterColorToAlpha::createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP, bool) const { return new KisWdgColorToAlpha(parent); } -KisFilterConfigurationSP KisFilterColorToAlpha::factoryConfiguration() const +KisFilterConfigurationSP KisFilterColorToAlpha::defaultConfiguration() const { - KisFilterConfigurationSP config = new KisFilterConfiguration("colortoalpha", 1); + KisFilterConfigurationSP config = factoryConfiguration(); config->setProperty("targetcolor", QColor(255, 255, 255)); config->setProperty("threshold", 100); return config; } template inline void inverseOver(const int numChannels, const int *channelIndex, channel_type *dst, const channel_type *baseColor, qreal dstOpacity) { for (int i = 0; i < numChannels; i++) { const int idx = channelIndex[i]; dst[idx] = KoColorSpaceMaths::clamp( (static_cast(dst[idx]) - baseColor[idx]) / dstOpacity + baseColor[idx]); } } template void applyToIterator(const int numChannels, const int *channelIndex, KisSequentialIteratorProgress &it, KoColor baseColor, int threshold, const KoColorSpace *cs) { qreal thresholdF = threshold; quint8 *baseColorData_uint8 = baseColor.data(); channel_type *baseColorData = reinterpret_cast(baseColorData_uint8); while (it.nextPixel()) { channel_type *dst = reinterpret_cast(it.rawData()); quint8 *dst_uint8 = it.rawData(); quint8 diff = cs->difference(baseColorData_uint8, dst_uint8); qreal newOpacity = diff >= threshold ? 1.0 : diff / thresholdF; if(newOpacity < cs->opacityF(dst_uint8)) { cs->setOpacity(dst_uint8, newOpacity, 1); } inverseOver(numChannels, channelIndex, dst, baseColorData, newOpacity); } } void KisFilterColorToAlpha::processImpl(KisPaintDeviceSP device, const QRect& rect, const KisFilterConfigurationSP _config, KoUpdater* progressUpdater ) const { Q_ASSERT(device != 0); KisFilterConfigurationSP config = _config ? _config : new KisFilterConfiguration("colortoalpha", 1); QVariant value; QColor cTA = (config->getProperty("targetcolor", value)) ? value.value() : QColor(255, 255, 255); int threshold = (config->getProperty("threshold", value)) ? value.toInt() : 1; const KoColorSpace * cs = device->colorSpace(); KisSequentialIteratorProgress it(device, rect, progressUpdater); KoColor baseColor(cTA, cs); QVector channelIndex; KoChannelInfo::enumChannelValueType valueType = KoChannelInfo::OTHER; QList channels = cs->channels(); for (int i = 0; i < channels.size(); i++) { const KoChannelInfo *info = channels[i]; if (info->channelType() != KoChannelInfo::COLOR) continue; KoChannelInfo::enumChannelValueType currentValueType = info->channelValueType(); if (valueType != KoChannelInfo::OTHER && valueType != currentValueType) { warnKrita << "Cannot apply a Color-to-Alpha filter to a heterogeneous colorspace"; return; } else { valueType = currentValueType; } channelIndex.append(i); } switch (valueType) { case KoChannelInfo::UINT8: applyToIterator(channelIndex.size(), channelIndex.data(), it, baseColor, threshold, cs); break; case KoChannelInfo::UINT16: applyToIterator(channelIndex.size(), channelIndex.data(), it, baseColor, threshold, cs); break; case KoChannelInfo::UINT32: applyToIterator(channelIndex.size(), channelIndex.data(), it, baseColor, threshold, cs); break; case KoChannelInfo::FLOAT32: applyToIterator(channelIndex.size(), channelIndex.data(), it, baseColor, threshold, cs); break; case KoChannelInfo::FLOAT64: applyToIterator(channelIndex.size(), channelIndex.data(), it, baseColor, threshold, cs); break; case KoChannelInfo::FLOAT16: #ifdef HAVE_OPENEXR #include applyToIterator(channelIndex.size(), channelIndex.data(), it, baseColor, threshold, cs); break; #endif case KoChannelInfo::INT8: /* !UNSUPPORTED! */ case KoChannelInfo::INT16: /* !UNSUPPORTED! */ case KoChannelInfo::OTHER: warnKrita << "Color To Alpha: Unsupported channel type:" << valueType; } } diff --git a/plugins/filters/colors/kis_color_to_alpha.h b/plugins/filters/colors/kis_color_to_alpha.h index b313069669..5c71c8462d 100644 --- a/plugins/filters/colors/kis_color_to_alpha.h +++ b/plugins/filters/colors/kis_color_to_alpha.h @@ -1,49 +1,49 @@ /* * This file is part of Krita * * Copyright (c) 2006 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_COLOR_TO_ALPHA_H_ #define KIS_COLOR_TO_ALPHA_H_ #include "filter/kis_filter.h" class KisFilterColorToAlpha : public KisFilter { public: KisFilterColorToAlpha(); void processImpl(KisPaintDeviceSP device, const QRect& rect, const KisFilterConfigurationSP config, KoUpdater* progressUpdater ) const override; static inline KoID id() { return KoID("colortoalpha", i18n("Color to Alpha")); } public: KisConfigWidget * createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP dev, bool useForMasks) const override; - KisFilterConfigurationSP factoryConfiguration() const override; + KisFilterConfigurationSP defaultConfiguration() const override; }; #endif diff --git a/plugins/filters/colorsfilters/kis_color_balance_filter.cpp b/plugins/filters/colorsfilters/kis_color_balance_filter.cpp index 6a999bf2d9..8c392a4fc8 100644 --- a/plugins/filters/colorsfilters/kis_color_balance_filter.cpp +++ b/plugins/filters/colorsfilters/kis_color_balance_filter.cpp @@ -1,206 +1,206 @@ /* * Copyright (c) 2013 Sahil Nagpal * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_color_balance_filter.h" #include #include "filter/kis_color_transformation_configuration.h" #include "kis_selection.h" #include "kis_paint_device.h" #include "kis_processing_information.h" KisColorBalanceFilter::KisColorBalanceFilter() : KisColorTransformationFilter(id(), FiltersCategoryAdjustId, i18n("&Color Balance...")) { setShortcut(QKeySequence(Qt::CTRL + Qt::Key_B)); setSupportsPainting(true); } KisConfigWidget * KisColorBalanceFilter::createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP dev, bool) const { Q_UNUSED(dev); return new KisColorBalanceConfigWidget(parent); } KoColorTransformation * KisColorBalanceFilter::createTransformation(const KoColorSpace* cs, const KisFilterConfigurationSP config) const { QHash params; if (config) { params["cyan_red_midtones"] = config->getInt("cyan_red_midtones", 0) * 0.01; params["magenta_green_midtones"] = config->getInt("magenta_green_midtones", 0) * 0.01; params["yellow_blue_midtones"] = config->getInt("yellow_blue_midtones", 0) * 0.01; params["cyan_red_shadows"] = config->getInt("cyan_red_shadows", 0) * 0.01; params["magenta_green_shadows"] = config->getInt("magenta_green_shadows", 0) * 0.01; params["yellow_blue_shadows"] = config->getInt("yellow_blue_shadows", 0) * 0.01; params["cyan_red_highlights"] = config->getInt("cyan_red_highlights", 0) * 0.01; params["magenta_green_highlights"] = config->getInt("magenta_green_highlights", 0) * 0.01; params["yellow_blue_highlights"] = config->getInt("yellow_blue_highlights", 0) * 0.01; params["preserve_luminosity"] = config->getBool("preserve_luminosity", true); } return cs->createColorTransformation("ColorBalance" , params); } -KisFilterConfigurationSP KisColorBalanceFilter::factoryConfiguration() const +KisFilterConfigurationSP KisColorBalanceFilter::defaultConfiguration() const { - KisColorTransformationConfigurationSP config = new KisColorTransformationConfiguration(id().id(), 0); + KisFilterConfigurationSP config = factoryConfiguration(); config->setProperty("cyan_red_midtones", 0); config->setProperty("yellow_green_midtones", 0); config->setProperty("magenta_blue_midtones", 0); config->setProperty("cyan_red_shadows", 0); config->setProperty("yellow_green_shadows", 0); config->setProperty("magenta_blue_shadows", 0); config->setProperty("cyan_red_highlights", 0); config->setProperty("yellow_green_highlights", 0); config->setProperty("magenta_blue_highlights", 0); config->setProperty("preserve_luminosity", true); return config; } KisColorBalanceConfigWidget::KisColorBalanceConfigWidget(QWidget* parent) : KisConfigWidget(parent) { m_page = new Ui_Form(); m_page->setupUi(this); m_page->cyanRedShadowsSlider->setMaximum(100); m_page->cyanRedShadowsSlider->setMinimum(-100); m_page->yellowBlueShadowsSlider->setMaximum(100); m_page->yellowBlueShadowsSlider->setMinimum(-100); m_page->magentaGreenShadowsSlider->setMaximum(100); m_page->magentaGreenShadowsSlider->setMinimum(-100); m_page->cyanRedMidtonesSlider->setMaximum(100); m_page->cyanRedMidtonesSlider->setMinimum(-100); m_page->yellowBlueMidtonesSlider->setMaximum(100); m_page->yellowBlueMidtonesSlider->setMinimum(-100); m_page->magentaGreenMidtonesSlider->setMaximum(100); m_page->magentaGreenMidtonesSlider->setMinimum(-100); m_page->cyanRedHighlightsSlider->setMaximum(100); m_page->cyanRedHighlightsSlider->setMinimum(-100); m_page->yellowBlueHighlightsSlider->setMaximum(100); m_page->yellowBlueHighlightsSlider->setMinimum(-100); m_page->magentaGreenHighlightsSlider->setMaximum(100); m_page->magentaGreenHighlightsSlider->setMinimum(-100); connect(m_page->cyanRedShadowsSlider, SIGNAL(valueChanged(int)), SIGNAL(sigConfigurationItemChanged())); connect(m_page->magentaGreenShadowsSlider, SIGNAL(valueChanged(int)), SIGNAL(sigConfigurationItemChanged())); connect(m_page->yellowBlueShadowsSlider, SIGNAL(valueChanged(int)), SIGNAL(sigConfigurationItemChanged())); connect(m_page->cyanRedMidtonesSlider, SIGNAL(valueChanged(int)), SIGNAL(sigConfigurationItemChanged())); connect(m_page->magentaGreenMidtonesSlider, SIGNAL(valueChanged(int)), SIGNAL(sigConfigurationItemChanged())); connect(m_page->yellowBlueMidtonesSlider, SIGNAL(valueChanged(int)), SIGNAL(sigConfigurationItemChanged())); connect(m_page->cyanRedHighlightsSlider, SIGNAL(valueChanged(int)), SIGNAL(sigConfigurationItemChanged())); connect(m_page->magentaGreenHighlightsSlider, SIGNAL(valueChanged(int)), SIGNAL(sigConfigurationItemChanged())); connect(m_page->yellowBlueHighlightsSlider, SIGNAL(valueChanged(int)), SIGNAL(sigConfigurationItemChanged())); connect(m_page->chkPreserveLuminosity, SIGNAL(toggled(bool)), SIGNAL(sigConfigurationItemChanged())); connect(m_page->pushResetShadows, SIGNAL(clicked()), SLOT(slotShadowsClear())); connect(m_page->pushResetMidtones, SIGNAL(clicked()), SLOT(slotMidtonesClear())); connect(m_page->pushResetHighlights, SIGNAL(clicked()), SLOT(slotHighlightsClear())); m_page->cyanRedShadowsSpinbox->setMaximum(100); m_page->cyanRedShadowsSpinbox->setMinimum(-100); m_page->yellowBlueShadowsSpinbox->setMaximum(100); m_page->yellowBlueShadowsSpinbox->setMinimum(-100); m_page->magentaGreenShadowsSpinbox->setMaximum(100); m_page->magentaGreenShadowsSpinbox->setMinimum(-100); m_page->cyanRedMidtonesSpinbox->setMaximum(100); m_page->cyanRedMidtonesSpinbox->setMinimum(-100); m_page->yellowBlueMidtonesSpinbox->setMaximum(100); m_page->yellowBlueMidtonesSpinbox->setMinimum(-100); m_page->magentaGreenMidtonesSpinbox->setMaximum(100); m_page->magentaGreenMidtonesSpinbox->setMinimum(-100); m_page->cyanRedHighlightsSpinbox->setMaximum(100); m_page->cyanRedHighlightsSpinbox->setMinimum(-100); m_page->yellowBlueHighlightsSpinbox->setMaximum(100); m_page->yellowBlueHighlightsSpinbox->setMinimum(-100); m_page->magentaGreenHighlightsSpinbox->setMaximum(100); m_page->magentaGreenHighlightsSpinbox->setMinimum(-100); } KisColorBalanceConfigWidget::~KisColorBalanceConfigWidget() { delete m_page; } KisPropertiesConfigurationSP KisColorBalanceConfigWidget::configuration() const { KisColorTransformationConfigurationSP c = new KisColorTransformationConfiguration(KisColorBalanceFilter::id().id(), 0); c->setProperty("cyan_red_shadows", m_page->cyanRedShadowsSlider->value()); c->setProperty("magenta_green_shadows", m_page->magentaGreenShadowsSlider->value()); c->setProperty("yellow_blue_shadows", m_page->yellowBlueShadowsSlider->value()); c->setProperty("cyan_red_midtones", m_page->cyanRedMidtonesSlider->value()); c->setProperty("magenta_green_midtones", m_page->magentaGreenMidtonesSlider->value()); c->setProperty("yellow_blue_midtones", m_page->yellowBlueMidtonesSlider->value()); c->setProperty("cyan_red_highlights", m_page->cyanRedHighlightsSlider->value()); c->setProperty("magenta_green_highlights", m_page->magentaGreenHighlightsSlider->value()); c->setProperty("yellow_blue_highlights", m_page->yellowBlueHighlightsSlider->value()); c->setProperty("preserve_luminosity", m_page->chkPreserveLuminosity->isChecked()); return c; } void KisColorBalanceConfigWidget::setConfiguration(const KisPropertiesConfigurationSP config) { m_page->cyanRedMidtonesSlider->setValue( config->getDouble("cyan_red_midtones", 0)); m_page->magentaGreenMidtonesSlider->setValue( config->getDouble("magenta_green_midtones", 0)); m_page->yellowBlueMidtonesSlider->setValue( config->getDouble("yellow_blue_midtones", 0)); m_page->cyanRedShadowsSlider->setValue( config->getDouble("cyan_red_shadows", 0)); m_page->magentaGreenShadowsSlider->setValue( config->getDouble("magenta_green_shadows", 0)); m_page->yellowBlueShadowsSlider->setValue( config->getDouble("yellow_blue_shadows", 0)); m_page->cyanRedHighlightsSlider->setValue( config->getDouble("cyan_red_highlights", 0)); m_page->magentaGreenHighlightsSlider->setValue( config->getDouble("magenta_green_highlights", 0)); m_page->yellowBlueHighlightsSlider->setValue( config->getDouble("yellow_blue_highlights", 0)); m_page->chkPreserveLuminosity->setChecked(config->getBool("preserve_luminosity", true)); } void KisColorBalanceConfigWidget::slotMidtonesClear() { m_page->cyanRedMidtonesSlider->setValue(0); m_page->magentaGreenMidtonesSlider->setValue(0); m_page->yellowBlueMidtonesSlider->setValue(0); } void KisColorBalanceConfigWidget::slotHighlightsClear() { m_page->cyanRedHighlightsSlider->setValue(0); m_page->magentaGreenHighlightsSlider->setValue(0); m_page->yellowBlueHighlightsSlider->setValue(0); } void KisColorBalanceConfigWidget::slotShadowsClear() { m_page->cyanRedShadowsSlider->setValue(0); m_page->magentaGreenShadowsSlider->setValue(0); m_page->yellowBlueShadowsSlider->setValue(0); } diff --git a/plugins/filters/colorsfilters/kis_color_balance_filter.h b/plugins/filters/colorsfilters/kis_color_balance_filter.h index 21474cd5fb..490378c74d 100644 --- a/plugins/filters/colorsfilters/kis_color_balance_filter.h +++ b/plugins/filters/colorsfilters/kis_color_balance_filter.h @@ -1,80 +1,80 @@ /* * Copyright (c) 2013 Sahil Nagpal * * 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_COLOR_BALANCE_FILTER_H_ #define _KIS_COLOR_BALANCE_FILTER_H_ #include #include "filter/kis_filter.h" #include "kis_config_widget.h" #include "ui_wdg_color_balance.h" #include "filter/kis_color_transformation_filter.h" class QWidget; class KoColorTransformation; class KisColorBalanceFilter : public KisColorTransformationFilter { public: KisColorBalanceFilter(); public: enum Type { SHADOWS, MIDTONES, HIGHLIGHTS }; public: KisConfigWidget * createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP dev, bool useForMasks) const override; KoColorTransformation* createTransformation(const KoColorSpace* cs, const KisFilterConfigurationSP config) const override; static inline KoID id() { return KoID("colorbalance", i18n("Color Balance")); } - KisFilterConfigurationSP factoryConfiguration() const override; + KisFilterConfigurationSP defaultConfiguration() const override; }; class KisColorBalanceConfigWidget : public KisConfigWidget { Q_OBJECT public: KisColorBalanceConfigWidget(QWidget * parent); ~KisColorBalanceConfigWidget() override; KisPropertiesConfigurationSP configuration() const override; void setConfiguration(const KisPropertiesConfigurationSP config) override; Ui_Form * m_page; QString m_id; public Q_SLOTS: void slotShadowsClear(); void slotMidtonesClear(); void slotHighlightsClear(); }; #endif diff --git a/plugins/filters/colorsfilters/kis_desaturate_filter.cpp b/plugins/filters/colorsfilters/kis_desaturate_filter.cpp index e910c3e988..d703886a33 100644 --- a/plugins/filters/colorsfilters/kis_desaturate_filter.cpp +++ b/plugins/filters/colorsfilters/kis_desaturate_filter.cpp @@ -1,123 +1,123 @@ /* * This file is part of Krita * * Copyright (c) 2004 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_desaturate_filter.h" #include #include #include #include #include #include #include #include #include #include #include "KoBasicHistogramProducers.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "filter/kis_filter_registry.h" #include #include #include #include KisDesaturateFilter::KisDesaturateFilter() : KisColorTransformationFilter(id(), FiltersCategoryAdjustId, i18n("&Desaturate...")) { setShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_U)); setSupportsPainting(true); } KisDesaturateFilter::~KisDesaturateFilter() { } KisConfigWidget *KisDesaturateFilter::createConfigurationWidget(QWidget *parent, const KisPaintDeviceSP dev, bool) const { Q_UNUSED(dev); return new KisDesaturateConfigWidget(parent); } KoColorTransformation* KisDesaturateFilter::createTransformation(const KoColorSpace* cs, const KisFilterConfigurationSP config) const { QHash params; if (config) { params["type"] = config->getInt("type", 0); } return cs->createColorTransformation("desaturate_adjustment", params); } -KisFilterConfigurationSP KisDesaturateFilter::factoryConfiguration() const +KisFilterConfigurationSP KisDesaturateFilter::defaultConfiguration() const { - KisColorTransformationConfigurationSP config = new KisColorTransformationConfiguration(id().id(), 1); + KisFilterConfigurationSP config = factoryConfiguration(); config->setProperty("type", 0); return config; } KisDesaturateConfigWidget::KisDesaturateConfigWidget(QWidget * parent, Qt::WindowFlags f) : KisConfigWidget(parent, f) { m_page = new Ui_WdgDesaturate(); m_page->setupUi(this); m_group = new QButtonGroup(this); m_group->addButton(m_page->radioLightness, 0); m_group->addButton(m_page->radioLuminosityBT709, 1); m_group->addButton(m_page->radioLuminosityBT601, 2); m_group->addButton(m_page->radioAverage, 3); m_group->addButton(m_page->radioMin, 4); m_group->addButton(m_page->radioMax, 5); m_group->setExclusive(true); connect(m_group, SIGNAL(buttonClicked(int)), SIGNAL(sigConfigurationItemChanged())); } KisDesaturateConfigWidget::~KisDesaturateConfigWidget() { delete m_page; } KisPropertiesConfigurationSP KisDesaturateConfigWidget::configuration() const { KisColorTransformationConfigurationSP c = new KisColorTransformationConfiguration(KisDesaturateFilter::id().id(), 0); c->setProperty("type", m_group->checkedId()); return c; } void KisDesaturateConfigWidget::setConfiguration(const KisPropertiesConfigurationSP config) { m_group->button(config->getInt("type", 0))->setChecked(true); emit sigConfigurationItemChanged(); } diff --git a/plugins/filters/colorsfilters/kis_desaturate_filter.h b/plugins/filters/colorsfilters/kis_desaturate_filter.h index 9ab8a3626d..b97f90ee94 100644 --- a/plugins/filters/colorsfilters/kis_desaturate_filter.h +++ b/plugins/filters/colorsfilters/kis_desaturate_filter.h @@ -1,70 +1,70 @@ /* * This file is part of Krita * * Copyright (c) 2004 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_DESATURATE_FILTER_H #define KIS_DESATURATE_FILTER_H #include #include #include #include #include "ui_wdg_desaturate.h" class KoColorSpace; class KoColorTransformation; class KisDesaturateFilter : public KisColorTransformationFilter { public: KisDesaturateFilter(); ~KisDesaturateFilter() override; public: KisConfigWidget * createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP dev, bool useForMasks) const override; KoColorTransformation* createTransformation(const KoColorSpace* cs, const KisFilterConfigurationSP config) const override; static inline KoID id() { return KoID("desaturate", i18n("Desaturate")); } - KisFilterConfigurationSP factoryConfiguration() const override; + KisFilterConfigurationSP defaultConfiguration() const override; }; class KisDesaturateConfigWidget : public KisConfigWidget { Q_OBJECT public: KisDesaturateConfigWidget(QWidget * parent, Qt::WindowFlags f = 0); ~KisDesaturateConfigWidget() override; KisPropertiesConfigurationSP configuration() const override; void setConfiguration(const KisPropertiesConfigurationSP config) override; Ui_WdgDesaturate *m_page; QButtonGroup *m_group; }; #endif diff --git a/plugins/filters/colorsfilters/kis_hsv_adjustment_filter.cpp b/plugins/filters/colorsfilters/kis_hsv_adjustment_filter.cpp index 4cc5d12ac2..2622af8d5f 100644 --- a/plugins/filters/colorsfilters/kis_hsv_adjustment_filter.cpp +++ b/plugins/filters/colorsfilters/kis_hsv_adjustment_filter.cpp @@ -1,208 +1,221 @@ /* * Copyright (c) 2007 Cyrille Berger * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; version 2 * of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_hsv_adjustment_filter.h" #include #include #include #include #include #include namespace { struct SliderConfig { QString m_text; int m_minimum; int m_maximum; inline void apply(QSpinBox* spinBox, QSlider* slider, QLabel* label) const { label->setText(m_text); slider->setMinimum(m_minimum); slider->setMaximum(m_maximum); spinBox->setMinimum(m_minimum); spinBox->setMaximum(m_maximum); int sliderValue = slider->value(); if (sliderValue < m_minimum || sliderValue > m_maximum) { slider->setValue((m_minimum + m_maximum) / 2); } } inline double normalize(int value) const { return (double)value / (double)m_maximum; } inline void resetSlider( QSlider* slider) const { slider->setValue(0); } }; struct WidgetSlidersConfig { SliderConfig m_sliders[3]; }; #define PERCENT_FIELD_REL(x) {x, -100, 100} #define PERCENT_FIELD_ABS(x) {x, 0, 100} #define DEGREES_FIELD_REL(x) {x, -180, 180} #define DEGREES_FIELD_ABS(x) {x, 0, 360} #define HSX_CONFIGS(x) { \ { {DEGREES_FIELD_REL(i18n("Hue:")), PERCENT_FIELD_REL(i18n("Saturation:")), PERCENT_FIELD_REL(x)} }, \ { {DEGREES_FIELD_ABS(i18n("Hue:")), PERCENT_FIELD_ABS(i18n("Saturation:")), PERCENT_FIELD_REL(x)} } \ } const WidgetSlidersConfig WIDGET_CONFIGS[][2] = { // Hue/Saturation/Value HSX_CONFIGS(i18n("Value:")), // Hue/Saturation/Lightness HSX_CONFIGS(i18n("Lightness:")), // Hue/Saturation/Intensity HSX_CONFIGS(i18n("Intensity:")), // Hue/Saturation/Luminosity HSX_CONFIGS(i18n("Luma:")), // Blue Chroma/Red Chroma/Luma {{ {PERCENT_FIELD_REL(i18n("Yellow-Blue:")), PERCENT_FIELD_REL(i18n("Green-Red:")), PERCENT_FIELD_REL(i18n("Luma:"))} }, { {PERCENT_FIELD_ABS(i18n("Yellow-Blue:")), PERCENT_FIELD_ABS(i18n("Green-Red:")), PERCENT_FIELD_REL(i18n("Luma:"))} }} }; inline const WidgetSlidersConfig& getCurrentWidgetConfig(int type, bool colorize) { return WIDGET_CONFIGS[type][colorize ? 1 : 0]; } } KisHSVAdjustmentFilter::KisHSVAdjustmentFilter() : KisColorTransformationFilter(id(), FiltersCategoryAdjustId, i18n("&HSV Adjustment...")) { setShortcut(QKeySequence(Qt::CTRL + Qt::Key_U)); setSupportsPainting(true); } KisConfigWidget * KisHSVAdjustmentFilter::createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP dev, bool) const { Q_UNUSED(dev); return new KisHSVConfigWidget(parent); } KoColorTransformation* KisHSVAdjustmentFilter::createTransformation(const KoColorSpace* cs, const KisFilterConfigurationSP config) const { QHash params; if (config) { int type = config->getInt("type", 1); bool colorize = config->getBool("colorize", false); + bool compatibilityMode = config->getBool("compatibilityMode", true); const WidgetSlidersConfig& widgetConfig = getCurrentWidgetConfig(type, colorize); params["h"] = widgetConfig.m_sliders[0].normalize(config->getInt("h", 0)); params["s"] = widgetConfig.m_sliders[1].normalize(config->getInt("s", 0)); params["v"] = widgetConfig.m_sliders[2].normalize(config->getInt("v", 0)); params["type"] = type; params["colorize"] = colorize; params["lumaRed"] = cs->lumaCoefficients()[0]; params["lumaGreen"] = cs->lumaCoefficients()[1]; params["lumaBlue"] = cs->lumaCoefficients()[2]; + params["compatibilityMode"] = compatibilityMode; } return cs->createColorTransformation("hsv_adjustment", params); } -KisFilterConfigurationSP KisHSVAdjustmentFilter::factoryConfiguration() const +KisFilterConfigurationSP KisHSVAdjustmentFilter::defaultConfiguration() const { - KisColorTransformationConfigurationSP config = new KisColorTransformationConfiguration(id().id(), 1); + KisFilterConfigurationSP config = factoryConfiguration(); config->setProperty("h", 0); config->setProperty("s", 0); config->setProperty("v", 0); config->setProperty("type", 1); config->setProperty("colorize", false); + config->setProperty("compatibilityMode", false); return config; } KisHSVConfigWidget::KisHSVConfigWidget(QWidget * parent, Qt::WindowFlags f) : KisConfigWidget(parent, f) { m_page = new Ui_WdgHSVAdjustment(); m_page->setupUi(this); connect(m_page->cmbType, SIGNAL(activated(int)), this, SLOT(configureSliderLimitsAndLabels())); connect(m_page->chkColorize, SIGNAL(toggled(bool)), this, SLOT(configureSliderLimitsAndLabels())); + connect(m_page->chkCompatibilityMode, SIGNAL(toggled(bool)), SIGNAL(sigConfigurationItemChanged())); + connect(m_page->reset,SIGNAL(clicked(bool)),this,SLOT(resetFilter())); // connect horizontal sliders connect(m_page->hueSlider, SIGNAL(valueChanged(int)), SIGNAL(sigConfigurationItemChanged())); connect(m_page->saturationSlider, SIGNAL(valueChanged(int)), SIGNAL(sigConfigurationItemChanged())); connect(m_page->valueSlider, SIGNAL(valueChanged(int)), SIGNAL(sigConfigurationItemChanged())); connect(m_page->hueSpinBox, SIGNAL(valueChanged(int)), m_page->hueSlider, SLOT(setValue(int))); connect(m_page->saturationSpinBox, SIGNAL(valueChanged(int)), m_page->saturationSlider, SLOT(setValue(int))); connect(m_page->valueSpinBox, SIGNAL(valueChanged(int)), m_page->valueSlider, SLOT(setValue(int))); connect(m_page->hueSlider, SIGNAL(valueChanged(int)), m_page->hueSpinBox, SLOT(setValue(int))); connect(m_page->saturationSlider, SIGNAL(valueChanged(int)), m_page->saturationSpinBox, SLOT(setValue(int))); connect(m_page->valueSlider, SIGNAL(valueChanged(int)), m_page->valueSpinBox, SLOT(setValue(int))); } KisHSVConfigWidget::~KisHSVConfigWidget() { delete m_page; } KisPropertiesConfigurationSP KisHSVConfigWidget::configuration() const { KisColorTransformationConfigurationSP c = new KisColorTransformationConfiguration(KisHSVAdjustmentFilter::id().id(), 0); c->setProperty("h", m_page->hueSlider->value()); c->setProperty("s", m_page->saturationSlider->value()); c->setProperty("v", m_page->valueSlider->value()); c->setProperty("type", m_page->cmbType->currentIndex()); c->setProperty("colorize", m_page->chkColorize->isChecked()); + c->setProperty("compatibilityMode", m_page->chkCompatibilityMode->isChecked()); return c; } void KisHSVConfigWidget::setConfiguration(const KisPropertiesConfigurationSP config) { m_page->cmbType->setCurrentIndex(config->getInt("type", 1)); m_page->chkColorize->setChecked(config->getBool("colorize", false)); m_page->hueSlider->setValue(config->getInt("h", 0)); m_page->saturationSlider->setValue(config->getInt("s", 0)); m_page->valueSlider->setValue(config->getInt("v", 0)); + m_page->chkCompatibilityMode->setChecked(config->getInt("compatibilityMode", true)); configureSliderLimitsAndLabels(); } void KisHSVConfigWidget::configureSliderLimitsAndLabels() { const WidgetSlidersConfig& widget = getCurrentWidgetConfig(m_page->cmbType->currentIndex(), m_page->chkColorize->isChecked()); widget.m_sliders[0].apply(m_page->hueSpinBox, m_page->hueSlider, m_page->label); widget.m_sliders[1].apply(m_page->saturationSpinBox, m_page->saturationSlider, m_page->label_2); widget.m_sliders[2].apply(m_page->valueSpinBox, m_page->valueSlider, m_page->label_3); + const bool compatibilityEnabled = + !m_page->chkColorize->isChecked() && + m_page->cmbType->currentIndex() >= 0 && m_page->cmbType->currentIndex() <= 3; + + m_page->chkCompatibilityMode->setEnabled(compatibilityEnabled); + emit sigConfigurationItemChanged(); } void KisHSVConfigWidget::resetFilter() { const WidgetSlidersConfig& widget = getCurrentWidgetConfig(m_page->cmbType->currentIndex(), m_page->chkColorize->isChecked()); widget.m_sliders[0].resetSlider(m_page->hueSlider); widget.m_sliders[1].resetSlider(m_page->saturationSlider); widget.m_sliders[2].resetSlider(m_page->valueSlider); } diff --git a/plugins/filters/colorsfilters/kis_hsv_adjustment_filter.h b/plugins/filters/colorsfilters/kis_hsv_adjustment_filter.h index 2b19fb8753..333df12b2d 100644 --- a/plugins/filters/colorsfilters/kis_hsv_adjustment_filter.h +++ b/plugins/filters/colorsfilters/kis_hsv_adjustment_filter.h @@ -1,78 +1,78 @@ /* * Copyright (c) 2007 Cyrille Berger * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; version 2 * of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this 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_FILTER_H_ #define _KIS_HSV_ADJUSTMENT_FILTER_H_ #include #include "filter/kis_filter.h" #include "kis_config_widget.h" #include "ui_wdg_hsv_adjustment.h" #include "filter/kis_color_transformation_filter.h" class QWidget; class KoColorTransformation; /** * This class affect Intensity Y of the image */ class KisHSVAdjustmentFilter : public KisColorTransformationFilter { public: KisHSVAdjustmentFilter(); public: KisConfigWidget * createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP dev, bool useForMasks) const override; KoColorTransformation* createTransformation(const KoColorSpace* cs, const KisFilterConfigurationSP config) const override; static inline KoID id() { return KoID("hsvadjustment", i18n("HSV/HSL Adjustment")); } - KisFilterConfigurationSP factoryConfiguration() const override; + KisFilterConfigurationSP defaultConfiguration() const override; }; class KisHSVConfigWidget : public KisConfigWidget { Q_OBJECT public: KisHSVConfigWidget(QWidget * parent, Qt::WindowFlags f = 0); ~KisHSVConfigWidget() override; KisPropertiesConfigurationSP configuration() const override; void setConfiguration(const KisPropertiesConfigurationSP config) override; Ui_WdgHSVAdjustment * m_page; private Q_SLOTS: void configureSliderLimitsAndLabels(); void resetFilter(); }; #endif diff --git a/plugins/filters/colorsfilters/wdg_hsv_adjustment.ui b/plugins/filters/colorsfilters/wdg_hsv_adjustment.ui index d88926d9ba..7f628c8b79 100644 --- a/plugins/filters/colorsfilters/wdg_hsv_adjustment.ui +++ b/plugins/filters/colorsfilters/wdg_hsv_adjustment.ui @@ -1,254 +1,261 @@ WdgHSVAdjustment 0 0 412 188 0 0 0 0 5 Qt::Vertical 20 0 &Type: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter cmbType &Colorize 0 Hue/Saturation/Value Hue/Saturation/Lightness Hue/Saturation/Intensity Hue/Saturation/Luma Blue Chroma/Red Chroma/Luma -180 180 Qt::Horizontal -100 100 &Value: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter valueSlider -100 100 0 true Qt::Horizontal false false QSlider::NoTicks 0 0 &Saturation: false Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter saturationSlider 0 0 &Hue: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter hueSlider -100 100 Qt::Horizontal -100 100 -180 180 Reset + + + + Legacy mode (before Krita 4.3) + + + KisIntParseSpinBox QSpinBox
kis_int_parse_spin_box.h
cmbType chkColorize
diff --git a/plugins/filters/convertheightnormalmap/kis_convert_height_to_normal_map_filter.cpp b/plugins/filters/convertheightnormalmap/kis_convert_height_to_normal_map_filter.cpp index df19b0c773..ff82aea2f9 100644 --- a/plugins/filters/convertheightnormalmap/kis_convert_height_to_normal_map_filter.cpp +++ b/plugins/filters/convertheightnormalmap/kis_convert_height_to_normal_map_filter.cpp @@ -1,171 +1,171 @@ /* * 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. */ #include "kis_convert_height_to_normal_map_filter.h" #include "kis_wdg_convert_height_to_normal_map.h" #include #include #include #include #include #include "kis_lod_transform.h" #include K_PLUGIN_FACTORY_WITH_JSON(KritaConvertHeightToNormalMapFilterFactory, "kritaconvertheighttonormalmap.json", registerPlugin();) KritaConvertHeightToNormalMapFilter::KritaConvertHeightToNormalMapFilter(QObject *parent, const QVariantList &) : QObject(parent) { KisFilterRegistry::instance()->add(KisFilterSP(new KisConvertHeightToNormalMapFilter())); } KritaConvertHeightToNormalMapFilter::~KritaConvertHeightToNormalMapFilter() { } KisConvertHeightToNormalMapFilter::KisConvertHeightToNormalMapFilter(): KisFilter(id(), FiltersCategoryEdgeDetectionId, i18n("&Height to Normal Map...")) { setSupportsPainting(true); setSupportsAdjustmentLayers(true); setSupportsLevelOfDetail(true); setColorSpaceIndependence(FULLY_INDEPENDENT); setShowConfigurationWidget(true); } void KisConvertHeightToNormalMapFilter::processImpl(KisPaintDeviceSP device, const QRect &rect, const KisFilterConfigurationSP config, KoUpdater *progressUpdater) const { Q_ASSERT(device); KisFilterConfigurationSP configuration = config ? config : new KisFilterConfiguration(id().id(), 1); KisLodTransformScalar t(device); QVariant value; float horizontalRadius = 1.0; if (configuration->getProperty("horizRadius", value)) { horizontalRadius = t.scale(value.toFloat()); } float verticalRadius = 1.0; if (configuration->getProperty("vertRadius", value)) { verticalRadius = t.scale(value.toFloat()); } QBitArray channelFlags; if (configuration) { channelFlags = configuration->channelFlags(); } KisEdgeDetectionKernel::FilterType type = KisEdgeDetectionKernel::SobelVector; if (configuration->getString("type") == "prewitt") { type = KisEdgeDetectionKernel::Prewit; } else if (configuration->getString("type") == "simple") { type = KisEdgeDetectionKernel::Simple; } int channelToConvert = configuration->getInt("channelToConvert", 0); QVector channelOrder(3); QVector channelFlip(3); channelFlip.fill(false); int i = config->getInt("redSwizzle", 0); if (i%2==1 || i==2) { channelFlip[0] = true; } if (i==3) { channelFlip[0] = false; } channelOrder[device->colorSpace()->channels().at(0)->displayPosition()] = qMax(i/2,0); i = config->getInt("greenSwizzle", 2); if (i%2==1 || i==2) { channelFlip[1] = true; } if (i==3) { channelFlip[1] = false; } channelOrder[device->colorSpace()->channels().at(1)->displayPosition()] = qMax(i/2,0); i = config->getInt("blueSwizzle", 4); if (i%2==1 || i==2) { channelFlip[2] = true; } if (i==3) { channelFlip[2] = false; } channelOrder[device->colorSpace()->channels().at(2)->displayPosition()] = qMax(i/2,0); KisEdgeDetectionKernel::convertToNormalMap(device, rect, horizontalRadius, verticalRadius, type, channelToConvert, channelOrder, channelFlip, channelFlags, progressUpdater); } -KisFilterConfigurationSP KisConvertHeightToNormalMapFilter::factoryConfiguration() const +KisFilterConfigurationSP KisConvertHeightToNormalMapFilter::defaultConfiguration() const { - KisFilterConfigurationSP config = new KisFilterConfiguration(id().id(), 1); + KisFilterConfigurationSP config = factoryConfiguration(); config->setProperty("horizRadius", 1); config->setProperty("vertRadius", 1); config->setProperty("type", "sobol"); config->setProperty("channelToConvert", 0); config->setProperty("lockAspect", true); config->setProperty("redSwizzle", KisWdgConvertHeightToNormalMap::xPlus); config->setProperty("greenSwizzle", KisWdgConvertHeightToNormalMap::yPlus); config->setProperty("blueSwizzle", KisWdgConvertHeightToNormalMap::zPlus); return config; } KisConfigWidget *KisConvertHeightToNormalMapFilter::createConfigurationWidget(QWidget *parent, const KisPaintDeviceSP dev, bool) const { return new KisWdgConvertHeightToNormalMap(parent, dev->colorSpace()); } QRect KisConvertHeightToNormalMapFilter::neededRect(const QRect &rect, const KisFilterConfigurationSP _config, int lod) const { KisLodTransformScalar t(lod); QVariant value; /** * NOTE: integer division by two is done on purpose, * because the kernel size is always odd */ const int halfWidth = _config->getProperty("horizRadius", value) ? KisEdgeDetectionKernel::kernelSizeFromRadius(t.scale(value.toFloat())) / 2 : 5; const int halfHeight = _config->getProperty("vertRadius", value) ? KisEdgeDetectionKernel::kernelSizeFromRadius(t.scale(value.toFloat())) / 2 : 5; return rect.adjusted(-halfWidth * 2, -halfHeight * 2, halfWidth * 2, halfHeight * 2); } QRect KisConvertHeightToNormalMapFilter::changedRect(const QRect &rect, const KisFilterConfigurationSP _config, int lod) const { KisLodTransformScalar t(lod); QVariant value; const int halfWidth = _config->getProperty("horizRadius", value) ? KisEdgeDetectionKernel::kernelSizeFromRadius(t.scale(value.toFloat())) / 2 : 5; const int halfHeight = _config->getProperty("vertRadius", value) ? KisEdgeDetectionKernel::kernelSizeFromRadius(t.scale(value.toFloat())) / 2 : 5; return rect.adjusted( -halfWidth, -halfHeight, halfWidth, halfHeight); } #include "kis_convert_height_to_normal_map_filter.moc" diff --git a/plugins/filters/convertheightnormalmap/kis_convert_height_to_normal_map_filter.h b/plugins/filters/convertheightnormalmap/kis_convert_height_to_normal_map_filter.h index 203e0a0b05..64a24a937d 100644 --- a/plugins/filters/convertheightnormalmap/kis_convert_height_to_normal_map_filter.h +++ b/plugins/filters/convertheightnormalmap/kis_convert_height_to_normal_map_filter.h @@ -1,52 +1,52 @@ /* * 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_CONVERT_HEIGHT_TO_NORMAL_MAP_FILTER_H #define KIS_CONVERT_HEIGHT_TO_NORMAL_MAP_FILTER_H #include "filter/kis_filter.h" class KritaConvertHeightToNormalMapFilter : public QObject { Q_OBJECT public: KritaConvertHeightToNormalMapFilter(QObject *parent, const QVariantList &); ~KritaConvertHeightToNormalMapFilter() override; }; class KisConvertHeightToNormalMapFilter : public KisFilter { public: KisConvertHeightToNormalMapFilter(); void processImpl(KisPaintDeviceSP device, const QRect& rect, const KisFilterConfigurationSP config, KoUpdater* progressUpdater ) const override; static inline KoID id() { return KoID("height to normal", i18n("Height to Normal Map")); } - KisFilterConfigurationSP factoryConfiguration() const override; + KisFilterConfigurationSP defaultConfiguration() const override; public: KisConfigWidget * createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP dev, bool useForMasks) const override; QRect neededRect(const QRect & rect, const KisFilterConfigurationSP _config, int lod) const override; QRect changedRect(const QRect & rect, const KisFilterConfigurationSP _config, int lod) const override; }; #endif // KIS_CONVERT_HEIGHT_TO_NORMAL_MAP_FILTER_H diff --git a/plugins/filters/convolutionfilters/kis_convolution_filter.cpp b/plugins/filters/convolutionfilters/kis_convolution_filter.cpp index 23080fe6ea..d91e293303 100644 --- a/plugins/filters/convolutionfilters/kis_convolution_filter.cpp +++ b/plugins/filters/convolutionfilters/kis_convolution_filter.cpp @@ -1,71 +1,89 @@ /* * This file is part of the KDE project * * Copyright (c) 2004 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_convolution_filter.h" #include #include #include #include "kis_painter.h" #include "kis_convolution_painter.h" #include "kis_convolution_kernel.h" #include #include #include #include +#include "kis_lod_transform.h" KisConvolutionFilter::KisConvolutionFilter(const KoID& id, const KoID & category, const QString & entry) : KisFilter(id, category, entry) { setColorSpaceIndependence(FULLY_INDEPENDENT); + setSupportsLevelOfDetail(true); } void KisConvolutionFilter::processImpl(KisPaintDeviceSP device, const QRect& applyRect, const KisFilterConfigurationSP config, KoUpdater* progressUpdater) const { Q_UNUSED(config); QPoint srcTopLeft = applyRect.topLeft(); Q_ASSERT(device != 0); KisConvolutionPainter painter(device); QBitArray channelFlags; if (config) { channelFlags = config->channelFlags(); } if (channelFlags.isEmpty() || !config) { channelFlags = QBitArray(device->colorSpace()->channelCount(), true); } painter.setChannelFlags(channelFlags); painter.setProgress(progressUpdater); painter.applyMatrix(m_matrix, device, srcTopLeft, srcTopLeft, applyRect.size(), BORDER_REPEAT); } +QRect KisConvolutionFilter::neededRect(const QRect &rect, const KisFilterConfigurationSP _config, int lod) const +{ + Q_UNUSED(_config); + + KisLodTransformScalar t(lod); + + const int windowsize = qMax(m_matrix->width(), m_matrix->height()); + const int margin = qCeil(t.scale(0.5 * windowsize)) + 1; + return kisGrowRect(rect, margin); +} + +QRect KisConvolutionFilter::changedRect(const QRect &rect, const KisFilterConfigurationSP _config, int lod) const +{ + return neededRect(rect, _config, lod); +} + void KisConvolutionFilter::setIgnoreAlpha(bool v) { m_ignoreAlpha = v; } diff --git a/plugins/filters/convolutionfilters/kis_convolution_filter.h b/plugins/filters/convolutionfilters/kis_convolution_filter.h index ab7cdb45d9..5026b22497 100644 --- a/plugins/filters/convolutionfilters/kis_convolution_filter.h +++ b/plugins/filters/convolutionfilters/kis_convolution_filter.h @@ -1,47 +1,51 @@ /* * This file is part of Krita * * Copyright (c) 2004 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_CONVOLUTION_FILTER_H_ #define _KIS_CONVOLUTION_FILTER_H_ #include "filter/kis_filter.h" #include "filter/kis_filter_registry.h" #include "filter/kis_filter_configuration.h" #include "kis_convolution_painter.h" class KisConvolutionFilter : public KisFilter { public: KisConvolutionFilter(const KoID& id, const KoID & category, const QString & entry); public: void processImpl(KisPaintDeviceSP device, const QRect& applyRect, const KisFilterConfigurationSP config, KoUpdater* progressUpdater) const override; + + QRect neededRect(const QRect & rect, const KisFilterConfigurationSP _config, int lod) const override; + QRect changedRect(const QRect & rect, const KisFilterConfigurationSP _config, int lod) const override; + protected: void setIgnoreAlpha(bool v); protected: KisConvolutionKernelSP m_matrix; bool m_ignoreAlpha; }; #endif diff --git a/plugins/filters/edgedetection/kis_edge_detection_filter.cpp b/plugins/filters/edgedetection/kis_edge_detection_filter.cpp index 3826164ae0..0564f73efc 100644 --- a/plugins/filters/edgedetection/kis_edge_detection_filter.cpp +++ b/plugins/filters/edgedetection/kis_edge_detection_filter.cpp @@ -1,156 +1,156 @@ /* * 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. */ #include "kis_edge_detection_filter.h" #include "kis_wdg_edge_detection.h" #include #include #include #include #include #include #include #include #include #include #include "kis_lod_transform.h" #include #include #include K_PLUGIN_FACTORY_WITH_JSON(KritaEdgeDetectionFilterFactory, "kritaedgedetection.json", registerPlugin();) KritaEdgeDetectionFilter::KritaEdgeDetectionFilter(QObject *parent, const QVariantList &) : QObject(parent) { KisFilterRegistry::instance()->add(KisFilterSP(new KisEdgeDetectionFilter())); } KritaEdgeDetectionFilter::~KritaEdgeDetectionFilter() { } KisEdgeDetectionFilter::KisEdgeDetectionFilter(): KisFilter(id(), FiltersCategoryEdgeDetectionId, i18n("&Edge Detection...")) { setSupportsPainting(true); setSupportsAdjustmentLayers(true); setSupportsLevelOfDetail(true); setColorSpaceIndependence(FULLY_INDEPENDENT); setShowConfigurationWidget(true); } void KisEdgeDetectionFilter::processImpl(KisPaintDeviceSP device, const QRect &rect, const KisFilterConfigurationSP config, KoUpdater *progressUpdater) const { Q_ASSERT(device != 0); KisFilterConfigurationSP configuration = config ? config : new KisFilterConfiguration(id().id(), 1); KisLodTransformScalar t(device); QVariant value; configuration->getProperty("horizRadius", value); float horizontalRadius = t.scale(value.toFloat()); configuration->getProperty("vertRadius", value); float verticalRadius = t.scale(value.toFloat()); QBitArray channelFlags; if (configuration) { channelFlags = configuration->channelFlags(); } KisEdgeDetectionKernel::FilterType type = KisEdgeDetectionKernel::SobelVector; if (config->getString("type") == "prewitt") { type = KisEdgeDetectionKernel::Prewit; } else if (config->getString("type") == "simple") { type = KisEdgeDetectionKernel::Simple; } KisEdgeDetectionKernel::FilterOutput output = KisEdgeDetectionKernel::pythagorean; if (config->getString("output") == "xGrowth") { output = KisEdgeDetectionKernel::xGrowth; } else if (config->getString("output") == "xFall") { output = KisEdgeDetectionKernel::xFall; } else if (config->getString("output") == "yGrowth") { output = KisEdgeDetectionKernel::yGrowth; } else if (config->getString("output") == "yFall") { output = KisEdgeDetectionKernel::yFall; } else if (config->getString("output") == "radian") { output = KisEdgeDetectionKernel::radian; } KisEdgeDetectionKernel::applyEdgeDetection(device, rect, horizontalRadius, verticalRadius, type, channelFlags, progressUpdater, output, config->getBool("transparency", false)); } -KisFilterConfigurationSP KisEdgeDetectionFilter::factoryConfiguration() const +KisFilterConfigurationSP KisEdgeDetectionFilter::defaultConfiguration() const { - KisFilterConfigurationSP config = new KisFilterConfiguration(id().id(), 1); + KisFilterConfigurationSP config = factoryConfiguration(); config->setProperty("horizRadius", 1); config->setProperty("vertRadius", 1); config->setProperty("type", "prewitt"); config->setProperty("output", "pythagorean"); config->setProperty("lockAspect", true); config->setProperty("transparency", false); return config; } KisConfigWidget *KisEdgeDetectionFilter::createConfigurationWidget(QWidget *parent, const KisPaintDeviceSP dev, bool) const { Q_UNUSED(dev); return new KisWdgEdgeDetection(parent); } QRect KisEdgeDetectionFilter::neededRect(const QRect &rect, const KisFilterConfigurationSP _config, int lod) const { KisLodTransformScalar t(lod); QVariant value; /** * NOTE: integer division by two is done on purpose, * because the kernel size is always odd */ const int halfWidth = _config->getProperty("horizRadius", value) ? KisEdgeDetectionKernel::kernelSizeFromRadius(t.scale(value.toFloat())) / 2 : 5; const int halfHeight = _config->getProperty("vertRadius", value) ? KisEdgeDetectionKernel::kernelSizeFromRadius(t.scale(value.toFloat())) / 2 : 5; return rect.adjusted(-halfWidth * 2, -halfHeight * 2, halfWidth * 2, halfHeight * 2); } QRect KisEdgeDetectionFilter::changedRect(const QRect &rect, const KisFilterConfigurationSP _config, int lod) const { KisLodTransformScalar t(lod); QVariant value; const int halfWidth = _config->getProperty("horizRadius", value) ? KisEdgeDetectionKernel::kernelSizeFromRadius(t.scale(value.toFloat())) / 2 : 5; const int halfHeight = _config->getProperty("vertRadius", value) ? KisEdgeDetectionKernel::kernelSizeFromRadius(t.scale(value.toFloat())) / 2 : 5; return rect.adjusted( -halfWidth, -halfHeight, halfWidth, halfHeight); } #include "kis_edge_detection_filter.moc" diff --git a/plugins/filters/edgedetection/kis_edge_detection_filter.h b/plugins/filters/edgedetection/kis_edge_detection_filter.h index a9fc3c8255..9bde8cc5dd 100644 --- a/plugins/filters/edgedetection/kis_edge_detection_filter.h +++ b/plugins/filters/edgedetection/kis_edge_detection_filter.h @@ -1,53 +1,53 @@ /* * 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_FILTER_H #define KIS_EDGE_DETECTION_FILTER_H #include "filter/kis_filter.h" #include class KritaEdgeDetectionFilter : public QObject { Q_OBJECT public: KritaEdgeDetectionFilter(QObject *parent, const QVariantList &); ~KritaEdgeDetectionFilter() override; }; class KisEdgeDetectionFilter : public KisFilter { public: KisEdgeDetectionFilter(); void processImpl(KisPaintDeviceSP device, const QRect& rect, const KisFilterConfigurationSP config, KoUpdater* progressUpdater ) const override; static inline KoID id() { return KoID("edge detection", i18n("Edge Detection")); } - KisFilterConfigurationSP factoryConfiguration() const override; + KisFilterConfigurationSP defaultConfiguration() const override; public: KisConfigWidget * createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP dev, bool useForMasks) const override; QRect neededRect(const QRect & rect, const KisFilterConfigurationSP _config, int lod) const override; QRect changedRect(const QRect & rect, const KisFilterConfigurationSP _config, int lod) const override; }; #endif // KIS_EDGE_DETECTION_FILTER_H diff --git a/plugins/filters/embossfilter/kis_emboss_filter.cpp b/plugins/filters/embossfilter/kis_emboss_filter.cpp index 78de2c82c1..defcb83c96 100644 --- a/plugins/filters/embossfilter/kis_emboss_filter.cpp +++ b/plugins/filters/embossfilter/kis_emboss_filter.cpp @@ -1,160 +1,160 @@ /* * This file is part of Krita * * Copyright (c) 2004 Michael Thaler * * ported from digikam, Copyrighted 2004 Gilles Caulier, * Original Emboss algorithm copyrighted 2004 by * Pieter Z. Voloshyn . * * 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_emboss_filter.h" #include #include #include #include #include #include #include #include "KoIntegerMaths.h" #include #include #include #include #include #include #include #include #include #include #include "widgets/kis_multi_integer_filter_widget.h" #include KisEmbossFilter::KisEmbossFilter() : KisFilter(id(), FiltersCategoryEmbossId, i18n("&Emboss with Variable Depth...")) { setSupportsPainting(false); setColorSpaceIndependence(TO_RGBA8); setSupportsThreading(false); setSupportsAdjustmentLayers(false); } -KisFilterConfigurationSP KisEmbossFilter::factoryConfiguration() const +KisFilterConfigurationSP KisEmbossFilter::defaultConfiguration() const { - KisFilterConfigurationSP config = new KisFilterConfiguration(id().id(), 0); + KisFilterConfigurationSP config = factoryConfiguration(); config->setProperty("depth", 30); return config; } // This method have been ported from Pieter Z. Voloshyn algorithm code. /* Function to apply the Emboss effect * * data => The image data in RGBA mode. * Width => Width of image. * Height => Height of image. * d => Emboss value * * Theory => This is an amazing effect. And the theory is very simple to * understand. You get the difference between the colors and * increase it. After this, get the gray tone */ void KisEmbossFilter::processImpl(KisPaintDeviceSP device, const QRect& applyRect, const KisFilterConfigurationSP config, KoUpdater* progressUpdater ) const { QPoint srcTopLeft = applyRect.topLeft(); Q_ASSERT(device); //read the filter configuration values from the KisFilterConfiguration object quint32 embossdepth = config ? config->getInt("depth", 30) : 30; //the actual filter function from digikam. It needs a pointer to a quint8 array //with the actual pixel data. float Depth = embossdepth / 10.0; int R = 0, G = 0, B = 0; uchar Gray = 0; int Width = applyRect.width(); int Height = applyRect.height(); KisSequentialIteratorProgress it(device, applyRect, progressUpdater); QColor color1; QColor color2; KisRandomConstAccessorSP acc = device->createRandomAccessorNG(srcTopLeft.x(), srcTopLeft.y()); while (it.nextPixel()) { // XXX: COLORSPACE_INDEPENDENCE or at least work IN RGB16A device->colorSpace()->toQColor(it.oldRawData(), &color1); acc->moveTo(srcTopLeft.x() + it.x() + Lim_Max(it.x(), 1, Width), srcTopLeft.y() + it.y() + Lim_Max(it.y(), 1, Height)); device->colorSpace()->toQColor(acc->oldRawData(), &color2); R = abs((int)((color1.red() - color2.red()) * (int)Depth + (quint8_MAX / 2))); G = abs((int)((color1.green() - color2.green()) * (int)Depth + (quint8_MAX / 2))); B = abs((int)((color1.blue() - color2.blue()) * (int)Depth + (quint8_MAX / 2))); Gray = CLAMP((R + G + B) / 3, 0, quint8_MAX); device->colorSpace()->fromQColor(QColor(Gray, Gray, Gray, color1.alpha()), it.rawData()); } } // This method have been ported from Pieter Z. Voloshyn algorithm code. /* This function limits the max and min values * defined by the developer * * Now => Original value * Up => Increments * Max => Maximum value * * Theory => This function is used in some functions to limit the * "for step". E.g. I have a picture with 309 pixels (width), and * my "for step" is 5. All the code goes alright until it reaches the * w = 305, because in the next step we will go to 310, but we want * to analyze all the pixels. So, this function will reduce the * "for step", when necessary, until we reach the last possible value */ int KisEmbossFilter::Lim_Max(int Now, int Up, int Max) const { --Max; while (Now > Max - Up) --Up; return (Up); } KisConfigWidget * KisEmbossFilter::createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP dev, bool) const { Q_UNUSED(dev); vKisIntegerWidgetParam param; param.push_back(KisIntegerWidgetParam(10, 300, 30, i18nc("Emboss depth", "Depth"), "depth")); KisConfigWidget * w = new KisMultiIntegerFilterWidget(id().id(), parent, id().id(), param); Q_CHECK_PTR(w); return w; } diff --git a/plugins/filters/embossfilter/kis_emboss_filter.h b/plugins/filters/embossfilter/kis_emboss_filter.h index 6248dfb5ae..f4fd55b090 100644 --- a/plugins/filters/embossfilter/kis_emboss_filter.h +++ b/plugins/filters/embossfilter/kis_emboss_filter.h @@ -1,51 +1,51 @@ /* * This file is part of the KDE project * * Copyright (c) Michael Thaler * * 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_EMBOSS_FILTER_H_ #define _KIS_EMBOSS_FILTER_H_ #include "filter/kis_filter.h" #include "kis_config_widget.h" class KisEmbossFilter : public KisFilter { public: KisEmbossFilter(); public: void processImpl(KisPaintDeviceSP device, const QRect& applyRect, const KisFilterConfigurationSP config, KoUpdater* progressUpdater ) const override; static inline KoID id() { return KoID("emboss", i18n("Emboss with Variable Depth")); } public: KisConfigWidget * createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP dev, bool useForMasks) const override; protected: - KisFilterConfigurationSP factoryConfiguration() const override; + KisFilterConfigurationSP defaultConfiguration() const override; private: inline int Lim_Max(int Now, int Up, int Max) const; }; #endif diff --git a/plugins/filters/fastcolortransfer/fastcolortransfer.cpp b/plugins/filters/fastcolortransfer/fastcolortransfer.cpp index dde61044eb..266df72d5a 100644 --- a/plugins/filters/fastcolortransfer/fastcolortransfer.cpp +++ b/plugins/filters/fastcolortransfer/fastcolortransfer.cpp @@ -1,171 +1,171 @@ /* * This file is part of Krita * * Copyright (c) 2006 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 "fastcolortransfer.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_wdg_fastcolortransfer.h" #include "ui_wdgfastcolortransfer.h" #include #include K_PLUGIN_FACTORY_WITH_JSON(KritaFastColorTransferFactory, "kritafastcolortransfer.json", registerPlugin();) FastColorTransferPlugin::FastColorTransferPlugin(QObject *parent, const QVariantList &) : QObject(parent) { KisFilterRegistry::instance()->add(new KisFilterFastColorTransfer()); } FastColorTransferPlugin::~FastColorTransferPlugin() { } KisFilterFastColorTransfer::KisFilterFastColorTransfer() : KisFilter(id(), FiltersCategoryColorId, i18n("&Color Transfer...")) { setColorSpaceIndependence(FULLY_INDEPENDENT); setSupportsThreading(false); setSupportsPainting(false); setSupportsAdjustmentLayers(false); } KisConfigWidget * KisFilterFastColorTransfer::createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP dev, bool) const { Q_UNUSED(dev); return new KisWdgFastColorTransfer(parent); } -KisFilterConfigurationSP KisFilterFastColorTransfer::factoryConfiguration() const +KisFilterConfigurationSP KisFilterFastColorTransfer::defaultConfiguration() const { - KisFilterConfigurationSP config = new KisFilterConfiguration(id().id(), 1); + KisFilterConfigurationSP config = factoryConfiguration(); config->setProperty("filename", ""); return config; } #define CLAMP(x,l,u) ((x)<(l)?(l):((x)>(u)?(u):(x))) void KisFilterFastColorTransfer::processImpl(KisPaintDeviceSP device, const QRect& applyRect, const KisFilterConfigurationSP config, KoUpdater* progressUpdater) const { Q_ASSERT(device != 0); dbgPlugins << "Start transferring color"; // Convert ref and src to LAB const KoColorSpace* labCS = KoColorSpaceRegistry::instance()->lab16(); if (!labCS) { dbgPlugins << "The LAB colorspace is not available."; return; } dbgPlugins << "convert a copy of src to lab"; const KoColorSpace* oldCS = device->colorSpace(); KisPaintDeviceSP srcLAB = new KisPaintDevice(*device.data()); dbgPlugins << "srcLab : " << srcLAB->extent(); srcLAB->convertTo(labCS, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); KoProgressUpdater compositeUpdater(progressUpdater, KoProgressUpdater::Unthreaded); KoUpdater *updaterStats = compositeUpdater.startSubtask(1); KoUpdater *updaterMap = compositeUpdater.startSubtask(2); // Compute the means and sigmas of src dbgPlugins << "Compute the means and sigmas of src"; double meanL_src = 0., meanA_src = 0., meanB_src = 0.; double sigmaL_src = 0., sigmaA_src = 0., sigmaB_src = 0.; { KisSequentialConstIteratorProgress srcIt(srcLAB, applyRect, updaterStats); while (srcIt.nextPixel()) { const quint16* data = reinterpret_cast(srcIt.oldRawData()); quint32 L = data[0]; quint32 A = data[1]; quint32 B = data[2]; meanL_src += L; meanA_src += A; meanB_src += B; sigmaL_src += L * L; sigmaA_src += A * A; sigmaB_src += B * B; } } double totalSize = 1. / (applyRect.width() * applyRect.height()); meanL_src *= totalSize; meanA_src *= totalSize; meanB_src *= totalSize; sigmaL_src *= totalSize; sigmaA_src *= totalSize; sigmaB_src *= totalSize; dbgPlugins << totalSize << "" << meanL_src << "" << meanA_src << "" << meanB_src << "" << sigmaL_src << "" << sigmaA_src << "" << sigmaB_src; double meanL_ref = config->getDouble("meanL"); double meanA_ref = config->getDouble("meanA"); double meanB_ref = config->getDouble("meanB"); double sigmaL_ref = config->getDouble("sigmaL"); double sigmaA_ref = config->getDouble("sigmaA"); double sigmaB_ref = config->getDouble("sigmaB"); // Transfer colors dbgPlugins << "Transfer colors"; { double coefL = sqrt((sigmaL_ref - meanL_ref * meanL_ref) / (sigmaL_src - meanL_src * meanL_src)); double coefA = sqrt((sigmaA_ref - meanA_ref * meanA_ref) / (sigmaA_src - meanA_src * meanA_src)); double coefB = sqrt((sigmaB_ref - meanB_ref * meanB_ref) / (sigmaB_src - meanB_src * meanB_src)); quint16 labPixel[4]; KisSequentialConstIteratorProgress srcLabIt(srcLAB, applyRect, updaterMap); KisSequentialIterator dstIt(device, applyRect); while (srcLabIt.nextPixel() && dstIt.nextPixel()) { const quint16* data = reinterpret_cast(srcLabIt.oldRawData()); labPixel[0] = (quint16)CLAMP(((double)data[0] - meanL_src) * coefL + meanL_ref, 0., 65535.); labPixel[1] = (quint16)CLAMP(((double)data[1] - meanA_src) * coefA + meanA_ref, 0., 65535.); labPixel[2] = (quint16)CLAMP(((double)data[2] - meanB_src) * coefB + meanB_ref, 0., 65535.); labPixel[3] = data[3]; oldCS->fromLabA16(reinterpret_cast(labPixel), dstIt.rawData(), 1); } } } #include "fastcolortransfer.moc" diff --git a/plugins/filters/fastcolortransfer/fastcolortransfer.h b/plugins/filters/fastcolortransfer/fastcolortransfer.h index aeb12b4667..e8dd9a0347 100644 --- a/plugins/filters/fastcolortransfer/fastcolortransfer.h +++ b/plugins/filters/fastcolortransfer/fastcolortransfer.h @@ -1,55 +1,55 @@ /* * This file is part of Krita * * Copyright (c) 2006 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 COLORTRANSFER_H #define COLORTRANSFER_H #include #include #include class FastColorTransferPlugin : public QObject { Q_OBJECT public: FastColorTransferPlugin(QObject *parent, const QVariantList &); ~FastColorTransferPlugin() override; }; class KisFilterFastColorTransfer : public KisFilter { public: KisFilterFastColorTransfer(); public: void processImpl(KisPaintDeviceSP device, const QRect& applyRect, const KisFilterConfigurationSP config, KoUpdater* progressUpdater) const override; static inline KoID id() { return KoID("colortransfer", i18n("Color Transfer")); } public: KisConfigWidget * createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP dev, bool useForMasks) const override; - KisFilterConfigurationSP factoryConfiguration() const override; + KisFilterConfigurationSP defaultConfiguration() const override; }; #endif diff --git a/plugins/filters/gaussianhighpass/gaussianhighpass_filter.cpp b/plugins/filters/gaussianhighpass/gaussianhighpass_filter.cpp index d92d48fb18..c258f3efc7 100644 --- a/plugins/filters/gaussianhighpass/gaussianhighpass_filter.cpp +++ b/plugins/filters/gaussianhighpass/gaussianhighpass_filter.cpp @@ -1,125 +1,124 @@ /* * This file is part of Krita * * Copyright (c) 2019 Miguel Lopez * * 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 "gaussianhighpass_filter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_lod_transform.h" #include #include "wdg_gaussianhighpass.h" #include "ui_wdggaussianhighpass.h" #include "KoColorSpaceTraits.h" #include KisGaussianHighPassFilter::KisGaussianHighPassFilter() : KisFilter(id(), FiltersCategoryEdgeDetectionId, i18n("&Gaussian High Pass...")) { setSupportsPainting(true); setSupportsAdjustmentLayers(true); setSupportsThreading(true); setSupportsLevelOfDetail(true); setColorSpaceIndependence(FULLY_INDEPENDENT); } KisConfigWidget * KisGaussianHighPassFilter::createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP, bool /* useForMasks */) const { return new KisWdgGaussianHighPass(parent); } -KisFilterConfigurationSP KisGaussianHighPassFilter::factoryConfiguration() const +KisFilterConfigurationSP KisGaussianHighPassFilter::defaultConfiguration() const { - KisFilterConfigurationSP config = new KisFilterConfiguration(id().id(), 1); + KisFilterConfigurationSP config = factoryConfiguration(); config->setProperty("blurAmount", 1); return config; } void KisGaussianHighPassFilter::processImpl(KisPaintDeviceSP device, const QRect& applyRect, const KisFilterConfigurationSP _config, KoUpdater * ) const { QPointer convolutionUpdater = 0; KisFilterConfigurationSP config = _config ? _config : new KisFilterConfiguration(id().id(), 1); QVariant value; KisLodTransformScalar t(device); const qreal blurAmount = t.scale(config->getProperty("blurAmount", value) ? value.toDouble() : 1.0); QBitArray channelFlags = config->channelFlags(); const QRect gaussNeedRect = this->neededRect(applyRect, config, device->defaultBounds()->currentLevelOfDetail()); - KisPaintDeviceSP blur = m_cachedPaintDevice.getDevice(device); + KisCachedPaintDevice::Guard d1(device, m_cachedPaintDevice); + KisPaintDeviceSP blur = d1.device(); KisPainter::copyAreaOptimizedOldData(gaussNeedRect.topLeft(), device, blur, gaussNeedRect); KisGaussianKernel::applyGaussian(blur, applyRect, blurAmount, blurAmount, channelFlags, convolutionUpdater, - true); // make sure we cerate an internal transaction on temp device + true); // make sure we craate an internal transaction on temp device KisPainter painter(device); painter.setCompositeOp(blur->colorSpace()->compositeOp(COMPOSITE_GRAIN_EXTRACT)); painter.bitBlt(applyRect.topLeft(), blur, applyRect); painter.end(); - - m_cachedPaintDevice.putDevice(blur); } QRect KisGaussianHighPassFilter::neededRect(const QRect & rect, const KisFilterConfigurationSP config, int lod) const { KisLodTransformScalar t(lod); QVariant value; const int halfSize = config->getProperty("blurAmount", value) ? KisGaussianKernel::kernelSizeFromRadius(t.scale(value.toFloat())) / 2 : 5; return rect.adjusted( -halfSize * 2, -halfSize * 2, halfSize * 2, halfSize * 2); } QRect KisGaussianHighPassFilter::changedRect(const QRect & rect, const KisFilterConfigurationSP config, int lod) const { KisLodTransformScalar t(lod); QVariant value; const int halfSize = config->getProperty("blurAmount", value) ? KisGaussianKernel::kernelSizeFromRadius(t.scale(value.toFloat())) / 2 : 5; return rect.adjusted( -halfSize, -halfSize, halfSize, halfSize); } diff --git a/plugins/filters/gaussianhighpass/gaussianhighpass_filter.h b/plugins/filters/gaussianhighpass/gaussianhighpass_filter.h index b772b97156..321a242699 100644 --- a/plugins/filters/gaussianhighpass/gaussianhighpass_filter.h +++ b/plugins/filters/gaussianhighpass/gaussianhighpass_filter.h @@ -1,54 +1,54 @@ /* * This file is part of Krita * * Copyright (c) 2019 Miguel Lopez * * 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_GAUSSIANHIGHPASS_FILTER_H #define KIS_GAUSSIANHIGHPASS_FILTER_H #include "filter/kis_filter.h" #include "kis_cached_paint_device.h" class KisGaussianHighPassFilter : public KisFilter { public: KisGaussianHighPassFilter(); void processImpl(KisPaintDeviceSP device, const QRect& applyRect, const KisFilterConfigurationSP config, KoUpdater * ) const override; static inline KoID id() { return KoID("gaussianhighpass", i18n("Gaussian High Pass")); } KisConfigWidget * createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP dev, bool useForMasks) const override; - KisFilterConfigurationSP factoryConfiguration() const override; + KisFilterConfigurationSP defaultConfiguration() const override; QRect changedRect(const QRect & rect, const KisFilterConfigurationSP _config, int lod) const override; QRect neededRect(const QRect & rect, const KisFilterConfigurationSP _config, int lod) const override; private: mutable KisCachedPaintDevice m_cachedPaintDevice; }; #endif diff --git a/plugins/filters/gradientmap/krita_filter_gradient_map.cpp b/plugins/filters/gradientmap/krita_filter_gradient_map.cpp index 4e6aa773ef..17e647b826 100644 --- a/plugins/filters/gradientmap/krita_filter_gradient_map.cpp +++ b/plugins/filters/gradientmap/krita_filter_gradient_map.cpp @@ -1,138 +1,143 @@ /* * This file is part of the KDE project * * Copyright (c) 2016 Spencer Brown * 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 "krita_filter_gradient_map.h" #include #include #include #include #include #include #include "kis_config_widget.h" #include #include #include #include #include #include "gradientmap.h" #include #include KritaFilterGradientMap::KritaFilterGradientMap() : KisFilter(id(), FiltersCategoryMapId, i18n("&Gradient Map...")) { setColorSpaceIndependence(FULLY_INDEPENDENT); setShowConfigurationWidget(true); setSupportsLevelOfDetail(true); setSupportsPainting(true); setSupportsAdjustmentLayers(true); setSupportsThreading(true); } void KritaFilterGradientMap::processImpl(KisPaintDeviceSP device, const QRect& applyRect, const KisFilterConfigurationSP config, KoUpdater *progressUpdater) const { Q_ASSERT(!device.isNull()); QDomDocument doc; if (config->version()==1) { QDomElement elt = doc.createElement("gradient"); KoAbstractGradient *gradientAb = KoResourceServerProvider::instance()->gradientServer()->resourceByName(config->getString("gradientName")); if (!gradientAb) { qWarning() << "Could not find gradient" << config->getString("gradientName"); } gradientAb = KoResourceServerProvider::instance()->gradientServer()->resources().first(); QScopedPointer qGradient(gradientAb->toQGradient()); KoStopGradient::fromQGradient(qGradient.data())->toXML(doc, elt); doc.appendChild(elt); } else { doc.setContent(config->getString("gradientXML", "")); } KoStopGradient gradient = KoStopGradient::fromXML(doc.firstChildElement()); const ColorMode colorMode = ColorMode(config->getInt("colorMode")); KisDitherUtil ditherUtil; if (colorMode == ColorMode::Dither) ditherUtil.setConfiguration(*config, "dither/"); KoColor outColor(Qt::white, device->colorSpace()); KisSequentialIteratorProgress it(device, applyRect, progressUpdater); qreal grey; const int pixelSize = device->colorSpace()->pixelSize(); while (it.nextPixel()) { grey = qreal(device->colorSpace()->intensity8(it.oldRawData())) / 255; if (colorMode == ColorMode::Nearest) { KoGradientStop leftStop, rightStop; if (!gradient.stopsAt(leftStop, rightStop, grey)) continue; if (std::abs(grey - leftStop.first) < std::abs(grey - rightStop.first)) { outColor = leftStop.second; } else { outColor = rightStop.second; } } else if (colorMode == ColorMode::Dither) { KoGradientStop leftStop, rightStop; if (!gradient.stopsAt(leftStop, rightStop, grey)) continue; qreal localT = (grey - leftStop.first) / (rightStop.first - leftStop.first); if (localT < ditherUtil.threshold(QPoint(it.x(), it.y()))) { outColor = leftStop.second; } else { outColor = rightStop.second; } } else { gradient.colorAt(outColor, grey); } outColor.setOpacity(qMin(KoColor(it.oldRawData(), device->colorSpace()).opacityF(), outColor.opacityF())); outColor.convertTo(device->colorSpace()); memcpy(it.rawData(), outColor.data(), pixelSize); } } KisFilterConfigurationSP KritaFilterGradientMap::factoryConfiguration() const { - KisFilterConfigurationSP config = new KisFilterConfiguration("gradientmap", 2); + return new KisFilterConfiguration(id().id(), 2); +} + +KisFilterConfigurationSP KritaFilterGradientMap::defaultConfiguration() const +{ + KisFilterConfigurationSP config = factoryConfiguration(); KoAbstractGradient *gradient = KoResourceServerProvider::instance()->gradientServer()->resources().first(); KoStopGradient stopGradient; QScopedPointer qGradient(gradient->toQGradient()); stopGradient.fromQGradient(qGradient.data()); QDomDocument doc; QDomElement elt = doc.createElement("gradient"); stopGradient.toXML(doc, elt); doc.appendChild(elt); config->setProperty("gradientXML", doc.toString()); config->setProperty("colorMode", false); KisDitherWidget::factoryConfiguration(*config, "dither/"); return config; } KisConfigWidget * KritaFilterGradientMap::createConfigurationWidget(QWidget * parent, const KisPaintDeviceSP dev, bool) const { return new KritaGradientMapConfigWidget(parent, dev); } diff --git a/plugins/filters/gradientmap/krita_filter_gradient_map.h b/plugins/filters/gradientmap/krita_filter_gradient_map.h index 48ea26ea96..4100671bba 100644 --- a/plugins/filters/gradientmap/krita_filter_gradient_map.h +++ b/plugins/filters/gradientmap/krita_filter_gradient_map.h @@ -1,57 +1,58 @@ /* * This file is part of Krita * * Copyright (c) 2016 Spencer Brown * * 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_GRADIENT_MAP_H #define KIS_GRADIENT_MAP_H #include #include #include #include #include #include #include class KritaFilterGradientMap : public KisFilter { public: KritaFilterGradientMap(); public: enum ColorMode { Blend, Nearest, Dither, }; static inline KoID id() { return KoID("gradientmap", i18n("Gradient Map")); } void processImpl(KisPaintDeviceSP device, const QRect& applyRect, const KisFilterConfigurationSP config, KoUpdater *progressUpdater) const override; KisFilterConfigurationSP factoryConfiguration() const override; + KisFilterConfigurationSP defaultConfiguration() const override; KisConfigWidget* createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP dev, bool useForMasks) const override; }; #endif diff --git a/plugins/filters/halftone/kis_halftone_filter.cpp b/plugins/filters/halftone/kis_halftone_filter.cpp index 32055035ba..f2a34e6094 100644 --- a/plugins/filters/halftone/kis_halftone_filter.cpp +++ b/plugins/filters/halftone/kis_halftone_filter.cpp @@ -1,270 +1,270 @@ /* * This file is part of Krita * * Copyright (c) 2016 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. */ #include #include #include #include #include #include #include "kis_filter_configuration.h" #include #include #include #include #include #include #include #include #include "kis_halftone_filter.h" K_PLUGIN_FACTORY_WITH_JSON(KritaHalftoneFactory, "kritahalftone.json", registerPlugin();) KritaHalftone::KritaHalftone(QObject *parent, const QVariantList &) : QObject(parent) { KisFilterRegistry::instance()->add(new KisHalftoneFilter()); } KritaHalftone::~KritaHalftone() { } KisHalftoneFilter::KisHalftoneFilter() : KisFilter(id(), FiltersCategoryArtisticId, i18n("&Halftone...")) { setColorSpaceIndependence(FULLY_INDEPENDENT); setSupportsPainting(false); setShowConfigurationWidget(true); setSupportsLevelOfDetail(false); setSupportsAdjustmentLayers(false); setSupportsThreading(false); } //I am pretty terrible at trigonometry, hence all the comments. void KisHalftoneFilter::processImpl(KisPaintDeviceSP device, const QRect &applyRect, const KisFilterConfigurationSP config, KoUpdater *progressUpdater) const { qreal cellSize = (qreal)config->getInt("cellSize", 8); qreal angle = fmod((qreal)config->getInt("patternAngle", 45), 90.0); KoColor foregroundC(Qt::black, device->colorSpace()); foregroundC.fromKoColor(config->getColor("foreGroundColor", KoColor(Qt::black, device->colorSpace()) ) ); KoColor backgroundC(Qt::white, device->colorSpace()); backgroundC.fromKoColor(config->getColor("backGroundColor", KoColor(Qt::white, device->colorSpace()) ) ); //First calculate the full diameter, using pythagoras theorem. qreal diameter = qSqrt((cellSize*cellSize)*2); qreal cellSpacingH = cellSize; qreal cellSpacingV = cellSize; qreal cellOffsetV = 0; if (angle>0.0){ //2/3=0.6 sin=o/h //0.6*3=2 sin*h=o //2/0.6=3 o/sin=h qreal angleRad = qDegreesToRadians(angle); //Horizontal cellspacing is the hypotenuse of the Angle and the cellSize(opposite). cellSpacingH = cellSize/qSin(angleRad); //Vertical cellspacing is the opposite of the Angle and the cellSize(hypotenuse). cellSpacingV = cellSize*qSin(angleRad); //cellSpacingoffset is the oppose of the (90-angle) and the vertical cellspacing(adjectant) toa t=o/a, t*a=o. cellOffsetV = qTan(qDegreesToRadians(90-angle))*cellSpacingV; } QPolygonF gridPoints; QRect totalRect(QPoint(0,0), applyRect.bottomRight()); if (gridPoints.size()<1) { int rows = (totalRect.height()/cellSpacingV)+3; for (int r=0; rexactBounds(); if (progressUpdater) { progressUpdater->setRange(0, (applyRect.height()/cellSpacingV+3)*(applyRect.width()/cellSpacingH+3)); } KisRandomConstAccessorSP iterator = device->createRandomConstAccessorNG( 0, 0); //iterator->numContiguousColumns(qCeil(cellSize)); //iterator->numContiguousRows(qCeil(cellSize)); KisPainter painter(device); painter.setCompositeOp(device->colorSpace()->compositeOp(COMPOSITE_OVER)); KisPaintDeviceSP dab = device->createCompositionSourceDevice(); KisPainter dbPainter(dab); KisSelectionSP alpha = new KisSelection(new KisSelectionEmptyBounds(0)); alpha->pixelSelection()->copyAlphaFrom(device, applyRect); device->fill(applyRect, backgroundC); dbPainter.setAntiAliasPolygonFill(config->getBool("antiAliasing", true)); dbPainter.setPaintColor(foregroundC); dbPainter.setFillStyle(KisPainter::FillStyleForegroundColor); dbPainter.setCompositeOp(device->colorSpace()->compositeOp(COMPOSITE_OVER)); quint8 eightbit = 255; if (config->getBool("invert", false)) { eightbit = 0; } QRect cellRect(applyRect.topLeft()-QPoint(qFloor(cellSpacingH), qFloor(qMax(cellSpacingV, diameter))), applyRect.bottomRight()+QPoint(qCeil(cellSpacingH), qCeil(qMax(cellSpacingV, diameter)))); for (int i=0; imoveTo(center.x(), center.y()); quint8 intensity = device->colorSpace()->intensity8(iterator->oldRawData()); qreal size = diameter*((qAbs(intensity-eightbit))/255.0); QPoint sPoint(qMax(qFloor(samplePoint.x()), applyRect.left()), qMax(qFloor(samplePoint.y()), applyRect.top())); dbPainter.bitBlt(0, 0, device, sPoint.x(), sPoint.y(), diameter, diameter); dbPainter.paintEllipse((samplePoint.x()-qFloor(samplePoint.x()))+qCeil(size)-size, (samplePoint.y()-qFloor(samplePoint.y()))+qCeil(size)-size, size, size); dab->crop(qAbs(qMin(0, xdifference)), qAbs(qMin(0, ydifference)), diameter, diameter); //we only want to paint the bits actually in the apply rect...) painter.bitBlt(sPoint, dab, dab->exactBounds()); if (progressUpdater) { progressUpdater->setValue(i); } } } alpha->pixelSelection()->invert(); device->clearSelection(alpha); } -KisFilterConfigurationSP KisHalftoneFilter::factoryConfiguration() const +KisFilterConfigurationSP KisHalftoneFilter::defaultConfiguration() const { - KisFilterConfigurationSP config = new KisFilterConfiguration("halftone", 1); + KisFilterConfigurationSP config = factoryConfiguration(); config->setProperty("cellSize", 8.0); config->setProperty("patternAngle", 45.0); QVariant v; KoColor black; black.fromQColor(QColor(Qt::black)); v.setValue(black); config->setProperty("foreGroundColor", v); KoColor white; white.fromQColor(QColor(Qt::white)); v.setValue(white); config->setProperty("backGroundColor", v); config->setProperty("antiAliasing", true); config->setProperty("invert", false); return config; } KisConfigWidget *KisHalftoneFilter::createConfigurationWidget(QWidget *parent, const KisPaintDeviceSP dev, bool) const { return new KisHalftoneConfigWidget(parent, dev); } //----------config---------// KisHalftoneConfigWidget::KisHalftoneConfigWidget(QWidget *parent, KisPaintDeviceSP dev) : KisConfigWidget(parent) { Q_ASSERT(dev); m_page.setupUi(this); KoColor white(Qt::white, dev->colorSpace()); KoColor black(Qt::black, dev->colorSpace()); m_page.bnforeground->setColor(white); m_page.bnbackground->setColor(black); m_page.bnforeground->setDefaultColor(white); m_page.bnbackground->setDefaultColor(black); m_page.sld_cellSize->setRange(3, 90); connect(m_page.sld_cellSize, SIGNAL(valueChanged(int)), SLOT(slotConfigChanged())); connect(m_page.dial_angle, SIGNAL(valueChanged(int)), m_page.spb_angle, SLOT(setValue(int))); connect(m_page.dial_angle, SIGNAL(valueChanged(int)), SLOT(slotConfigChanged())); connect(m_page.spb_angle, SIGNAL(valueChanged(int)), SLOT(slotConfigChanged())); connect(m_page.bnforeground, SIGNAL(changed(KoColor)), SLOT(slotConfigChanged())); connect(m_page.bnbackground, SIGNAL(changed(KoColor)), SLOT(slotConfigChanged())); connect(m_page.ckbAntialiasing, SIGNAL(toggled(bool)), SLOT(slotConfigChanged())); connect(m_page.ckbInvert, SIGNAL(toggled(bool)), SLOT(slotConfigChanged())); } KisHalftoneConfigWidget::~KisHalftoneConfigWidget() { } KisPropertiesConfigurationSP KisHalftoneConfigWidget::configuration() const { KisFilterConfiguration *config = new KisFilterConfiguration("halftone", 1); config->setProperty("cellSize", m_page.sld_cellSize->value()); config->setProperty("patternAngle", m_page.spb_angle->value()); QVariant v; v.setValue(m_page.bnforeground->color()); config->setProperty("foreGroundColor", v); v.setValue(m_page.bnbackground->color()); config->setProperty("backGroundColor", v); config->setProperty("antiAliasing", m_page.ckbAntialiasing->isChecked()); config->setProperty("invert", m_page.ckbInvert->isChecked()); return config; } void KisHalftoneConfigWidget::setConfiguration(const KisPropertiesConfigurationSP config) { QVariant value; if (config->getProperty("cellSize", value)) { m_page.sld_cellSize->setValue(value.toUInt()); } if (config->getProperty("patternAngle", value)) { m_page.dial_angle->setValue(value.toUInt()); m_page.spb_angle->setValue(value.toUInt()); } if (config->getProperty("antiAliasing", value)) { m_page.ckbAntialiasing->setChecked(value.toBool()); } if (config->getProperty("invert", value)) { m_page.ckbInvert->setChecked(value.toBool()); } m_page.bnforeground->setColor(config->getColor("foreGroundColor",m_page.bnforeground->defaultColor())); m_page.bnbackground->setColor(config->getColor("backGroundColor",m_page.bnbackground->defaultColor())); } #include "kis_halftone_filter.moc" diff --git a/plugins/filters/halftone/kis_halftone_filter.h b/plugins/filters/halftone/kis_halftone_filter.h index 6fe36b2f61..e70084f899 100644 --- a/plugins/filters/halftone/kis_halftone_filter.h +++ b/plugins/filters/halftone/kis_halftone_filter.h @@ -1,101 +1,101 @@ /* * This file is part of Krita * * Copyright (c) 2016 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 KISHALFTONEFILTER_H #define KISHALFTONEFILTER_H #include #include #include #include #include "ui_wdg_halftone_filter.h" class WdgHalftone; class KritaHalftone : public QObject { Q_OBJECT public: KritaHalftone(QObject *parent, const QVariantList &); ~KritaHalftone() override; }; /** * @brief The kisHalftoneFilter class * This filter will allow the user to input an image and have it be approximated with * a halftone pattern. https://en.wikipedia.org/wiki/Halftone * * The primary usecase of such a filter is for specialized printing techniques, but for * many people the half-tone pattern also serves as a neutral pattern that is more pleasant * than plain flat look. The half tone in this case also becomes a stylistic technique. * * Based on that, there's a few ways a user could want to use this technique: * 1. Per-component. Per patch, each component will have a halftone approximated. * 2. Intensity only. The relative luminosity of the patch is determined and will be used * for the approximation, resulting in a black/white pattern. * 3. Intensity and then two colors mapped to the black/white pattern. * * On top of that, the pattern can be rotated, the shape can be chosen, and the user will want to * decide whether or not to use antialiasing(as printers themselves give * inefficient results with anti-aliasing). * * As of currently, 2 and 3 can be done. 1 is not impossible to code with some creative usage of composite * modes, but might be a little slow. */ class KisHalftoneFilter : public KisFilter { public: KisHalftoneFilter(); static inline KoID id() { return KoID("halftone", i18n("Halftone")); } void processImpl(KisPaintDeviceSP device, const QRect& applyRect, const KisFilterConfigurationSP config, KoUpdater *progressUpdater) const override; - KisFilterConfigurationSP factoryConfiguration() const override; + KisFilterConfigurationSP defaultConfiguration() const override; KisConfigWidget *createConfigurationWidget(QWidget *parent, const KisPaintDeviceSP dev, bool useForMasks) const override; private: QPolygonF m_gridPoints; }; class KisHalftoneConfigWidget : public KisConfigWidget { Q_OBJECT public: KisHalftoneConfigWidget(QWidget *parent, KisPaintDeviceSP dev); ~KisHalftoneConfigWidget() override; KisPropertiesConfigurationSP configuration() const override; void setConfiguration(const KisPropertiesConfigurationSP config) override; Ui::WdgHalftone m_page; }; #endif // KISHALFTONEFILTER_H diff --git a/plugins/filters/imageenhancement/kis_simple_noise_reducer.cpp b/plugins/filters/imageenhancement/kis_simple_noise_reducer.cpp index fccafd3542..f9202d54e7 100644 --- a/plugins/filters/imageenhancement/kis_simple_noise_reducer.cpp +++ b/plugins/filters/imageenhancement/kis_simple_noise_reducer.cpp @@ -1,113 +1,128 @@ /* * 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. */ #include //MSVC requires that Vc come first #include "kis_simple_noise_reducer.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include "kis_lod_transform.h" KisSimpleNoiseReducer::KisSimpleNoiseReducer() : KisFilter(id(), FiltersCategoryEnhanceId, i18n("&Gaussian Noise Reduction...")) { setSupportsPainting(false); + setSupportsLevelOfDetail(true); } KisSimpleNoiseReducer::~KisSimpleNoiseReducer() { } KisConfigWidget * KisSimpleNoiseReducer::createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP dev, bool) const { Q_UNUSED(dev); vKisIntegerWidgetParam param; param.push_back(KisIntegerWidgetParam(0, 255, 15, i18n("Threshold"), "threshold")); param.push_back(KisIntegerWidgetParam(0, 10, 1, i18n("Window size"), "windowsize")); return new KisMultiIntegerFilterWidget(id().id(), parent, id().id(), param); } -KisFilterConfigurationSP KisSimpleNoiseReducer::factoryConfiguration() const +KisFilterConfigurationSP KisSimpleNoiseReducer::defaultConfiguration() const { - KisFilterConfigurationSP config = new KisFilterConfiguration(id().id(), 0); + KisFilterConfigurationSP config = factoryConfiguration(); config->setProperty("threshold", 15); config->setProperty("windowsize", 1); return config; } inline int ABS(int v) { if (v < 0) return -v; return v; } void KisSimpleNoiseReducer::processImpl(KisPaintDeviceSP device, const QRect& applyRect, const KisFilterConfigurationSP _config, KoUpdater* progressUpdater ) const { QPoint srcTopLeft = applyRect.topLeft(); Q_ASSERT(device); KisFilterConfigurationSP config = _config ? _config : defaultConfiguration(); const int threshold = config->getInt("threshold", 15); const int windowsize = config->getInt("windowsize", 1); const KoColorSpace* cs = device->colorSpace(); // Compute the blur mask KisCircleMaskGenerator* kas = new KisCircleMaskGenerator(2*windowsize + 1, 1, windowsize, windowsize, 2, true); KisConvolutionKernelSP kernel = KisConvolutionKernel::fromMaskGenerator(kas); delete kas; KisPaintDeviceSP interm = new KisPaintDevice(*device); // TODO no need for a full copy and then a transaction KisConvolutionPainter painter(interm); painter.beginTransaction(); painter.applyMatrix(kernel, interm, srcTopLeft, srcTopLeft, applyRect.size(), BORDER_REPEAT); painter.deleteTransaction(); KisSequentialConstIteratorProgress intermIt(interm, applyRect, progressUpdater); KisSequentialIterator dstIt(device, applyRect); while (dstIt.nextPixel() && intermIt.nextPixel()) { const quint8 diff = cs->difference(dstIt.oldRawData(), intermIt.oldRawData()); if (diff > threshold) { memcpy(dstIt.rawData(), intermIt.oldRawData(), cs->pixelSize()); } } } +QRect KisSimpleNoiseReducer::neededRect(const QRect & rect, const KisFilterConfigurationSP _config, int lod) const +{ + KisLodTransformScalar t(lod); + + const int windowsize = _config->getInt("windowsize", 1); + const int margin = qCeil(t.scale(qreal(windowsize))) + 1; + return kisGrowRect(rect, margin); +} + +QRect KisSimpleNoiseReducer::changedRect(const QRect & rect, const KisFilterConfigurationSP _config, int lod) const +{ + return neededRect(rect, _config, lod); +} diff --git a/plugins/filters/imageenhancement/kis_simple_noise_reducer.h b/plugins/filters/imageenhancement/kis_simple_noise_reducer.h index 2766507f16..a06fa47254 100644 --- a/plugins/filters/imageenhancement/kis_simple_noise_reducer.h +++ b/plugins/filters/imageenhancement/kis_simple_noise_reducer.h @@ -1,48 +1,52 @@ /* a * 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 KISSIMPLENOISEREDUCER_H #define KISSIMPLENOISEREDUCER_H #include #include "kis_config_widget.h" /** @author Cyrille Berger */ class KisSimpleNoiseReducer : public KisFilter { public: KisSimpleNoiseReducer(); ~KisSimpleNoiseReducer() override; public: void processImpl(KisPaintDeviceSP device, const QRect& applyRect, const KisFilterConfigurationSP config, KoUpdater* progressUpdater ) const override; KisConfigWidget * createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP dev, bool useForMasks) const override; static inline KoID id() { return KoID("gaussiannoisereducer", i18n("Gaussian Noise Reducer")); } + + QRect changedRect(const QRect &rect, const KisFilterConfigurationSP _config, int lod) const override; + QRect neededRect(const QRect &rect, const KisFilterConfigurationSP _config, int lod) const override; + protected: - KisFilterConfigurationSP factoryConfiguration() const override; + KisFilterConfigurationSP defaultConfiguration() const override; }; #endif diff --git a/plugins/filters/imageenhancement/kis_wavelet_noise_reduction.cpp b/plugins/filters/imageenhancement/kis_wavelet_noise_reduction.cpp index 14df748383..da81ca1866 100644 --- a/plugins/filters/imageenhancement/kis_wavelet_noise_reduction.cpp +++ b/plugins/filters/imageenhancement/kis_wavelet_noise_reduction.cpp @@ -1,125 +1,125 @@ /* * This file is part of the KDE project * * 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. */ #include "kis_wavelet_noise_reduction.h" #include #include #include #include #include #include #include #include #include #include #include "kis_global.h" KisWaveletNoiseReduction::KisWaveletNoiseReduction() : KisFilter(id(), FiltersCategoryEnhanceId, i18n("&Wavelet Noise Reducer...")) { setSupportsPainting(false); setSupportsThreading(false); } KisWaveletNoiseReduction::~KisWaveletNoiseReduction() { } KisConfigWidget * KisWaveletNoiseReduction::createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP, bool) const { vKisDoubleWidgetParam param; param.push_back(KisDoubleWidgetParam(0.0, 256.0, BEST_WAVELET_THRESHOLD_VALUE, i18n("Threshold"), "threshold")); return new KisMultiDoubleFilterWidget(id().id(), parent, id().id(), param); } -KisFilterConfigurationSP KisWaveletNoiseReduction::factoryConfiguration() const +KisFilterConfigurationSP KisWaveletNoiseReduction::defaultConfiguration() const { - KisFilterConfigurationSP config = new KisFilterConfiguration(id().id(), 0); + KisFilterConfigurationSP config = factoryConfiguration(); config->setProperty("threshold", BEST_WAVELET_THRESHOLD_VALUE); return config; } void KisWaveletNoiseReduction::processImpl(KisPaintDeviceSP device, const QRect& applyRect, const KisFilterConfigurationSP _config, KoUpdater* progressUpdater ) const { Q_ASSERT(device); KisFilterConfigurationSP config = _config ? _config : defaultConfiguration(); const float threshold = config->getDouble("threshold", BEST_WAVELET_THRESHOLD_VALUE); KisMathToolbox mathToolbox; // dbgFilters << size <<"" << maxrectsize <<"" << srcTopLeft.x() <<"" << srcTopLeft.y(); // dbgFilters <<"Transforming..."; KisMathToolbox::KisWavelet* buff = 0; KisMathToolbox::KisWavelet* wav = 0; try { buff = mathToolbox.initWavelet(device, applyRect); } catch (const std::bad_alloc&) { if (buff) delete buff; return; } try { wav = mathToolbox.fastWaveletTransformation(device, applyRect, buff); } catch (const std::bad_alloc&) { if (wav) delete wav; return; } float* const fin = wav->coeffs + wav->depth * pow2(wav->size); float* const begin = wav->coeffs + wav->depth; const int size = fin - begin; const int progressOffset = int(std::ceil(std::log2(size / 100))); const int progressMask = (1 << progressOffset) - 1; const int numProgressSteps = size >> progressOffset; int pointsProcessed = 0; progressUpdater->setRange(0, numProgressSteps); for (float* it = begin; it < fin; it++) { if (*it > threshold) { *it -= threshold; } else if (*it < -threshold) { *it += threshold; } else { *it = 0.; } if (!(pointsProcessed & progressMask)) { progressUpdater->setValue(pointsProcessed >> progressOffset); } pointsProcessed++; } mathToolbox.fastWaveletUntransformation(device, applyRect, wav, buff); delete wav; delete buff; } diff --git a/plugins/filters/imageenhancement/kis_wavelet_noise_reduction.h b/plugins/filters/imageenhancement/kis_wavelet_noise_reduction.h index 76665787cf..20c0878d11 100644 --- a/plugins/filters/imageenhancement/kis_wavelet_noise_reduction.h +++ b/plugins/filters/imageenhancement/kis_wavelet_noise_reduction.h @@ -1,57 +1,56 @@ /* * This file is part of the KDE project * * 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_WAVELET_NOISE_REDUCTION_H #define KIS_WAVELET_NOISE_REDUCTION_H #include #include #define BEST_WAVELET_THRESHOLD_VALUE 7.0 /** @author Cyrille Berger */ class KisWaveletNoiseReduction : public KisFilter { public: KisWaveletNoiseReduction(); ~KisWaveletNoiseReduction() override; public: void processImpl(KisPaintDeviceSP device, const QRect& applyRect, const KisFilterConfigurationSP config, KoUpdater* progressUpdater ) const override; KisConfigWidget * createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP dev, bool useForMasks) const override; static inline KoID id() { return KoID("waveletnoisereducer", i18n("Wavelet Noise Reducer")); } private: - KisFilterConfigurationSP factoryConfiguration() const override; - + KisFilterConfigurationSP defaultConfiguration() const override; }; #endif diff --git a/plugins/filters/indexcolors/indexcolors.cpp b/plugins/filters/indexcolors/indexcolors.cpp index 67d532ed24..c3460028c6 100644 --- a/plugins/filters/indexcolors/indexcolors.cpp +++ b/plugins/filters/indexcolors/indexcolors.cpp @@ -1,145 +1,145 @@ /* * Copyright 2014 Manuel Riecke * * Permission to use, copy, modify, and distribute this software * and its documentation for any purpose and without fee is hereby * granted, provided that the above copyright notice appear in all * copies and that both that the copyright notice and this * permission notice and warranty disclaimer appear in supporting * documentation, and that the name of the author not be used in * advertising or publicity pertaining to distribution of the * software without specific, written prior permission. * * The author disclaim all warranties with regard to this * software, including all implied warranties of merchantability * and fitness. In no event shall the author be liable for any * special, indirect or consequential damages or any damages * whatsoever resulting from loss of use, data or profits, whether * in an action of contract, negligence or other tortious action, * arising out of or in connection with the use or performance of * this software. */ #include "indexcolors.h" #include #include #include #include #include #include #include #include #include "kiswdgindexcolors.h" #include "palettegeneratorconfig.h" K_PLUGIN_FACTORY_WITH_JSON(IndexColorsFactory, "kritaindexcolors.json", registerPlugin();) IndexColors::IndexColors(QObject *parent, const QVariantList &) : QObject(parent) { KisFilterRegistry::instance()->add(KisFilterSP(new KisFilterIndexColors())); } IndexColors::~IndexColors() { } KisFilterIndexColors::KisFilterIndexColors() : KisColorTransformationFilter(id(), FiltersCategoryArtisticId, i18n("&Index Colors...")) { setColorSpaceIndependence(FULLY_INDEPENDENT); // Technically it is TO_LAB16 but that would only display a warning we don't want // This filter will always degrade the color space, that is it's purpose setSupportsPainting(true); setShowConfigurationWidget(true); } KoColorTransformation* KisFilterIndexColors::createTransformation(const KoColorSpace* cs, const KisFilterConfigurationSP config) const { IndexColorPalette pal; PaletteGeneratorConfig palCfg; palCfg.fromByteArray(config->getProperty("paletteGen").toByteArray()); pal = palCfg.generate(); if(config->getBool("reduceColorsEnabled")) { int maxClrs = config->getInt("colorLimit"); while(pal.numColors() > maxClrs) pal.mergeMostReduantColors(); } pal.similarityFactors.L = config->getFloat("LFactor"); pal.similarityFactors.a = config->getFloat("aFactor"); pal.similarityFactors.b = config->getFloat("bFactor"); return new KisIndexColorTransformation(pal, cs, config->getInt("alphaSteps")); } KisConfigWidget* KisFilterIndexColors::createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP dev, bool) const { Q_UNUSED(dev); KisWdgIndexColors* w = new KisWdgIndexColors(parent); w->setup( QStringList() << i18nc("Color palette shade", "Bright") << i18nc("Color palette shade", "Light") << i18nc("Color palette shade", "Base") << i18nc("Color palette shade", "Shadow") , 4 ); return w; } -KisFilterConfigurationSP KisFilterIndexColors::factoryConfiguration() const +KisFilterConfigurationSP KisFilterIndexColors::defaultConfiguration() const { - KisColorTransformationConfigurationSP config = new KisColorTransformationConfiguration(id().id(), 0); + KisFilterConfigurationSP config = factoryConfiguration(); PaletteGeneratorConfig palCfg; // Default constructor is factory config config->setProperty("paletteGen", palCfg.toByteArray()); config->setProperty("LFactor", 1.f); config->setProperty("aFactor", 1.f); config->setProperty("bFactor", 1.f); config->setProperty("reduceColorsEnabled", false); config->setProperty("colorLimit", 32); config->setProperty("alphaSteps", 1); return config; } KisIndexColorTransformation::KisIndexColorTransformation(IndexColorPalette palette, const KoColorSpace* cs, int alphaSteps) : m_colorSpace(cs), m_psize(cs->pixelSize()) { m_palette = palette; static const qreal max = KoColorSpaceMathsTraits::max; if(alphaSteps > 0) { m_alphaStep = max / alphaSteps; m_alphaHalfStep = m_alphaStep / 2; } else { m_alphaStep = 0; m_alphaHalfStep = 0; } } void KisIndexColorTransformation::transform(const quint8* src, quint8* dst, qint32 nPixels) const { union { quint16 laba[4]; LabColor lab; } clr; while (nPixels--) { m_colorSpace->toLabA16(src, reinterpret_cast(clr.laba), 1); clr.lab = m_palette.getNearestIndex(clr.lab); if(m_alphaStep) { quint16 amod = clr.laba[3] % m_alphaStep; clr.laba[3] = clr.laba[3] + (amod > m_alphaHalfStep ? m_alphaStep - amod : -amod); } m_colorSpace->fromLabA16(reinterpret_cast(clr.laba), dst, 1); src += m_psize; dst += m_psize; } } #include "indexcolors.moc" diff --git a/plugins/filters/indexcolors/indexcolors.h b/plugins/filters/indexcolors/indexcolors.h index dae51d49cf..2c5d386810 100644 --- a/plugins/filters/indexcolors/indexcolors.h +++ b/plugins/filters/indexcolors/indexcolors.h @@ -1,69 +1,69 @@ /* * Copyright 2014 Manuel Riecke * * Permission to use, copy, modify, and distribute this software * and its documentation for any purpose and without fee is hereby * granted, provided that the above copyright notice appear in all * copies and that both that the copyright notice and this * permission notice and warranty disclaimer appear in supporting * documentation, and that the name of the author not be used in * advertising or publicity pertaining to distribution of the * software without specific, written prior permission. * * The author disclaim all warranties with regard to this * software, including all implied warranties of merchantability * and fitness. In no event shall the author be liable for any * special, indirect or consequential damages or any damages * whatsoever resulting from loss of use, data or profits, whether * in an action of contract, negligence or other tortious action, * arising out of or in connection with the use or performance of * this software. */ #ifndef INDEXCOLORS_H #define INDEXCOLORS_H #include #include #include "filter/kis_color_transformation_filter.h" #include "kis_config_widget.h" #include #include "indexcolorpalette.h" class IndexColors : public QObject { Q_OBJECT public: IndexColors(QObject *parent, const QVariantList &); ~IndexColors() override; }; class KisFilterIndexColors : public KisColorTransformationFilter { public: KisFilterIndexColors(); public: KoColorTransformation* createTransformation(const KoColorSpace* cs, const KisFilterConfigurationSP config) const override; KisConfigWidget* createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP dev, bool useForMasks) const override; static inline KoID id() { return KoID("indexcolors", i18n("Index Colors")); } protected: - KisFilterConfigurationSP factoryConfiguration() const override; + KisFilterConfigurationSP defaultConfiguration() const override; }; class KisIndexColorTransformation : public KoColorTransformation { public: KisIndexColorTransformation(IndexColorPalette palette, const KoColorSpace* cs, int alphaSteps); void transform(const quint8* src, quint8* dst, qint32 nPixels) const override; private: const KoColorSpace* m_colorSpace; quint32 m_psize; IndexColorPalette m_palette; quint16 m_alphaStep; quint16 m_alphaHalfStep; }; #endif diff --git a/plugins/filters/noisefilter/noisefilter.cpp b/plugins/filters/noisefilter/noisefilter.cpp index 78cd05028d..3005f3c8c7 100644 --- a/plugins/filters/noisefilter/noisefilter.cpp +++ b/plugins/filters/noisefilter/noisefilter.cpp @@ -1,149 +1,149 @@ /* * This file is part of the KDE project * * 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. */ #include "noisefilter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_wdg_noise.h" #include "ui_wdgnoiseoptions.h" #include K_PLUGIN_FACTORY_WITH_JSON(KritaNoiseFilterFactory, "kritanoisefilter.json", registerPlugin();) KritaNoiseFilter::KritaNoiseFilter(QObject *parent, const QVariantList &) : QObject(parent) { KisFilterRegistry::instance()->add(new KisFilterNoise()); } KritaNoiseFilter::~KritaNoiseFilter() { } KisFilterNoise::KisFilterNoise() : KisFilter(id(), FiltersCategoryOtherId, i18n("&Random Noise...")) { setColorSpaceIndependence(FULLY_INDEPENDENT); setSupportsPainting(true); } -KisFilterConfigurationSP KisFilterNoise::factoryConfiguration() const +KisFilterConfigurationSP KisFilterNoise::defaultConfiguration() const { - KisFilterConfigurationSP config = new KisFilterConfiguration("noise", 1); + KisFilterConfigurationSP config = factoryConfiguration(); config->setProperty("level", 50); config->setProperty("opacity", 100); config->setProperty("seedThreshold", rand()); config->setProperty("seedRed", rand()); config->setProperty("seedGreen", rand()); config->setProperty("seedBlue", rand()); return config; } KisConfigWidget * KisFilterNoise::createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP dev, bool) const { Q_UNUSED(dev); return new KisWdgNoise((KisFilter*)this, (QWidget*)parent); } void KisFilterNoise::processImpl(KisPaintDeviceSP device, const QRect& applyRect, const KisFilterConfigurationSP config, KoUpdater* progressUpdater ) const { Q_ASSERT(!device.isNull()); const KoColorSpace * cs = device->colorSpace(); QVariant value; const int level = (config && config->getProperty("level", value)) ? value.toInt() : 50; const int opacity = (config && config->getProperty("opacity", value)) ? value.toInt() : 100; quint8* interm = new quint8[cs->pixelSize()]; const double threshold = (100.0 - level) * 0.01; qint16 weights[2]; weights[0] = (255 * opacity) / 100; weights[1] = 255 - weights[0]; const quint8* pixels[2]; pixels[0] = interm; KoMixColorsOp * mixOp = cs->mixColorsOp(); int seedThreshold = rand(); int seedRed = rand(); int seedGreen = rand(); int seedBlue = rand(); if (config) { seedThreshold = config->getInt("seedThreshold", seedThreshold); seedRed = config->getInt("seedRed", seedRed); seedGreen = config->getInt("seedGreen", seedGreen); seedBlue = config->getInt("seedBlue", seedBlue); } KisRandomGenerator randt(seedThreshold); KisRandomGenerator randr(seedRed); KisRandomGenerator randg(seedGreen); KisRandomGenerator randb(seedBlue); KisSequentialIteratorProgress it(device, applyRect, progressUpdater); while (it.nextPixel()) { if (randt.doubleRandomAt(it.x(), it.y()) > threshold) { // XXX: Added static_cast to get rid of warnings QColor c = qRgb(static_cast((double)randr.doubleRandomAt(it.x(), it.y()) * 255), static_cast((double)randg.doubleRandomAt(it.x(), it.y()) * 255), static_cast((double)randb.doubleRandomAt(it.x(), it.y()) * 255)); cs->fromQColor(c, interm, 0); pixels[1] = it.oldRawData(); mixOp->mixColors(pixels, weights, 2, it.rawData()); } } delete [] interm; } #include "noisefilter.moc" diff --git a/plugins/filters/noisefilter/noisefilter.h b/plugins/filters/noisefilter/noisefilter.h index 22cdcfc5cb..34bd76204a 100644 --- a/plugins/filters/noisefilter/noisefilter.h +++ b/plugins/filters/noisefilter/noisefilter.h @@ -1,56 +1,56 @@ /* * This file is part of Krita * * Copyright (c) 2006 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 NOISEFILTER_H #define NOISEFILTER_H #include #include #include "filter/kis_filter.h" class KisConfigWidget; class KritaNoiseFilter : public QObject { Q_OBJECT public: KritaNoiseFilter(QObject *parent, const QVariantList &); ~KritaNoiseFilter() override; }; class KisFilterNoise : public KisFilter { public: KisFilterNoise(); public: void processImpl(KisPaintDeviceSP device, const QRect& applyRect, const KisFilterConfigurationSP config, KoUpdater* progressUpdater ) const override; static inline KoID id() { return KoID("noise", i18n("Random Noise")); } - KisFilterConfigurationSP factoryConfiguration() const override; + KisFilterConfigurationSP defaultConfiguration() const override; KisConfigWidget * createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP dev, bool useForMasks) const override; }; #endif diff --git a/plugins/filters/oilpaintfilter/kis_oilpaint_filter.cpp b/plugins/filters/oilpaintfilter/kis_oilpaint_filter.cpp index fa6108937b..64ff6cf932 100644 --- a/plugins/filters/oilpaintfilter/kis_oilpaint_filter.cpp +++ b/plugins/filters/oilpaintfilter/kis_oilpaint_filter.cpp @@ -1,204 +1,204 @@ /* * This file is part of Krita * * Copyright (c) 2004 Michael Thaler * Copyright (c) 2008 Cyrille Berger * * ported from digikam, Copyrighted by Gilles Caulier, * Original Oilpaint algorithm copyrighted 2004 by * Pieter Z. Voloshyn . * * 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_oilpaint_filter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "widgets/kis_multi_integer_filter_widget.h" KisOilPaintFilter::KisOilPaintFilter() : KisFilter(id(), FiltersCategoryArtisticId, i18n("&Oilpaint...")) { setSupportsPainting(true); setSupportsThreading(false); setSupportsAdjustmentLayers(true); } void KisOilPaintFilter::processImpl(KisPaintDeviceSP device, const QRect& applyRect, const KisFilterConfigurationSP config, KoUpdater* progressUpdater ) const { Q_ASSERT(!device.isNull()); //read the filter configuration values from the KisFilterConfiguration object const quint32 brushSize = config ? config->getInt("brushSize", 1) : 1; const quint32 smooth = config ? config->getInt("smooth", 30) : 30; OilPaint(device, device, applyRect, brushSize, smooth, progressUpdater); } // This method have been ported from Pieter Z. Voloshyn algorithm code. /* Function to apply the OilPaint effect. * * data => The image data in RGBA mode. * w => Width of image. * h => Height of image. * BrushSize => Brush size. * Smoothness => Smooth value. * * Theory => Using MostFrequentColor function we take the main color in * a matrix and simply write at the original position. */ void KisOilPaintFilter::OilPaint(const KisPaintDeviceSP src, KisPaintDeviceSP dst, const QRect &applyRect, int BrushSize, int Smoothness, KoUpdater* progressUpdater) const { KisSequentialConstIteratorProgress it(src, applyRect, progressUpdater); KisSequentialIterator dstIt(dst, applyRect); while (it.nextPixel() && dstIt.nextPixel()) { MostFrequentColor(src, dstIt.rawData(), applyRect, it.x(), it.y(), BrushSize, Smoothness); } } // This method has been ported from Pieter Z. Voloshyn's algorithm code in Digikam. /* Function to determine the most frequent color in a matrix * * Bits => Bits array * Width => Image width * Height => Image height * X => Position horizontal * Y => Position vertical * Radius => Is the radius of the matrix to be analyzed * Intensity => Intensity to calculate * * Theory => This function creates a matrix with the analyzed pixel in * the center of this matrix and find the most frequenty color */ void KisOilPaintFilter::MostFrequentColor(KisPaintDeviceSP src, quint8* dst, const QRect& bounds, int X, int Y, int Radius, int Intensity) const { uint I; double Scale = Intensity / 255.0; // Alloc some arrays to be used uchar *IntensityCount = new uchar[(Intensity + 1) * sizeof(uchar)]; const KoColorSpace* cs = src->colorSpace(); QVector channel(cs->channelCount()); QVector* AverageChannels = new QVector[(Intensity + 1)]; // Erase the array memset(IntensityCount, 0, (Intensity + 1) * sizeof(uchar)); int startx = qMax(X - Radius, bounds.left()); int starty = qMax(Y - Radius, bounds.top()); int width = (2 * Radius) + 1; if ((startx + width - 1) > bounds.right()) width = bounds.right() - startx + 1; Q_ASSERT((startx + width - 1) <= bounds.right()); int height = (2 * Radius) + 1; if ((starty + height) > bounds.bottom()) height = bounds.bottom() - starty + 1; Q_ASSERT((starty + height - 1) <= bounds.bottom()); KisSequentialConstIterator srcIt(src, QRect(startx, starty, width, height)); while (srcIt.nextPixel()) { cs->normalisedChannelsValue(srcIt.rawDataConst(), channel); I = (uint)(cs->intensity8(srcIt.rawDataConst()) * Scale); IntensityCount[I]++; if (IntensityCount[I] == 1) { AverageChannels[I] = channel; } else { for (int i = 0; i < channel.size(); i++) { AverageChannels[I][i] += channel[i]; } } } I = 0; int MaxInstance = 0; for (int i = 0 ; i <= Intensity ; ++i) { if (IntensityCount[i] > MaxInstance) { I = i; MaxInstance = IntensityCount[i]; } } if (MaxInstance != 0) { channel = AverageChannels[I]; for (int i = 0; i < channel.size(); i++) { channel[i] /= MaxInstance; } cs->fromNormalisedChannelsValue(dst, channel); } else { memset(dst, 0, cs->pixelSize()); cs->setOpacity(dst, OPACITY_OPAQUE_U8, 1); } delete [] IntensityCount; // free all the arrays delete [] AverageChannels; } KisConfigWidget * KisOilPaintFilter::createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP, bool) const { vKisIntegerWidgetParam param; param.push_back(KisIntegerWidgetParam(1, 5, 1, i18n("Brush size"), "brushSize")); param.push_back(KisIntegerWidgetParam(10, 255, 30, i18nc("smooth out the painting strokes the filter creates", "Smooth"), "smooth")); KisMultiIntegerFilterWidget * w = new KisMultiIntegerFilterWidget(id().id(), parent, id().id(), param); - w->setConfiguration(factoryConfiguration()); + w->setConfiguration(defaultConfiguration()); return w; } -KisFilterConfigurationSP KisOilPaintFilter::factoryConfiguration() const +KisFilterConfigurationSP KisOilPaintFilter::defaultConfiguration() const { - KisFilterConfigurationSP config = new KisFilterConfiguration("oilpaint", 1); + KisFilterConfigurationSP config = factoryConfiguration(); config->setProperty("brushSize", 1); config->setProperty("smooth", 30); return config; } diff --git a/plugins/filters/oilpaintfilter/kis_oilpaint_filter.h b/plugins/filters/oilpaintfilter/kis_oilpaint_filter.h index 758dd7086a..2ce3fafc1a 100644 --- a/plugins/filters/oilpaintfilter/kis_oilpaint_filter.h +++ b/plugins/filters/oilpaintfilter/kis_oilpaint_filter.h @@ -1,51 +1,51 @@ /* * This file is part of the KDE project * * Copyright (c) Michael Thaler * * 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_OILPAINT_FILTER_H_ #define _KIS_OILPAINT_FILTER_H_ #include "filter/kis_filter.h" #include "kis_config_widget.h" class KisOilPaintFilter : public KisFilter { public: KisOilPaintFilter(); public: void processImpl(KisPaintDeviceSP device, const QRect& applyRect, const KisFilterConfigurationSP config, KoUpdater* progressUpdater ) const override; static inline KoID id() { return KoID("oilpaint", i18n("Oilpaint")); } - KisFilterConfigurationSP factoryConfiguration() const override; + KisFilterConfigurationSP defaultConfiguration() const override; public: KisConfigWidget * createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP dev, bool useForMasks) const override; private: void OilPaint(const KisPaintDeviceSP src, KisPaintDeviceSP dst, const QRect &applyRect, int BrushSize, int Smoothness, KoUpdater* progressUpdater) const; void MostFrequentColor(KisPaintDeviceSP src, quint8* dst, const QRect& bounds, int X, int Y, int Radius, int Intensity) const; }; #endif diff --git a/plugins/filters/palettize/palettize.cpp b/plugins/filters/palettize/palettize.cpp index 63515a5916..e37b519e6d 100644 --- a/plugins/filters/palettize/palettize.cpp +++ b/plugins/filters/palettize/palettize.cpp @@ -1,287 +1,287 @@ /* * This file is part of Krita * * Copyright (c) 2019 Carl Olsson * * 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 "palettize.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include K_PLUGIN_FACTORY_WITH_JSON(PalettizeFactory, "kritapalettize.json", registerPlugin();) Palettize::Palettize(QObject *parent, const QVariantList &) : QObject(parent) { KisFilterRegistry::instance()->add(new KisFilterPalettize()); } #include "palettize.moc" KisFilterPalettize::KisFilterPalettize() : KisFilter(id(), FiltersCategoryMapId, i18n("&Palettize...")) { setColorSpaceIndependence(FULLY_INDEPENDENT); setSupportsPainting(true); setShowConfigurationWidget(true); } KisPalettizeWidget::KisPalettizeWidget(QWidget* parent) : KisConfigWidget(parent) { Q_UNUSED(m_ditherPatternWidget); setupUi(this); paletteIconWidget->setFixedSize(32, 32); KoResourceServer* paletteServer = KoResourceServerProvider::instance()->paletteServer(); QSharedPointer paletteAdapter(new KoResourceServerAdapter(paletteServer)); m_paletteWidget = new KoResourceItemChooser(paletteAdapter, this, false); paletteIconWidget->setPopupWidget(m_paletteWidget); QObject::connect(m_paletteWidget, &KoResourceItemChooser::resourceSelected, paletteIconWidget, &KisIconWidget::setResource); QObject::connect(m_paletteWidget, &KoResourceItemChooser::resourceSelected, this, &KisConfigWidget::sigConfigurationItemChanged); QObject::connect(colorspaceComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, &KisConfigWidget::sigConfigurationItemChanged); QObject::connect(ditherGroupBox, &QGroupBox::toggled, this, &KisConfigWidget::sigConfigurationItemChanged); QObject::connect(ditherWidget, &KisDitherWidget::sigConfigurationItemChanged, this, &KisConfigWidget::sigConfigurationItemChanged); QObject::connect(colorModeComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, &KisConfigWidget::sigConfigurationItemChanged); offsetScaleSpinBox->setPrefix(QString("%1 ").arg(i18n("Offset Scale:"))); offsetScaleSpinBox->setRange(0.0, 1.0, 3); offsetScaleSpinBox->setSingleStep(0.125); QObject::connect(offsetScaleSpinBox, &KisDoubleSliderSpinBox::valueChanged, this, &KisConfigWidget::sigConfigurationItemChanged); QObject::connect(alphaGroupBox, &QGroupBox::toggled, this, &KisConfigWidget::sigConfigurationItemChanged); QObject::connect(alphaModeComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, &KisConfigWidget::sigConfigurationItemChanged); alphaClipSpinBox->setPrefix(QString("%1 ").arg(i18n("Clip:"))); alphaClipSpinBox->setRange(0.0, 1.0, 3); alphaClipSpinBox->setSingleStep(0.125); QObject::connect(alphaClipSpinBox, &KisDoubleSliderSpinBox::valueChanged, this, &KisConfigWidget::sigConfigurationItemChanged); alphaIndexSpinBox->setPrefix(QString("%1 ").arg(i18n("Index:"))); alphaIndexSpinBox->setRange(0, 255); QObject::connect(alphaIndexSpinBox, &KisSliderSpinBox::valueChanged, this, &KisConfigWidget::sigConfigurationItemChanged); QObject::connect(m_paletteWidget, &KoResourceItemChooser::resourceSelected, [this](){ const KoColorSet* const palette = static_cast(m_paletteWidget->currentResource()); alphaIndexSpinBox->setMaximum(palette ? int(palette->colorCount() - 1) : 0); alphaIndexSpinBox->setValue(std::min(alphaIndexSpinBox->value(), alphaIndexSpinBox->maximum())); }); QObject::connect(alphaDitherWidget, &KisDitherWidget::sigConfigurationItemChanged, this, &KisConfigWidget::sigConfigurationItemChanged); } void KisPalettizeWidget::setConfiguration(const KisPropertiesConfigurationSP config) { KoColorSet* palette = KoResourceServerProvider::instance()->paletteServer()->resourceByName(config->getString("palette")); if (palette) m_paletteWidget->setCurrentResource(palette); colorspaceComboBox->setCurrentIndex(config->getInt("colorspace")); ditherGroupBox->setChecked(config->getBool("ditherEnabled")); ditherWidget->setConfiguration(*config, "dither/"); colorModeComboBox->setCurrentIndex(config->getInt("dither/colorMode")); offsetScaleSpinBox->setValue(config->getDouble("dither/offsetScale")); alphaGroupBox->setChecked(config->getBool("alphaEnabled")); alphaModeComboBox->setCurrentIndex(config->getInt("alphaMode")); alphaClipSpinBox->setValue(config->getDouble("alphaClip")); alphaIndexSpinBox->setValue(config->getInt("alphaIndex")); alphaDitherWidget->setConfiguration(*config, "alphaDither/"); } KisPropertiesConfigurationSP KisPalettizeWidget::configuration() const { KisFilterConfigurationSP config = new KisFilterConfiguration("palettize", 1); if (m_paletteWidget->currentResource()) config->setProperty("palette", QVariant(m_paletteWidget->currentResource()->name())); config->setProperty("colorspace", colorspaceComboBox->currentIndex()); config->setProperty("ditherEnabled", ditherGroupBox->isChecked()); ditherWidget->configuration(*config, "dither/"); config->setProperty("dither/colorMode", colorModeComboBox->currentIndex()); config->setProperty("dither/offsetScale", offsetScaleSpinBox->value()); config->setProperty("alphaEnabled", alphaGroupBox->isChecked()); config->setProperty("alphaMode", alphaModeComboBox->currentIndex()); config->setProperty("alphaClip", alphaClipSpinBox->value()); config->setProperty("alphaIndex", alphaIndexSpinBox->value()); alphaDitherWidget->configuration(*config, "alphaDither/"); return config; } KisConfigWidget* KisFilterPalettize::createConfigurationWidget(QWidget *parent, const KisPaintDeviceSP dev, bool useForMasks) const { Q_UNUSED(dev) Q_UNUSED(useForMasks) return new KisPalettizeWidget(parent); } -KisFilterConfigurationSP KisFilterPalettize::factoryConfiguration() const +KisFilterConfigurationSP KisFilterPalettize::defaultConfiguration() const { - KisFilterConfigurationSP config = new KisFilterConfiguration("palettize", 1); + KisFilterConfigurationSP config = factoryConfiguration(); config->setProperty("palette", "Default"); config->setProperty("colorspace", Colorspace::Lab); config->setProperty("ditherEnabled", false); KisDitherWidget::factoryConfiguration(*config, "dither/"); config->setProperty("dither/colorMode", ColorMode::PerChannelOffset); config->setProperty("dither/offsetScale", 0.125); config->setProperty("alphaEnabled", true); config->setProperty("alphaMode", AlphaMode::Clip); config->setProperty("alphaClip", 0.5); config->setProperty("alphaIndex", 0); KisDitherWidget::factoryConfiguration(*config, "alphaDither/"); return config; } void KisFilterPalettize::processImpl(KisPaintDeviceSP device, const QRect& applyRect, const KisFilterConfigurationSP config, KoUpdater* progressUpdater) const { const KoColorSet* palette = KoResourceServerProvider::instance()->paletteServer()->resourceByName(config->getString("palette")); const int searchColorspace = config->getInt("colorspace"); const bool ditherEnabled = config->getBool("ditherEnabled"); const int colorMode = config->getInt("dither/colorMode"); const double offsetScale = config->getDouble("dither/offsetScale"); const bool alphaEnabled = config->getBool("alphaEnabled"); const int alphaMode = config->getInt("alphaMode"); const double alphaClip = config->getDouble("alphaClip"); const int alphaIndex = config->getInt("alphaIndex"); const KoColorSpace* colorspace = device->colorSpace(); const KoColorSpace* workColorspace = (searchColorspace == Colorspace::Lab ? KoColorSpaceRegistry::instance()->lab16() : KoColorSpaceRegistry::instance()->rgb16("sRGB-elle-V2-srgbtrc.icc")); const quint8 colorCount = ditherEnabled && colorMode == ColorMode::NearestColors ? 2 : 1; using SearchColor = boost::geometry::model::point; struct ColorCandidate { KoColor color; quint16 index; double distance; }; using SearchEntry = std::pair; boost::geometry::index::rtree> rtree; if (palette) { // Add palette colors to search tree quint16 index = 0; for (int row = 0; row < palette->rowCount(); ++row) { for (int column = 0; column < palette->columnCount(); ++column) { KisSwatch swatch = palette->getColorGlobal(column, row); if (swatch.isValid()) { KoColor color = swatch.color().convertedTo(colorspace); KoColor workColor = swatch.color().convertedTo(workColorspace); SearchColor searchColor; memcpy(&searchColor, workColor.data(), sizeof(SearchColor)); // Don't add duplicates so won't dither between identical colors std::vector result; rtree.query(boost::geometry::index::contains(searchColor), std::back_inserter(result)); if (result.empty()) rtree.insert(SearchEntry(searchColor, {color, index, 0.0})); } ++index; } } KisDitherUtil ditherUtil; if (ditherEnabled) ditherUtil.setConfiguration(*config, "dither/"); KisDitherUtil alphaDitherUtil; if (alphaMode == AlphaMode::Dither) alphaDitherUtil.setConfiguration(*config, "alphaDither/"); KisSequentialIteratorProgress pixel(device, applyRect, progressUpdater); while (pixel.nextPixel()) { KoColor workColor(pixel.oldRawData(), colorspace); workColor.convertTo(workColorspace); // Find dither threshold double threshold = 0.5; if (ditherEnabled) { threshold = ditherUtil.threshold(QPoint(pixel.x(), pixel.y())); // Traditional per-channel ordered dithering if (colorMode == ColorMode::PerChannelOffset) { QVector normalized(int(workColorspace->channelCount())); workColorspace->normalisedChannelsValue(workColor.data(), normalized); for (int channel = 0; channel < int(workColorspace->channelCount()); ++channel) { normalized[channel] += (threshold - 0.5) * offsetScale; } workColorspace->fromNormalisedChannelsValue(workColor.data(), normalized); } } // Get candidate colors and their distances SearchColor searchColor; memcpy(reinterpret_cast(&searchColor), workColor.data(), sizeof(SearchColor)); std::vector candidateColors; candidateColors.reserve(size_t(colorCount)); double distanceSum = 0.0; for (auto it = rtree.qbegin(boost::geometry::index::nearest(searchColor, colorCount)); it != rtree.qend() && candidateColors.size() < colorCount; ++it) { ColorCandidate candidate = it->second; candidate.distance = boost::geometry::distance(searchColor, it->first); candidateColors.push_back(candidate); distanceSum += candidate.distance; } // Select color candidate quint16 selected; if (ditherEnabled && colorMode == ColorMode::NearestColors) { // Sort candidates by palette order for stable dither color ordering const bool swap = candidateColors[0].index > candidateColors[1].index; selected = swap ^ (candidateColors[swap].distance / distanceSum > threshold); } else { selected = 0; } ColorCandidate &candidate = candidateColors[selected]; // Set alpha const double oldAlpha = colorspace->opacityF(pixel.oldRawData()); double newAlpha = oldAlpha; if (alphaEnabled && !(!ditherEnabled && alphaMode == AlphaMode::Dither)) { if (alphaMode == AlphaMode::Clip) { newAlpha = oldAlpha < alphaClip? 0.0 : 1.0; } else if (alphaMode == AlphaMode::Index) { newAlpha = (candidate.index == alphaIndex ? 0.0 : 1.0); } else if (alphaMode == AlphaMode::Dither) { newAlpha = oldAlpha < alphaDitherUtil.threshold(QPoint(pixel.x(), pixel.y())) ? 0.0 : 1.0; } } colorspace->setOpacity(candidate.color.data(), newAlpha, 1); // Copy color to pixel memcpy(pixel.rawData(), candidate.color.data(), colorspace->pixelSize()); } } } diff --git a/plugins/filters/palettize/palettize.h b/plugins/filters/palettize/palettize.h index a9eade61cd..9f37a4166e 100644 --- a/plugins/filters/palettize/palettize.h +++ b/plugins/filters/palettize/palettize.h @@ -1,85 +1,85 @@ /* * This file is part of the KDE project * * Copyright (c) 2019 Carl Olsson * * 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 PALETTIZE_H #define PALETTIZE_H #include "ui_palettize.h" #include #include #include #include #include #include #include class KoResourceItemChooser; class Palettize : public QObject { public: Palettize(QObject *parent, const QVariantList &); }; class KisPalettizeWidget : public KisConfigWidget, public Ui::Palettize { public: KisPalettizeWidget(QWidget* parent = 0); void setConfiguration(const KisPropertiesConfigurationSP) override; KisPropertiesConfigurationSP configuration() const override; private: KoResourceItemChooser* m_paletteWidget; KoResourceItemChooser* m_ditherPatternWidget; }; class KisFilterPalettize : public KisFilter { public: enum Colorspace { Lab, RGB }; enum AlphaMode { Clip, Index, Dither }; enum ThresholdMode { Pattern, Noise }; enum PatternValueMode { Auto, Lightness, Alpha }; enum ColorMode { PerChannelOffset, NearestColors }; KisFilterPalettize(); static inline KoID id() { return KoID("palettize", i18n("Palettize")); } KisConfigWidget* createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP dev, bool useForMasks) const override; - KisFilterConfigurationSP factoryConfiguration() const override; + KisFilterConfigurationSP defaultConfiguration() const override; void processImpl(KisPaintDeviceSP device, const QRect &applyRect, const KisFilterConfigurationSP config, KoUpdater *progressUpdater) const override; }; #endif diff --git a/plugins/filters/phongbumpmap/kis_phong_bumpmap_filter.cpp b/plugins/filters/phongbumpmap/kis_phong_bumpmap_filter.cpp index 3ec63ea454..8d7a1a2dfa 100644 --- a/plugins/filters/phongbumpmap/kis_phong_bumpmap_filter.cpp +++ b/plugins/filters/phongbumpmap/kis_phong_bumpmap_filter.cpp @@ -1,238 +1,238 @@ /* * Copyright (c) 2010-2011 José Luis Vergara * * 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_phong_bumpmap_filter.h" #include "kis_phong_bumpmap_config_widget.h" #include "phong_pixel_processor.h" #include "kis_debug.h" #include "kis_paint_device.h" #include "kis_config_widget.h" #include "KoUpdater.h" #include "kis_math_toolbox.h" #include "KoColorSpaceRegistry.h" #include #include #include #include "kis_iterator_ng.h" #include "kundo2command.h" #include "kis_painter.h" KisFilterPhongBumpmap::KisFilterPhongBumpmap() : KisFilter(KoID("phongbumpmap", i18n("Phong Bumpmap")), FiltersCategoryMapId, i18n("&Phong Bumpmap...")) { setColorSpaceIndependence(TO_LAB16); setSupportsPainting(true); setSupportsLevelOfDetail(true); } void KisFilterPhongBumpmap::processImpl(KisPaintDeviceSP device, const QRect& applyRect, const KisFilterConfigurationSP config, KoUpdater *progressUpdater ) const { if (!config) return; if (progressUpdater) progressUpdater->setProgress(0); QString userChosenHeightChannel = config->getString(PHONG_HEIGHT_CHANNEL, "FAIL"); bool m_usenormalmap = config->getBool(USE_NORMALMAP_IS_ENABLED); if (userChosenHeightChannel == "FAIL") { qDebug("FIX YOUR FILTER"); return; } KoChannelInfo *m_heightChannel = 0; Q_FOREACH (KoChannelInfo* channel, device->colorSpace()->channels()) { if (userChosenHeightChannel == channel->name()) { m_heightChannel = channel; } } if (!m_heightChannel) { m_heightChannel = device->colorSpace()->channels().first(); } KIS_ASSERT_RECOVER_RETURN(m_heightChannel); QRect inputArea = applyRect; QRect outputArea = applyRect; if (m_usenormalmap==false) { inputArea.adjust(-1, -1, 1, 1); } quint32 posup; quint32 posdown; quint32 posleft; quint32 posright; QColor I; //Reflected light if (progressUpdater) progressUpdater->setProgress(1); //======Preparation paraphlenalia======= //Hardcoded facts about Phong Bumpmap: it _will_ generate an RGBA16 bumpmap const quint8 BYTE_DEPTH_OF_BUMPMAP = 2; // 16 bits per channel const quint8 CHANNEL_COUNT_OF_BUMPMAP = 4; // RGBA const quint32 pixelsOfInputArea = abs(inputArea.width() * inputArea.height()); const quint32 pixelsOfOutputArea = abs(outputArea.width() * outputArea.height()); const quint8 pixelSize = BYTE_DEPTH_OF_BUMPMAP * CHANNEL_COUNT_OF_BUMPMAP; const quint32 bytesToFillBumpmapArea = pixelsOfOutputArea * pixelSize; QVector bumpmap(bytesToFillBumpmapArea); quint8 *bumpmapDataPointer = bumpmap.data(); quint32 ki = KoChannelInfo::displayPositionToChannelIndex(m_heightChannel->displayPosition(), device->colorSpace()->channels()); PhongPixelProcessor tileRenderer(pixelsOfInputArea, config); if (progressUpdater) progressUpdater->setProgress(2); //===============RENDER================= QVector toDoubleFuncPtr(device->colorSpace()->channels().count()); KisMathToolbox mathToolbox; if (!mathToolbox.getToDoubleChannelPtr(device->colorSpace()->channels(), toDoubleFuncPtr)) { return; } KisHLineConstIteratorSP iterator; quint32 curPixel = 0; iterator = device->createHLineConstIteratorNG(inputArea.x(), inputArea.y(), inputArea.width() ); if (m_usenormalmap==false) { for (qint32 srcRow = 0; srcRow < inputArea.height(); ++srcRow) { do { const quint8 *data = iterator->oldRawData(); tileRenderer.realheightmap[curPixel] = toDoubleFuncPtr[ki](data, device->colorSpace()->channels()[ki]->pos()); curPixel++; } while (iterator->nextPixel()); iterator->nextRow(); } if (progressUpdater) progressUpdater->setProgress(50); const int tileHeightMinus1 = inputArea.height() - 1; const int tileWidthMinus1 = inputArea.width() - 1; // Foreach INNER pixel in tile for (int y = 1; y < tileHeightMinus1; ++y) { for (int x = 1; x < tileWidthMinus1; ++x) { posup = (y + 1) * inputArea.width() + x; posdown = (y - 1) * inputArea.width() + x; posleft = y * inputArea.width() + x - 1; posright = y * inputArea.width() + x + 1; memcpy(bumpmapDataPointer, tileRenderer.IlluminatePixelFromHeightmap(posup, posdown, posleft, posright).data(), pixelSize); bumpmapDataPointer += pixelSize; } } } else { for (qint32 srcRow = 0; srcRow < inputArea.height(); ++srcRow) { do { const quint8 *data = iterator->oldRawData(); tileRenderer.realheightmap[curPixel] = toDoubleFuncPtr[ki](data, device->colorSpace()->channels()[ki]->pos()); QVector current_pixel_values(4); device->colorSpace()->normalisedChannelsValue(data, current_pixel_values ); //dbgKrita<< "Vector:" << current_pixel_values[2] << "," << current_pixel_values[1] << "," << current_pixel_values[0]; memcpy(bumpmapDataPointer, tileRenderer.IlluminatePixelFromNormalmap(current_pixel_values[2], current_pixel_values[1], current_pixel_values[0]).data(), pixelSize); curPixel++; //pointer that crashes here, but not in the other if statement. bumpmapDataPointer += pixelSize; } while (iterator->nextPixel()); iterator->nextRow(); } } if (progressUpdater) progressUpdater->setProgress(90); KisPaintDeviceSP bumpmapPaintDevice = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb16()); bumpmapPaintDevice->writeBytes(bumpmap.data(), outputArea.x(), outputArea.y(), outputArea.width(), outputArea.height()); bumpmapPaintDevice->convertTo(device->colorSpace(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); KisPainter copier(device); copier.bitBlt(outputArea.x(), outputArea.y(), bumpmapPaintDevice, outputArea.x(), outputArea.y(), outputArea.width(), outputArea.height()); //device->prepareClone(bumpmapPaintDevice); //device->makeCloneFrom(bumpmapPaintDevice, bumpmapPaintDevice->extent()); // THIS COULD BE BUG GY if (progressUpdater) progressUpdater->setProgress(100); } -KisFilterConfigurationSP KisFilterPhongBumpmap::factoryConfiguration() const +KisFilterConfigurationSP KisFilterPhongBumpmap::defaultConfiguration() const { - KisFilterConfigurationSP config = new KisFilterConfiguration(id(), 2); + KisFilterConfigurationSP config = factoryConfiguration(); config->setProperty(PHONG_AMBIENT_REFLECTIVITY, 0.2); config->setProperty(PHONG_DIFFUSE_REFLECTIVITY, 0.5); config->setProperty(PHONG_SPECULAR_REFLECTIVITY, 0.3); config->setProperty(PHONG_SHINYNESS_EXPONENT, 2); config->setProperty(USE_NORMALMAP_IS_ENABLED, false); config->setProperty(PHONG_DIFFUSE_REFLECTIVITY_IS_ENABLED, true); config->setProperty(PHONG_SPECULAR_REFLECTIVITY_IS_ENABLED, true); // Indexes are off by 1 simply because arrays start at 0 and the GUI naming scheme started at 1 config->setProperty(PHONG_ILLUMINANT_IS_ENABLED[0], true); config->setProperty(PHONG_ILLUMINANT_IS_ENABLED[1], true); config->setProperty(PHONG_ILLUMINANT_IS_ENABLED[2], false); config->setProperty(PHONG_ILLUMINANT_IS_ENABLED[3], false); config->setProperty(PHONG_ILLUMINANT_COLOR[0], QColor(255, 255, 0)); config->setProperty(PHONG_ILLUMINANT_COLOR[1], QColor(255, 0, 0)); config->setProperty(PHONG_ILLUMINANT_COLOR[2], QColor(0, 0, 255)); config->setProperty(PHONG_ILLUMINANT_COLOR[3], QColor(0, 255, 0)); config->setProperty(PHONG_ILLUMINANT_AZIMUTH[0], 50); config->setProperty(PHONG_ILLUMINANT_AZIMUTH[1], 100); config->setProperty(PHONG_ILLUMINANT_AZIMUTH[2], 150); config->setProperty(PHONG_ILLUMINANT_AZIMUTH[3], 200); config->setProperty(PHONG_ILLUMINANT_INCLINATION[0], 25); config->setProperty(PHONG_ILLUMINANT_INCLINATION[1], 20); config->setProperty(PHONG_ILLUMINANT_INCLINATION[2], 30); config->setProperty(PHONG_ILLUMINANT_INCLINATION[3], 40); return config; } QRect KisFilterPhongBumpmap::neededRect(const QRect &rect, const KisFilterConfigurationSP /*config*/, int /*lod*/) const { return rect.adjusted(-1, -1, 1, 1); } QRect KisFilterPhongBumpmap::changedRect(const QRect &rect, const KisFilterConfigurationSP /*config*/, int /*lod*/) const { return rect; } KisConfigWidget *KisFilterPhongBumpmap::createConfigurationWidget(QWidget *parent, const KisPaintDeviceSP dev, bool) const { KisPhongBumpmapConfigWidget *w = new KisPhongBumpmapConfigWidget(dev, parent); return w; } diff --git a/plugins/filters/phongbumpmap/kis_phong_bumpmap_filter.h b/plugins/filters/phongbumpmap/kis_phong_bumpmap_filter.h index 56152a9183..e6d6dd3b4e 100644 --- a/plugins/filters/phongbumpmap/kis_phong_bumpmap_filter.h +++ b/plugins/filters/phongbumpmap/kis_phong_bumpmap_filter.h @@ -1,54 +1,54 @@ /* * Copyright (c) 2010 Dmitry Kazakov * Copyright (c) 2010-2011 José Luis Vergara * * 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_PHONG_BUMPMAP_FILTER_H #define KIS_PHONG_BUMPMAP_FILTER_H #include #include /** * This class is an implementation of the phong illumination model. * It uses a heightmap as an input mesh (normally taken from 1 * channel of a colorspace) to achieve a bumpmapping effect with * multiple illumination sources. */ class KisFilterPhongBumpmap : public KisFilter { public: KisFilterPhongBumpmap(); public: void processImpl(KisPaintDeviceSP device, const QRect& applyRect, const KisFilterConfigurationSP config, KoUpdater *progressUpdater ) const override; QRect neededRect(const QRect &rect, const KisFilterConfigurationSP config, int lod) const override; QRect changedRect(const QRect &rect, const KisFilterConfigurationSP config, int lod) const override; KisConfigWidget *createConfigurationWidget(QWidget *parent, const KisPaintDeviceSP dev, bool useForMasks) const override; - KisFilterConfigurationSP factoryConfiguration() const override; + KisFilterConfigurationSP defaultConfiguration() const override; private: //bool m_usenormalmap; }; #endif //KIS_PHONG_BUMPMAP_FILTER_H diff --git a/plugins/filters/pixelizefilter/kis_pixelize_filter.cpp b/plugins/filters/pixelizefilter/kis_pixelize_filter.cpp index 4d2294249d..7885ef47cc 100644 --- a/plugins/filters/pixelizefilter/kis_pixelize_filter.cpp +++ b/plugins/filters/pixelizefilter/kis_pixelize_filter.cpp @@ -1,163 +1,163 @@ /* * This file is part of Krita * * Copyright (c) 2005 Michael Thaler * * ported from Gimp, Copyright (C) 1997 Eiichi Takamori * original pixelize.c for GIMP 0.54 by Tracy Scott * * 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_pixelize_filter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "widgets/kis_multi_integer_filter_widget.h" #include #include #include "kis_algebra_2d.h" #include "kis_lod_transform.h" KisPixelizeFilter::KisPixelizeFilter() : KisFilter(id(), FiltersCategoryArtisticId, i18n("&Pixelize...")) { setSupportsPainting(true); setSupportsThreading(true); setSupportsAdjustmentLayers(true); setSupportsLevelOfDetail(true); setColorSpaceIndependence(FULLY_INDEPENDENT); } void KisPixelizeFilter::processImpl(KisPaintDeviceSP device, const QRect& applyRect, const KisFilterConfigurationSP config, KoUpdater* progressUpdater ) const { Q_ASSERT(device); KisLodTransformScalar t(device); const int pixelWidth = qCeil(t.scale(config ? qMax(1, config->getInt("pixelWidth", 10)) : 10)); const int pixelHeight = qCeil(t.scale(config ? qMax(1, config->getInt("pixelHeight", 10)) : 10)); const qint32 pixelSize = device->pixelSize(); const QRect deviceBounds = device->defaultBounds()->bounds(); const int bufferSize = pixelSize * pixelWidth * pixelHeight; QScopedArrayPointer buffer(new quint8[bufferSize]); KoColor pixelColor(Qt::black, device->colorSpace()); KoMixColorsOp *mixOp = device->colorSpace()->mixColorsOp(); using namespace KisAlgebra2D; const qint32 firstCol = divideFloor(applyRect.x(), pixelWidth); const qint32 firstRow = divideFloor(applyRect.y(), pixelHeight); const qint32 lastCol = divideFloor(applyRect.x() + applyRect.width() - 1, pixelWidth); const qint32 lastRow = divideFloor(applyRect.y() + applyRect.height() - 1, pixelHeight); progressUpdater->setRange(firstRow, lastRow); for(qint32 i = firstRow; i <= lastRow; i++) { for(qint32 j = firstCol; j <= lastCol; j++) { const QRect maxPatchRect(j * pixelWidth, i * pixelHeight, pixelWidth, pixelHeight); const QRect pixelRect = maxPatchRect & deviceBounds; const int numColors = pixelRect.width() * pixelRect.height(); //read KisSequentialConstIterator srcIt(device, pixelRect); memset(buffer.data(), 0, bufferSize); quint8 *bufferPtr = buffer.data(); while (srcIt.nextPixel()) { memcpy(bufferPtr, srcIt.oldRawData(), pixelSize); bufferPtr += pixelSize; } // mix all the colors mixOp->mixColors(buffer.data(), numColors, pixelColor.data()); // write only colors in applyRect const QRect writeRect = pixelRect & applyRect; KisSequentialIterator dstIt(device, writeRect); while (dstIt.nextPixel()) { memcpy(dstIt.rawData(), pixelColor.data(), pixelSize); } } progressUpdater->setValue(i); } } QRect KisPixelizeFilter::neededRect(const QRect &rect, const KisFilterConfigurationSP config, int lod) const { KisLodTransformScalar t(lod); const int pixelWidth = qCeil(t.scale(config ? qMax(1, config->getInt("pixelWidth", 10)) : 10)); const int pixelHeight = qCeil(t.scale(config ? qMax(1, config->getInt("pixelHeight", 10)) : 10)); // TODO: make more precise calculation of the rect, including the alignment return rect.adjusted(-2*pixelWidth, -2*pixelHeight, 2*pixelWidth, 2*pixelHeight); } QRect KisPixelizeFilter::changedRect(const QRect &rect, const KisFilterConfigurationSP config, int lod) const { return neededRect(rect, config, lod); } KisConfigWidget * KisPixelizeFilter::createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP, bool) const { vKisIntegerWidgetParam param; param.push_back(KisIntegerWidgetParam(2, 512, 10, i18n("Pixel width"), "pixelWidth")); param.push_back(KisIntegerWidgetParam(2, 512, 10, i18n("Pixel height"), "pixelHeight")); return new KisMultiIntegerFilterWidget(id().id(), parent, id().id(), param); } -KisFilterConfigurationSP KisPixelizeFilter::factoryConfiguration() const +KisFilterConfigurationSP KisPixelizeFilter::defaultConfiguration() const { - KisFilterConfigurationSP config = new KisFilterConfiguration("pixelize", 1); + KisFilterConfigurationSP config = factoryConfiguration(); config->setProperty("pixelWidth", 10); config->setProperty("pixelHeight", 10); return config; } diff --git a/plugins/filters/pixelizefilter/kis_pixelize_filter.h b/plugins/filters/pixelizefilter/kis_pixelize_filter.h index d1023251dc..6e385ae81b 100644 --- a/plugins/filters/pixelizefilter/kis_pixelize_filter.h +++ b/plugins/filters/pixelizefilter/kis_pixelize_filter.h @@ -1,50 +1,50 @@ /* * This file is part of the KDE project * * Copyright (c) Michael Thaler * * 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_PIXELIZE_FILTER_H_ #define _KIS_PIXELIZE_FILTER_H_ #include "filter/kis_filter.h" #include "kis_config_widget.h" class KisPixelizeFilter : public KisFilter { public: KisPixelizeFilter(); public: void processImpl(KisPaintDeviceSP device, const QRect& applyRect, const KisFilterConfigurationSP config, KoUpdater* progressUpdater) const override; static inline KoID id() { return KoID("pixelize", i18n("Pixelize")); } QRect neededRect(const QRect & rect, const KisFilterConfigurationSP config, int lod) const override; QRect changedRect(const QRect & rect, const KisFilterConfigurationSP config, int lod) const override; public: KisConfigWidget * createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP dev, bool useForMasks) const override; - KisFilterConfigurationSP factoryConfiguration() const override; + KisFilterConfigurationSP defaultConfiguration() const override; }; #endif diff --git a/plugins/filters/posterize/posterize.cpp b/plugins/filters/posterize/posterize.cpp index 776371e996..09d677ab29 100644 --- a/plugins/filters/posterize/posterize.cpp +++ b/plugins/filters/posterize/posterize.cpp @@ -1,128 +1,128 @@ /* * Copyright (c) 2014 Manuel Riecke * * 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 "posterize.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include K_PLUGIN_FACTORY_WITH_JSON(PosterizeFactory, "kritaposterize.json", registerPlugin();) Posterize::Posterize(QObject *parent, const QVariantList &) : QObject(parent) { KisFilterRegistry::instance()->add(KisFilterSP(new KisFilterPosterize())); } Posterize::~Posterize() { } KisFilterPosterize::KisFilterPosterize() : KisColorTransformationFilter(id(), FiltersCategoryArtisticId, i18n("&Posterize...")) { setColorSpaceIndependence(FULLY_INDEPENDENT); setSupportsPainting(true); setShowConfigurationWidget(true); } KoColorTransformation* KisFilterPosterize::createTransformation(const KoColorSpace* cs, const KisFilterConfigurationSP config) const { return new KisPosterizeColorTransformation(config->getInt("steps", 16), cs); } KisPosterizeColorTransformation::KisPosterizeColorTransformation(int steps, const KoColorSpace* cs) : m_colorSpace(cs), m_psize(cs->pixelSize()) { m_step = KoColorSpaceMathsTraits::max / steps; m_halfStep = m_step / 2; m_fromConversion = KoColorSpaceRegistry::instance()->createColorConverter( m_colorSpace, KoColorSpaceRegistry::instance()->rgb16("sRGB-elle-V2-srgbtrc.icc"), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); m_toConversion = KoColorSpaceRegistry::instance()->createColorConverter( KoColorSpaceRegistry::instance()->rgb16("sRGB-elle-V2-srgbtrc.icc"), m_colorSpace, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); } KisPosterizeColorTransformation::~KisPosterizeColorTransformation() { delete m_fromConversion; delete m_toConversion; } KisConfigWidget* KisFilterPosterize::createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP dev, bool) const { Q_UNUSED(dev); vKisIntegerWidgetParam param; param.push_back(KisIntegerWidgetParam(2, 128, 16, i18n("Steps"), "steps")); return new KisMultiIntegerFilterWidget(id().id(), parent, id().id(), param); } -KisFilterConfigurationSP KisFilterPosterize::factoryConfiguration() const +KisFilterConfigurationSP KisFilterPosterize::defaultConfiguration() const { - KisColorTransformationConfigurationSP config = new KisColorTransformationConfiguration(id().id(), 0); + KisFilterConfigurationSP config = factoryConfiguration(); config->setProperty("steps", 16); return config; } void KisPosterizeColorTransformation::transform(const quint8* src, quint8* dst, qint32 nPixels) const { quint16 m_rgba[4]; quint16 m_mod[4]; while (nPixels--) { m_fromConversion->transform(src, reinterpret_cast(m_rgba), 1); m_mod[0] = m_rgba[0] % m_step; m_mod[1] = m_rgba[1] % m_step; m_mod[2] = m_rgba[2] % m_step; m_mod[3] = m_rgba[3] % m_step; m_rgba[0] = m_rgba[0] + (m_mod[0] > m_halfStep ? m_step - m_mod[0] : -m_mod[0]); m_rgba[1] = m_rgba[1] + (m_mod[1] > m_halfStep ? m_step - m_mod[1] : -m_mod[1]); m_rgba[2] = m_rgba[2] + (m_mod[2] > m_halfStep ? m_step - m_mod[2] : -m_mod[2]); m_rgba[3] = m_rgba[3] + (m_mod[3] > m_halfStep ? m_step - m_mod[3] : -m_mod[3]); m_toConversion->transform(reinterpret_cast(m_rgba), dst, 1); src += m_psize; dst += m_psize; } } #include "posterize.moc" diff --git a/plugins/filters/posterize/posterize.h b/plugins/filters/posterize/posterize.h index 2774a94d33..2b3fca42d0 100644 --- a/plugins/filters/posterize/posterize.h +++ b/plugins/filters/posterize/posterize.h @@ -1,64 +1,64 @@ /* * Copyright (c) 2014 Manuel Riecke * * 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 POSTERIZE_H #define POSTERIZE_H #include #include #include "filter/kis_color_transformation_filter.h" #include "kis_config_widget.h" class Posterize : public QObject { Q_OBJECT public: Posterize(QObject *parent, const QVariantList &); ~Posterize() override; }; class KisFilterPosterize : public KisColorTransformationFilter { public: KisFilterPosterize(); public: KoColorTransformation* createTransformation(const KoColorSpace* cs, const KisFilterConfigurationSP config) const override; KisConfigWidget* createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP dev, bool useForMasks) const override; static inline KoID id() { return KoID("posterize", i18n("Posterize")); } protected: - KisFilterConfigurationSP factoryConfiguration() const override; + KisFilterConfigurationSP defaultConfiguration() const override; }; class KisPosterizeColorTransformation : public KoColorTransformation { public: KisPosterizeColorTransformation(int steps, const KoColorSpace* cs); ~KisPosterizeColorTransformation() override; void transform(const quint8* src, quint8* dst, qint32 nPixels) const override; private: const KoColorSpace* m_colorSpace; quint32 m_psize; quint16 m_step; quint16 m_halfStep; KoColorConversionTransformation* m_fromConversion; KoColorConversionTransformation* m_toConversion; }; #endif diff --git a/plugins/filters/raindropsfilter/kis_raindrops_filter.cpp b/plugins/filters/raindropsfilter/kis_raindrops_filter.cpp index c63b366a8f..fa3d88c75b 100644 --- a/plugins/filters/raindropsfilter/kis_raindrops_filter.cpp +++ b/plugins/filters/raindropsfilter/kis_raindrops_filter.cpp @@ -1,397 +1,397 @@ /* * This file is part of the KDE project * * Copyright (c) 2004 Michael Thaler * * ported from digikam, copyrighted 2004 by Gilles Caulier, * Original RainDrops algorithm copyrighted 2004 by * Pieter Z. Voloshyn . * * 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_raindrops_filter.h" #include #include #include #include #include #include #include #include #include #include "KoIntegerMaths.h" #include #include #include #include #include #include #include #include #include #include #include #include "widgets/kis_multi_integer_filter_widget.h" KisRainDropsFilter::KisRainDropsFilter() : KisFilter(id(), FiltersCategoryArtisticId, i18n("&Raindrops...")) { setSupportsPainting(false); setSupportsThreading(false); - setSupportsAdjustmentLayers(true); + setSupportsAdjustmentLayers(false); } // This method have been ported from Pieter Z. Voloshyn algorithm code. /* Function to apply the RainDrops effect (inspired from Jason Waltman code) * * data => The image data in RGBA mode. * Width => Width of image. * Height => Height of image. * DropSize => Raindrop size * number => Maximum number of raindrops * fishEyes => FishEye coefficient * * Theory => This functions does several math's functions and the engine * is simple to understand, but a little hard to implement. A * control will indicate if there is or not a raindrop in that * area, if not, a fisheye effect with a random size (max=DropSize) * will be applied, after this, a shadow will be applied too. * and after this, a blur function will finish the effect. */ void KisRainDropsFilter::processImpl(KisPaintDeviceSP device, const QRect& applyRect, const KisFilterConfigurationSP config, KoUpdater* progressUpdater ) const { QPoint srcTopLeft = applyRect.topLeft(); Q_ASSERT(device); //read the filter configuration values from the KisFilterConfiguration object quint32 DropSize = config->getInt("dropSize", 80); quint32 number = config->getInt("number", 80); quint32 fishEyes = config->getInt("fishEyes", 30); qsrand(config->getInt("seed")); if (fishEyes <= 0) fishEyes = 1; if (fishEyes > 100) fishEyes = 100; int Width = applyRect.width(); int Height = applyRect.height(); bool** BoolMatrix = CreateBoolArray(Width, Height); int i, j, k, l, m, n; // loop variables int Bright; // Bright value for shadows and highlights int x, y; // center coordinates int Counter = 0; // Counter (duh !) int NewSize; // Size of current raindrop int halfSize; // Half of the current raindrop int Radius; // Maximum radius for raindrop int BlurRadius; // Blur Radius int BlurPixels; double r, a; // polar coordinates double OldRadius; // Radius before processing double NewfishEyes = (double)fishEyes * 0.01; // FishEye fishEyesicients double s; double R, G, B; bool FindAnother = false; // To search for good coordinates const KoColorSpace * cs = device->colorSpace(); // Init boolean Matrix. for (i = 0 ; i < Width; ++i) { for (j = 0 ; j < Height; ++j) { BoolMatrix[i][j] = false; } } progressUpdater->setRange(0, number); KisRandomAccessorSP dstAccessor = device->createRandomAccessorNG(srcTopLeft.x(), srcTopLeft.y()); for (uint NumBlurs = 0; NumBlurs <= number; ++NumBlurs) { NewSize = (int)(qrand() * ((double)(DropSize - 5) / RAND_MAX) + 5); halfSize = NewSize / 2; Radius = halfSize; s = Radius / log(NewfishEyes * Radius + 1); Counter = 0; do { FindAnother = false; y = (int)(qrand() * ((double)(Width - 1) / RAND_MAX)); x = (int)(qrand() * ((double)(Height - 1) / RAND_MAX)); if (BoolMatrix[y][x]) FindAnother = true; else for (i = x - halfSize ; i <= x + halfSize; i++) for (j = y - halfSize ; j <= y + halfSize; j++) if ((i >= 0) && (i < Height) && (j >= 0) && (j < Width)) if (BoolMatrix[j][i]) FindAnother = true; Counter++; } while (FindAnother && Counter < 10000); if (Counter >= 10000) { NumBlurs = number; break; } for (i = -1 * halfSize ; i < NewSize - halfSize; i++) { for (j = -1 * halfSize ; j < NewSize - halfSize; j++) { r = sqrt((double)i * i + j * j); a = atan2(static_cast(i), static_cast(j)); if (r <= Radius) { OldRadius = r; r = (exp(r / s) - 1) / NewfishEyes; k = x + (int)(r * sin(a)); l = y + (int)(r * cos(a)); m = x + i; n = y + j; if ((k >= 0) && (k < Height) && (l >= 0) && (l < Width)) { if ((m >= 0) && (m < Height) && (n >= 0) && (n < Width)) { Bright = 0; if (OldRadius >= 0.9 * Radius) { if ((a <= 0) && (a > -2.25)) Bright = -80; else if ((a <= -2.25) && (a > -2.5)) Bright = -40; else if ((a <= 0.25) && (a > 0)) Bright = -40; } else if (OldRadius >= 0.8 * Radius) { if ((a <= -0.75) && (a > -1.50)) Bright = -40; else if ((a <= 0.10) && (a > -0.75)) Bright = -30; else if ((a <= -1.50) && (a > -2.35)) Bright = -30; } else if (OldRadius >= 0.7 * Radius) { if ((a <= -0.10) && (a > -2.0)) Bright = -20; else if ((a <= 2.50) && (a > 1.90)) Bright = 60; } else if (OldRadius >= 0.6 * Radius) { if ((a <= -0.50) && (a > -1.75)) Bright = -20; else if ((a <= 0) && (a > -0.25)) Bright = 20; else if ((a <= -2.0) && (a > -2.25)) Bright = 20; } else if (OldRadius >= 0.5 * Radius) { if ((a <= -0.25) && (a > -0.50)) Bright = 30; else if ((a <= -1.75) && (a > -2.0)) Bright = 30; } else if (OldRadius >= 0.4 * Radius) { if ((a <= -0.5) && (a > -1.75)) Bright = 40; } else if (OldRadius >= 0.3 * Radius) { if ((a <= 0) && (a > -2.25)) Bright = 30; } else if (OldRadius >= 0.2 * Radius) { if ((a <= -0.5) && (a > -1.75)) Bright = 20; } BoolMatrix[n][m] = true; QColor originalColor; dstAccessor->moveTo(srcTopLeft.x() + l, srcTopLeft.y() + k); cs->toQColor(dstAccessor->oldRawData(), &originalColor); int newRed = CLAMP(originalColor.red() + Bright, 0, quint8_MAX); int newGreen = CLAMP(originalColor.green() + Bright, 0, quint8_MAX); int newBlue = CLAMP(originalColor.blue() + Bright, 0, quint8_MAX); QColor newColor; newColor.setRgb(newRed, newGreen, newBlue); dstAccessor->moveTo(srcTopLeft.x() + n, srcTopLeft.y() + m); cs->fromQColor(newColor, dstAccessor->rawData()); } } } } } BlurRadius = NewSize / 25 + 1; for (i = -1 * halfSize - BlurRadius ; i < NewSize - halfSize + BlurRadius; i++) { for (j = -1 * halfSize - BlurRadius; j < NewSize - halfSize + BlurRadius; ++j) { r = sqrt((double)i * i + j * j); if (r <= Radius * 1.1) { R = G = B = 0; BlurPixels = 0; for (k = -1 * BlurRadius; k < BlurRadius + 1; k++) for (l = -1 * BlurRadius; l < BlurRadius + 1; l++) { m = x + i + k; n = y + j + l; if ((m >= 0) && (m < Height) && (n >= 0) && (n < Width)) { QColor color; dstAccessor->moveTo(srcTopLeft.x() + n, srcTopLeft.y() + m); cs->toQColor(dstAccessor->rawData(), &color); R += color.red(); G += color.green(); B += color.blue(); BlurPixels++; } } m = x + i; n = y + j; if ((m >= 0) && (m < Height) && (n >= 0) && (n < Width)) { QColor color; color.setRgb((int)(R / BlurPixels), (int)(G / BlurPixels), (int)(B / BlurPixels)); dstAccessor->moveTo(srcTopLeft.x() + n, srcTopLeft.y() + m); cs->fromQColor(color, dstAccessor->rawData()); } } } } progressUpdater->setValue(NumBlurs); } FreeBoolArray(BoolMatrix, Width); } // This method have been ported from Pieter Z. Voloshyn algorithm code. /* Function to free a dynamic boolean array * * lpbArray => Dynamic boolean array * Columns => The array bidimension value * * Theory => An easy to understand 'for' statement */ void KisRainDropsFilter::FreeBoolArray(bool** lpbArray, uint Columns) const { for (uint i = 0; i < Columns; ++i) free(lpbArray[i]); free(lpbArray); } /* Function to create a bidimentional dynamic boolean array * * Columns => Number of columns * Rows => Number of rows * * Theory => Using 'for' statement, we can alloc multiple dynamic arrays * To create more dimensions, just add some 'for's, ok? */ bool** KisRainDropsFilter::CreateBoolArray(uint Columns, uint Rows) const { bool** lpbArray = 0; lpbArray = (bool**) malloc(Columns * sizeof(bool*)); if (lpbArray == 0) return (0); for (uint i = 0; i < Columns; ++i) { lpbArray[i] = (bool*) malloc(Rows * sizeof(bool)); if (lpbArray[i] == 0) { FreeBoolArray(lpbArray, Columns); return (0); } } return (lpbArray); } // This method have been ported from Pieter Z. Voloshyn algorithm code. /* This function limits the RGB values * * ColorValue => Here, is an RGB value to be analyzed * * Theory => A color is represented in RGB value (e.g. 0xFFFFFF is * white color). But R, G and B values have 256 values to be used * so, this function analyzes the value and limits to this range */ uchar KisRainDropsFilter::LimitValues(int ColorValue) const { if (ColorValue > 255) // MAX = 255 ColorValue = 255; if (ColorValue < 0) // MIN = 0 ColorValue = 0; return ((uchar) ColorValue); } KisConfigWidget * KisRainDropsFilter::createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP, bool) const { vKisIntegerWidgetParam param; param.push_back(KisIntegerWidgetParam(1, 200, 80, i18n("Drop size"), "dropsize")); param.push_back(KisIntegerWidgetParam(1, 500, 80, i18n("Number of drops"), "number")); param.push_back(KisIntegerWidgetParam(1, 100, 30, i18n("Fish eyes"), "fishEyes")); KisMultiIntegerFilterWidget * w = new KisMultiIntegerFilterWidget(id().id(), parent, id().id(), param); - w->setConfiguration(factoryConfiguration()); + w->setConfiguration(defaultConfiguration()); return w; } -KisFilterConfigurationSP KisRainDropsFilter::factoryConfiguration() const +KisFilterConfigurationSP KisRainDropsFilter::defaultConfiguration() const { - KisFilterConfigurationSP config = new KisFilterConfiguration("raindrops", 2); + KisFilterConfigurationSP config = factoryConfiguration(); config->setProperty("dropsize", 80); config->setProperty("number", 80); config->setProperty("fishEyes", 30); config->setProperty("seed", QTime::currentTime().msec()); return config; } diff --git a/plugins/filters/raindropsfilter/kis_raindrops_filter.h b/plugins/filters/raindropsfilter/kis_raindrops_filter.h index c394a3e992..a155a7bb56 100644 --- a/plugins/filters/raindropsfilter/kis_raindrops_filter.h +++ b/plugins/filters/raindropsfilter/kis_raindrops_filter.h @@ -1,51 +1,51 @@ /* * This file is part of Krita * * Copyright (c) Michael Thaler * * 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_RAINDROPS_FILTER_H_ #define _KIS_RAINDROPS_FILTER_H_ #include "filter/kis_filter.h" #include "kis_config_widget.h" #include "kis_paint_device.h" class KisRainDropsFilter : public KisFilter { public: KisRainDropsFilter(); public: void processImpl(KisPaintDeviceSP device, const QRect& applyRect, const KisFilterConfigurationSP config, KoUpdater* progressUpdater) const override; static inline KoID id() { return KoID("raindrops", i18n("Raindrops")); } - KisFilterConfigurationSP factoryConfiguration() const override; + KisFilterConfigurationSP defaultConfiguration() const override; public: KisConfigWidget * createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP dev, bool useForMasks) const override; private: bool** CreateBoolArray(uint Columns, uint Rows) const; void FreeBoolArray(bool** lpbArray, uint Columns) const; uchar LimitValues(int ColorValue) const; }; #endif diff --git a/plugins/filters/randompickfilter/randompickfilter.cpp b/plugins/filters/randompickfilter/randompickfilter.cpp index d98df1bfb5..da1ee22bf9 100644 --- a/plugins/filters/randompickfilter/randompickfilter.cpp +++ b/plugins/filters/randompickfilter/randompickfilter.cpp @@ -1,157 +1,157 @@ /* * This file is part of the KDE project * * 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. */ #include "randompickfilter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_wdg_random_pick.h" #include "ui_wdgrandompickoptions.h" #include #include K_PLUGIN_FACTORY_WITH_JSON(KritaRandomPickFilterFactory, "kritarandompickfilter.json", registerPlugin();) KritaRandomPickFilter::KritaRandomPickFilter(QObject *parent, const QVariantList &) : QObject(parent) { KisFilterRegistry::instance()->add(new KisFilterRandomPick()); } KritaRandomPickFilter::~KritaRandomPickFilter() { } KisFilterRandomPick::KisFilterRandomPick() : KisFilter(id(), FiltersCategoryOtherId, i18n("&Random Pick...")) { setColorSpaceIndependence(FULLY_INDEPENDENT); setSupportsPainting(true); } void KisFilterRandomPick::processImpl(KisPaintDeviceSP device, const QRect& applyRect, const KisFilterConfigurationSP config, KoUpdater* progressUpdater ) const { Q_UNUSED(config); Q_ASSERT(!device.isNull()); const KoColorSpace * cs = device->colorSpace(); QVariant value; int level = (config && config->getProperty("level", value)) ? value.toInt() : 50; int opacity = (config && config->getProperty("opacity", value)) ? value.toInt() : 100; double windowsize = (config && config->getProperty("windowsize", value)) ? value.toDouble() : 2.5; int seedThreshold = rand(); int seedH = rand(); int seedV = rand(); if (config) { seedThreshold = config->getInt("seedThreshold", seedThreshold); seedH = config->getInt("seedH", seedH); seedV = config->getInt("seedV", seedV); } KisRandomGenerator randT(seedThreshold); KisRandomGenerator randH(seedH); KisRandomGenerator randV(seedV); KisSequentialIteratorProgress dstIt(device, applyRect, progressUpdater); KisRandomConstAccessorSP srcRA = device->createRandomConstAccessorNG(0, 0); double threshold = (100 - level) / 100.0; qint16 weights[2]; weights[0] = (255 * opacity) / 100; weights[1] = 255 - weights[0]; const quint8* pixels[2]; KoMixColorsOp * mixOp = cs->mixColorsOp(); while (dstIt.nextPixel()) { if (randT.doubleRandomAt(dstIt.x(), dstIt.y()) > threshold) { int x = static_cast(dstIt.x() + windowsize * (randH.doubleRandomAt(dstIt.x(), dstIt.y()) - 0.5)); int y = static_cast(dstIt.y() + windowsize * (randV.doubleRandomAt(dstIt.x(), dstIt.y()) -0.5)); srcRA->moveTo(x, y); pixels[0] = srcRA->oldRawData(); pixels[1] = dstIt.oldRawData(); mixOp->mixColors(pixels, weights, 2, dstIt.rawData()); } } } KisConfigWidget * KisFilterRandomPick::createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP dev, bool) const { Q_UNUSED(dev); return new KisWdgRandomPick((KisFilter*)this, (QWidget*)parent); } -KisFilterConfigurationSP KisFilterRandomPick::factoryConfiguration() const +KisFilterConfigurationSP KisFilterRandomPick::defaultConfiguration() const { - KisFilterConfigurationSP config = new KisFilterConfiguration("randompick", 1); + KisFilterConfigurationSP config = factoryConfiguration(); config->setProperty("level", 50); config->setProperty("windowsize", 2.5); config->setProperty("opacity", 100); config->setProperty("seedThreshold", rand()); config->setProperty("seedH", rand()); config->setProperty("seedV", rand()); return config; } QRect KisFilterRandomPick::neededRect(const QRect& rect, const KisFilterConfigurationSP config, int lod) const { Q_UNUSED(lod); QVariant value; int windowsize = ceil((config && config->getProperty("windowsize", value)) ? value.toDouble() : 2.5); return rect.adjusted(-windowsize, -windowsize, windowsize, windowsize); } QRect KisFilterRandomPick::changedRect(const QRect &rect, const KisFilterConfigurationSP config, int lod) const { return neededRect(rect, config, lod); } #include "randompickfilter.moc" diff --git a/plugins/filters/randompickfilter/randompickfilter.h b/plugins/filters/randompickfilter/randompickfilter.h index 63487b060b..919ee23118 100644 --- a/plugins/filters/randompickfilter/randompickfilter.h +++ b/plugins/filters/randompickfilter/randompickfilter.h @@ -1,60 +1,60 @@ /* * This file is part of Krita * * Copyright (c) 2006 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 RANDOMPICKFILTER_H #define RANDOMPICKFILTER_H #include #include #include "filter/kis_filter.h" class KisConfigWidget; class KritaRandomPickFilter : public QObject { Q_OBJECT public: KritaRandomPickFilter(QObject *parent, const QVariantList &); ~KritaRandomPickFilter() override; }; class KisFilterRandomPick : public KisFilter { public: KisFilterRandomPick(); public: void processImpl(KisPaintDeviceSP device, const QRect& applyRect, const KisFilterConfigurationSP config, KoUpdater* progressUpdater ) const override; static inline KoID id() { return KoID("randompick", i18n("Random Pick")); } - KisFilterConfigurationSP factoryConfiguration() const override; + KisFilterConfigurationSP defaultConfiguration() const override; public: KisConfigWidget * createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP dev, bool useForMasks) const override; QRect neededRect(const QRect& rect, const KisFilterConfigurationSP config, int lod = 0) const override; QRect changedRect(const QRect& rect, const KisFilterConfigurationSP config, int lod = 0) const override; }; #endif diff --git a/plugins/filters/roundcorners/kis_round_corners_filter.cpp b/plugins/filters/roundcorners/kis_round_corners_filter.cpp index 04b3827acf..6df920c813 100644 --- a/plugins/filters/roundcorners/kis_round_corners_filter.cpp +++ b/plugins/filters/roundcorners/kis_round_corners_filter.cpp @@ -1,155 +1,155 @@ /* * This file is part of Krita * * Copyright (c) 2005 Michael Thaler * * ported from Gimp, Copyright (C) 1997 Eiichi Takamori * original pixelize.c for GIMP 0.54 by Tracy Scott * * 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_round_corners_filter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include KisRoundCornersFilter::KisRoundCornersFilter() : KisFilter(id(), FiltersCategoryMapId, i18n("&Round Corners...")) { setSupportsPainting(false); } void fadeOneCorner(KisPaintDeviceSP device, const QPoint &basePoint, const QRect &processRect, const qreal thresholdSq, KoUpdater* progressUpdater) { const KoColorSpace *cs = device->colorSpace(); KisSequentialIteratorProgress dstIt(device, processRect, progressUpdater); while (dstIt.nextPixel()) { const QPointF point(dstIt.x(), dstIt.y()); const qreal distanceSq = kisSquareDistance(point, basePoint); if (distanceSq >= thresholdSq) { cs->setOpacity(dstIt.rawData(), OPACITY_TRANSPARENT_U8, 1); } } } void KisRoundCornersFilter::processImpl(KisPaintDeviceSP device, const QRect& applyRect, const KisFilterConfigurationSP config, KoUpdater* progressUpdater ) const { Q_UNUSED(config); Q_ASSERT(!device.isNull()); if (!device || !config) { warnKrita << "Invalid parameters for round corner filter"; dbgPlugins << device << " " << config; return; } const QRect bounds = device->defaultBounds()->bounds(); const qint32 radius = qMin(KisAlgebra2D::minDimension(bounds) / 2, qMax(1, config->getInt("radius" , 30))); const qreal radiusSq = pow2(radius); struct CornerJob { QRect rc; QPoint pt; KoUpdater *progressUpdater; }; QVector jobs; KoProgressUpdater compositeUpdater(progressUpdater, KoProgressUpdater::Unthreaded); { QRect rc(bounds.x(), bounds.y(), radius, radius); QPoint pt(rc.bottomRight()); jobs << CornerJob({rc, pt, compositeUpdater.startSubtask()}); } { QRect rc(bounds.x() + bounds.width() - radius, bounds.y(), radius, radius); QPoint pt(rc.bottomLeft()); jobs << CornerJob({rc, pt, compositeUpdater.startSubtask()}); } { QRect rc(bounds.x(), bounds.y() + bounds.height() - radius, radius, radius); QPoint pt(rc.topRight()); jobs << CornerJob({rc, pt, compositeUpdater.startSubtask()}); } { QRect rc(bounds.x() + bounds.width() - radius, bounds.y() + bounds.height() - radius, radius, radius); QPoint pt(rc.topLeft()); jobs << CornerJob({rc, pt, compositeUpdater.startSubtask()}); } Q_FOREACH (const CornerJob &job, jobs) { const QRect processRect = job.rc & applyRect; if (!processRect.isEmpty()) { fadeOneCorner(device, job.pt, processRect, radiusSq, job.progressUpdater); } } } KisConfigWidget * KisRoundCornersFilter::createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP, bool) const { vKisIntegerWidgetParam param; param.push_back(KisIntegerWidgetParam(2, 100, 30, i18n("Radius"), "radius")); return new KisMultiIntegerFilterWidget(id().id(), parent, id().id(), param); } -KisFilterConfigurationSP KisRoundCornersFilter::factoryConfiguration() const +KisFilterConfigurationSP KisRoundCornersFilter::defaultConfiguration() const { - KisFilterConfigurationSP config = new KisFilterConfiguration("roundcorners", 1); + KisFilterConfigurationSP config = factoryConfiguration(); config->setProperty("radius", 30); return config; } diff --git a/plugins/filters/roundcorners/kis_round_corners_filter.h b/plugins/filters/roundcorners/kis_round_corners_filter.h index ae6d8e5ed6..d22003d50c 100644 --- a/plugins/filters/roundcorners/kis_round_corners_filter.h +++ b/plugins/filters/roundcorners/kis_round_corners_filter.h @@ -1,47 +1,47 @@ /* * This file is part of the KDE project * * Copyright (c) Michael Thaler * * 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_ROUND_CORNERS_FILTER_H_ #define _KIS_ROUND_CORNERS_FILTER_H_ #include "kis_paint_device.h" #include "filter/kis_filter.h" #include "kis_config_widget.h" class KisRoundCornersFilter : public KisFilter { public: KisRoundCornersFilter(); public: void processImpl(KisPaintDeviceSP device, const QRect& applyRect, const KisFilterConfigurationSP config, KoUpdater* progressUpdater ) const override; static inline KoID id() { return KoID("roundcorners", i18n("Round Corners")); } - KisFilterConfigurationSP factoryConfiguration() const override; + KisFilterConfigurationSP defaultConfiguration() const override; public: KisConfigWidget * createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP dev, bool useForMasks) const override; private: }; #endif diff --git a/plugins/filters/smalltilesfilter/kis_small_tiles_filter.cpp b/plugins/filters/smalltilesfilter/kis_small_tiles_filter.cpp index 673b15964d..ee25e5a2bf 100644 --- a/plugins/filters/smalltilesfilter/kis_small_tiles_filter.cpp +++ b/plugins/filters/smalltilesfilter/kis_small_tiles_filter.cpp @@ -1,115 +1,115 @@ /* * This file is part of Krita * * Copyright (c) 2005 Michael Thaler * * ported from Gimp, Copyright (C) 1997 Eiichi Takamori * original pixelize.c for GIMP 0.54 by Tracy Scott * * 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_small_tiles_filter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "widgets/kis_multi_integer_filter_widget.h" KisSmallTilesFilter::KisSmallTilesFilter() : KisFilter(id(), FiltersCategoryMapId, i18n("&Small Tiles...")) { setSupportsPainting(true); setSupportsThreading(false); setSupportsAdjustmentLayers(false); } void KisSmallTilesFilter::processImpl(KisPaintDeviceSP device, const QRect& applyRect, const KisFilterConfigurationSP config, KoUpdater* progressUpdater ) const { Q_ASSERT(!device.isNull()); //read the filter configuration values from the KisFilterConfiguration object const quint32 numberOfTiles = config->getInt("numberOfTiles", 2); const QRect srcRect = applyRect; const int w = static_cast(srcRect.width() / numberOfTiles); const int h = static_cast(srcRect.height() / numberOfTiles); KisPaintDeviceSP tile = device->createThumbnailDevice(w, h); if (tile.isNull()) return; device->clear(applyRect); KisPainter gc(device); gc.setCompositeOp(COMPOSITE_COPY); if (progressUpdater) { progressUpdater->setRange(0, numberOfTiles); } for (uint y = 0; y < numberOfTiles; ++y) { for (uint x = 0; x < numberOfTiles; ++x) { gc.bitBlt(w * x, h * y, tile, 0, 0, w, h); } if (progressUpdater) progressUpdater->setValue(y); } gc.end(); } KisConfigWidget * KisSmallTilesFilter::createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP, bool) const { vKisIntegerWidgetParam param; param.push_back(KisIntegerWidgetParam(2, 5, 1, i18n("Number of tiles"), "numberOfTiles")); return new KisMultiIntegerFilterWidget(id().id(), parent, id().id(), param); } -KisFilterConfigurationSP KisSmallTilesFilter::factoryConfiguration() const +KisFilterConfigurationSP KisSmallTilesFilter::defaultConfiguration() const { - KisFilterConfigurationSP config = new KisFilterConfiguration("smalltiles", 1); + KisFilterConfigurationSP config = factoryConfiguration(); config->setProperty("numberOfTiles", 2); return config; } diff --git a/plugins/filters/smalltilesfilter/kis_small_tiles_filter.h b/plugins/filters/smalltilesfilter/kis_small_tiles_filter.h index 309171ab6f..dbc1d549a0 100644 --- a/plugins/filters/smalltilesfilter/kis_small_tiles_filter.h +++ b/plugins/filters/smalltilesfilter/kis_small_tiles_filter.h @@ -1,51 +1,51 @@ /* * This file is part of the KDE project * * Copyright (c) Michael Thaler * * 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_SMALL_TILES_FILTER_H_ #define _KIS_SMALL_TILES_FILTER_H_ #include "kis_paint_device.h" #include "filter/kis_filter.h" #include "kis_config_widget.h" class KisSmallTilesFilter : public KisFilter { public: KisSmallTilesFilter(); public: void processImpl(KisPaintDeviceSP device, const QRect& applyRect, const KisFilterConfigurationSP config, KoUpdater* progressUpdater ) const override; static inline KoID id() { return KoID("smalltiles", i18n("Small Tiles")); } - KisFilterConfigurationSP factoryConfiguration() const override; + KisFilterConfigurationSP defaultConfiguration() const override; public: KisConfigWidget * createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP dev, bool useForMasks) const override; }; #endif diff --git a/plugins/filters/threshold/threshold.cpp b/plugins/filters/threshold/threshold.cpp index 8fe991fd07..0ae8e1c105 100644 --- a/plugins/filters/threshold/threshold.cpp +++ b/plugins/filters/threshold/threshold.cpp @@ -1,216 +1,216 @@ /* * This file is part of the KDE project * * Copyright (c) 2016 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 "threshold.h" #include #include #include #include #include #include #include #include #include #include #include "KisGradientSlider.h" #include "kis_histogram.h" #include #include "kis_paint_device.h" #include "kis_painter.h" #include #include #include #include #include #include "KoColorModelStandardIds.h" #include #include #include K_PLUGIN_FACTORY_WITH_JSON(KritaThresholdFactory, "kritathreshold.json", registerPlugin();) KritaThreshold::KritaThreshold(QObject *parent, const QVariantList &) : QObject(parent) { KisFilterRegistry::instance()->add(new KisFilterThreshold()); } KritaThreshold::~KritaThreshold() { } KisFilterThreshold::KisFilterThreshold() : KisFilter(id(), FiltersCategoryAdjustId, i18n("&Threshold...")) { setColorSpaceIndependence(FULLY_INDEPENDENT); setSupportsPainting(false); setShowConfigurationWidget(true); setSupportsLevelOfDetail(true); setSupportsAdjustmentLayers(true); setSupportsThreading(true); } void KisFilterThreshold::processImpl(KisPaintDeviceSP device, const QRect& applyRect, const KisFilterConfigurationSP config, KoUpdater *progressUpdater) const { Q_ASSERT(!device.isNull()); const int threshold = config->getInt("threshold"); KoColor white(Qt::white, device->colorSpace()); KoColor black(Qt::black, device->colorSpace()); KisSequentialIteratorProgress it(device, applyRect, progressUpdater); const int pixelSize = device->colorSpace()->pixelSize(); while (it.nextPixel()) { if (device->colorSpace()->intensity8(it.oldRawData()) > threshold) { white.setOpacity(device->colorSpace()->opacityU8(it.oldRawData())); memcpy(it.rawData(), white.data(), pixelSize); } else { black.setOpacity(device->colorSpace()->opacityU8(it.oldRawData())); memcpy(it.rawData(), black.data(), pixelSize); } } } -KisFilterConfigurationSP KisFilterThreshold::factoryConfiguration() const +KisFilterConfigurationSP KisFilterThreshold::defaultConfiguration() const { - KisFilterConfigurationSP config = new KisFilterConfiguration("threshold", 1); + KisFilterConfigurationSP config = factoryConfiguration(); config->setProperty("threshold", 128); return config; } KisConfigWidget *KisFilterThreshold::createConfigurationWidget(QWidget *parent, const KisPaintDeviceSP dev, bool) const { return new KisThresholdConfigWidget(parent, dev); } KisThresholdConfigWidget::KisThresholdConfigWidget(QWidget * parent, KisPaintDeviceSP dev) : KisConfigWidget(parent) { Q_ASSERT(dev); m_page.setupUi(this); m_page.thresholdGradient->enableGamma(false); m_page.thresholdGradient->enableWhite(false); m_page.intThreshold->setValue(128); connect(m_page.intThreshold, SIGNAL(valueChanged(int)), SIGNAL(sigConfigurationItemChanged())); connect(m_page.thresholdGradient, SIGNAL(sigModifiedGamma(double)), SIGNAL(sigConfigurationItemChanged())); connect(m_page.intThreshold, SIGNAL(valueChanged(int)), m_page.thresholdGradient, SLOT(slotModifyBlack(int))); connect(m_page.thresholdGradient, SIGNAL(sigModifiedBlack(int)), m_page.intThreshold, SLOT(setValue(int))); connect((QObject*)(m_page.chkLogarithmic), SIGNAL(toggled(bool)), this, SLOT(slotDrawHistogram(bool))); KoHistogramProducer *producer = new KoGenericLabHistogramProducer(); m_histogram.reset( new KisHistogram(dev, dev->exactBounds(), producer, LINEAR) ); m_histlog = false; m_page.histview->resize(288,100); slotDrawHistogram(); } KisThresholdConfigWidget::~KisThresholdConfigWidget() { } void KisThresholdConfigWidget::slotDrawHistogram(bool logarithmic) { int wHeight = m_page.histview->height(); int wHeightMinusOne = wHeight - 1; int wWidth = m_page.histview->width(); if (m_histlog != logarithmic) { // Update the m_histogram if (logarithmic) m_histogram->setHistogramType(LOGARITHMIC); else m_histogram->setHistogramType(LINEAR); m_histlog = logarithmic; } QPalette appPalette = QApplication::palette(); QPixmap pix(wWidth-100, wHeight); pix.fill(QColor(appPalette.color(QPalette::Base))); QPainter p(&pix); p.setPen(QPen(Qt::gray, 1, Qt::SolidLine)); double highest = (double)m_histogram->calculations().getHighest(); qint32 bins = m_histogram->producer()->numberOfBins(); // use nearest neighbour interpolation if (m_histogram->getHistogramType() == LINEAR) { double factor = (double)(wHeight - wHeight / 5.0) / highest; for (int i = 0; i < wWidth; i++) { int binNo = qRound((double)i / wWidth * (bins - 1)); if ((int)m_histogram->getValue(binNo) != 0) p.drawLine(i, wHeightMinusOne, i, wHeightMinusOne - (int)m_histogram->getValue(binNo) * factor); } } else { double factor = (double)(wHeight - wHeight / 5.0) / (double)log(highest); for (int i = 0; i < wWidth; i++) { int binNo = qRound((double)i / wWidth * (bins - 1)) ; if ((int)m_histogram->getValue(binNo) != 0) p.drawLine(i, wHeightMinusOne, i, wHeightMinusOne - log((double)m_histogram->getValue(binNo)) * factor); } } m_page.histview->setPixmap(pix); } void KisThresholdConfigWidget::slotSetThreshold(int limit) { m_page.intThreshold->setMaximum(limit - 1); } KisPropertiesConfigurationSP KisThresholdConfigWidget::configuration() const { KisFilterConfigurationSP config = new KisFilterConfiguration("threshold", 1); config->setProperty("threshold", m_page.intThreshold->value()); return config; } void KisThresholdConfigWidget::setConfiguration(const KisPropertiesConfigurationSP config) { QVariant value; if (config->getProperty("threshold", value)) { m_page.intThreshold->setValue(value.toUInt()); m_page.thresholdGradient->slotModifyBlack(value.toUInt()); } } #include "threshold.moc" diff --git a/plugins/filters/threshold/threshold.h b/plugins/filters/threshold/threshold.h index c6e0eae930..f61a44e28b 100644 --- a/plugins/filters/threshold/threshold.h +++ b/plugins/filters/threshold/threshold.h @@ -1,91 +1,91 @@ /* * This file is part of Krita * * Copyright (c) 2016 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 THRESHOLD_H #define THRESHOLD_H #include #include #include #include #include #include "ui_wdg_threshold.h" class WdgThreshold; class QWidget; class KisHistogram; class KritaThreshold : public QObject { Q_OBJECT public: KritaThreshold(QObject *parent, const QVariantList &); ~KritaThreshold() override; }; class KisFilterThreshold : public KisFilter { public: KisFilterThreshold(); public: static inline KoID id() { return KoID("threshold", i18n("Threshold")); } void processImpl(KisPaintDeviceSP device, const QRect& applyRect, const KisFilterConfigurationSP config, KoUpdater *progressUpdater) const override; - KisFilterConfigurationSP factoryConfiguration() const override; + KisFilterConfigurationSP defaultConfiguration() const override; KisConfigWidget *createConfigurationWidget(QWidget *parent, const KisPaintDeviceSP dev, bool useForMasks) const override; }; class KisThresholdConfigWidget : public KisConfigWidget { Q_OBJECT public: KisThresholdConfigWidget(QWidget *parent, KisPaintDeviceSP dev); ~KisThresholdConfigWidget() override; KisPropertiesConfigurationSP configuration() const override; void setConfiguration(const KisPropertiesConfigurationSP config) override; Ui::WdgThreshold m_page; private Q_SLOTS: void slotDrawHistogram(bool logarithmic = false); void slotSetThreshold(int); protected: QScopedPointer m_histogram; bool m_histlog; }; #endif diff --git a/plugins/filters/unsharp/kis_unsharp_filter.cpp b/plugins/filters/unsharp/kis_unsharp_filter.cpp index f782e5ba6f..cb12116791 100644 --- a/plugins/filters/unsharp/kis_unsharp_filter.cpp +++ b/plugins/filters/unsharp/kis_unsharp_filter.cpp @@ -1,229 +1,229 @@ /* * This file is part of Krita * * Copyright (c) 2006 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 //MSVC requires that Vc come first #include "kis_unsharp_filter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_lod_transform.h" #include "kis_wdg_unsharp.h" #include "ui_wdgunsharp.h" #include "KoColorSpaceTraits.h" #include KisUnsharpFilter::KisUnsharpFilter() : KisFilter(id(), FiltersCategoryEnhanceId, i18n("&Unsharp Mask...")) { setSupportsPainting(true); setSupportsAdjustmentLayers(true); setSupportsThreading(true); /** * Officially Unsharp Mask doesn't support LoD, because it * generates subtle artifacts when the unsharp radius is smaller * than current zoom level. But LoD devices can still appear when * the filter is used in Adjustment Layer. So the actual LoD is * still counted on. */ setSupportsLevelOfDetail(false); setColorSpaceIndependence(FULLY_INDEPENDENT); } KisConfigWidget * KisUnsharpFilter::createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP, bool) const { return new KisWdgUnsharp(parent); } -KisFilterConfigurationSP KisUnsharpFilter::factoryConfiguration() const +KisFilterConfigurationSP KisUnsharpFilter::defaultConfiguration() const { - KisFilterConfigurationSP config = new KisFilterConfiguration(id().id(), 1); + KisFilterConfigurationSP config = factoryConfiguration(); config->setProperty("halfSize", 1); config->setProperty("amount", 0.5); config->setProperty("threshold", 0); config->setProperty("lightnessOnly", true); return config; } void KisUnsharpFilter::processImpl(KisPaintDeviceSP device, const QRect& applyRect, const KisFilterConfigurationSP _config, KoUpdater* progressUpdater ) const { QPointer filterUpdater = 0; QPointer convolutionUpdater = 0; QScopedPointer updater; if (progressUpdater) { updater.reset(new KoProgressUpdater(progressUpdater)); updater->start(100, i18n("Unsharp Mask")); // Two sub-sub tasks that each go from 0 to 100. convolutionUpdater = updater->startSubtask(); filterUpdater = updater->startSubtask(); } KisFilterConfigurationSP config = _config ? _config : new KisFilterConfiguration(id().id(), 1); QVariant value; KisLodTransformScalar t(device); const qreal halfSize = t.scale(config->getProperty("halfSize", value) ? value.toDouble() : 1.0); const qreal amount = (config->getProperty("amount", value)) ? value.toDouble() : 0.5; const uint threshold = (config->getProperty("threshold", value)) ? value.toUInt() : 0; const uint lightnessOnly = (config->getProperty("lightnessOnly", value)) ? value.toBool() : true; QBitArray channelFlags = config->channelFlags(); KisGaussianKernel::applyGaussian(device, applyRect, halfSize, halfSize, channelFlags, convolutionUpdater); qreal weights[2]; qreal factor = 128; weights[0] = factor * (1. + amount); weights[1] = -factor * amount; if (lightnessOnly) { processLightnessOnly(device, applyRect, threshold, weights, factor, channelFlags, filterUpdater); } else { processRaw(device, applyRect, threshold, weights, factor, channelFlags, filterUpdater); } } void KisUnsharpFilter::processRaw(KisPaintDeviceSP device, const QRect &rect, quint8 threshold, qreal weights[2], qreal factor, const QBitArray &channelFlags, KoUpdater *progressUpdater) const { const KoColorSpace *cs = device->colorSpace(); const int pixelSize = cs->pixelSize(); KoConvolutionOp * convolutionOp = cs->convolutionOp(); quint8 *colors[2]; colors[0] = new quint8[pixelSize]; colors[1] = new quint8[pixelSize]; KisSequentialIteratorProgress dstIt(device, rect, progressUpdater); while (dstIt.nextPixel()) { quint8 diff = 0; if (threshold == 1) { if (memcmp(dstIt.oldRawData(), dstIt.rawDataConst(), cs->pixelSize()) == 0) { diff = 1; } } else { diff = cs->difference(dstIt.oldRawData(), dstIt.rawDataConst()); } if (diff >= threshold) { memcpy(colors[0], dstIt.oldRawData(), pixelSize); memcpy(colors[1], dstIt.rawDataConst(), pixelSize); convolutionOp->convolveColors(colors, weights, dstIt.rawData(), factor, 0, 2, channelFlags); } else { memcpy(dstIt.rawData(), dstIt.oldRawData(), pixelSize); } } delete[] colors[0]; delete[] colors[1]; } void KisUnsharpFilter::processLightnessOnly(KisPaintDeviceSP device, const QRect &rect, quint8 threshold, qreal weights[2], qreal factor, const QBitArray & /*channelFlags*/, KoUpdater *progressUpdater) const { const KoColorSpace *cs = device->colorSpace(); const int pixelSize = cs->pixelSize(); quint16 labColorSrc[4]; quint16 labColorDst[4]; const int posL = 0; const int posAplha = 3; const qreal factorInv = 1.0 / factor; KisSequentialIteratorProgress dstIt(device, rect, progressUpdater); while (dstIt.nextPixel()) { quint8 diff = cs->differenceA(dstIt.oldRawData(), dstIt.rawDataConst()); if (diff >= threshold) { cs->toLabA16(dstIt.oldRawData(), (quint8*)labColorSrc, 1); cs->toLabA16(dstIt.rawDataConst(), (quint8*)labColorDst, 1); qint32 valueL = (labColorSrc[posL] * weights[0] + labColorDst[posL] * weights[1]) * factorInv; labColorSrc[posL] = CLAMP(valueL, KoColorSpaceMathsTraits::min, KoColorSpaceMathsTraits::max); qint32 valueAlpha = (labColorSrc[posAplha] * weights[0] + labColorDst[posAplha] * weights[1]) * factorInv; labColorSrc[posAplha] = CLAMP(valueAlpha, KoColorSpaceMathsTraits::min, KoColorSpaceMathsTraits::max); cs->fromLabA16((quint8*)labColorSrc, dstIt.rawData(), 1); } else { memcpy(dstIt.rawData(), dstIt.oldRawData(), pixelSize); } } } QRect KisUnsharpFilter::neededRect(const QRect & rect, const KisFilterConfigurationSP config, int lod) const { KisLodTransformScalar t(lod); QVariant value; const qreal halfSize = t.scale(config->getProperty("halfSize", value) ? value.toDouble() : 1.0); return rect.adjusted(-halfSize * 2, -halfSize * 2, halfSize * 2, halfSize * 2); } QRect KisUnsharpFilter::changedRect(const QRect & rect, const KisFilterConfigurationSP config, int lod) const { KisLodTransformScalar t(lod); QVariant value; const qreal halfSize = t.scale(config->getProperty("halfSize", value) ? value.toDouble() : 1.0); return rect.adjusted( -halfSize, -halfSize, halfSize, halfSize); } diff --git a/plugins/filters/unsharp/kis_unsharp_filter.h b/plugins/filters/unsharp/kis_unsharp_filter.h index 2395e673c3..6cc243e6a4 100644 --- a/plugins/filters/unsharp/kis_unsharp_filter.h +++ b/plugins/filters/unsharp/kis_unsharp_filter.h @@ -1,64 +1,64 @@ /* * This file is part of Krita * * Copyright (c) 2006 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_UNSHARP_FILTER_H #define KIS_UNSHARP_FILTER_H #include "filter/kis_filter.h" class KisUnsharpFilter : public KisFilter { public: KisUnsharpFilter(); void processImpl(KisPaintDeviceSP device, const QRect& applyRect, const KisFilterConfigurationSP config, KoUpdater* progressUpdater ) const override; static inline KoID id() { return KoID("unsharp", i18n("Unsharp Mask")); } KisConfigWidget * createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP dev, bool useForMasks) const override; - KisFilterConfigurationSP factoryConfiguration() const override; + KisFilterConfigurationSP defaultConfiguration() const override; QRect changedRect(const QRect & rect, const KisFilterConfigurationSP _config, int lod) const override; QRect neededRect(const QRect & rect, const KisFilterConfigurationSP _config, int lod) const override; private: void processLightnessOnly(KisPaintDeviceSP device, const QRect &rect, quint8 threshold, qreal weights[2], qreal factor, const QBitArray &channelFlags, KoUpdater *progressUpdater) const; void processRaw(KisPaintDeviceSP device, const QRect &rect, quint8 threshold, qreal weights[2], qreal factor, const QBitArray &channelFlags, KoUpdater *progressUpdater) const; }; #endif diff --git a/plugins/filters/wavefilter/wavefilter.cpp b/plugins/filters/wavefilter/wavefilter.cpp index 10996d155e..0b9a570d10 100644 --- a/plugins/filters/wavefilter/wavefilter.cpp +++ b/plugins/filters/wavefilter/wavefilter.cpp @@ -1,177 +1,177 @@ /* * This file is part of the KDE project * * 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. */ #include "wavefilter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_wdg_wave.h" #include "ui_wdgwaveoptions.h" #include #include K_PLUGIN_FACTORY_WITH_JSON(KritaWaveFilterFactory, "kritawavefilter.json", registerPlugin();) class KisWaveCurve { public: virtual ~KisWaveCurve() {} virtual double valueAt(int x, int y) = 0; }; class KisSinusoidalWaveCurve : public KisWaveCurve { public: KisSinusoidalWaveCurve(int amplitude, int wavelength, int shift) : m_amplitude(amplitude), m_wavelength(wavelength), m_shift(shift) { } ~KisSinusoidalWaveCurve() override {} double valueAt(int x, int y) override { return y + m_amplitude * cos((double)(m_shift + x) / m_wavelength); } private: int m_amplitude, m_wavelength, m_shift; }; class KisTriangleWaveCurve : public KisWaveCurve { public: KisTriangleWaveCurve(int amplitude, int wavelength, int shift) : m_amplitude(amplitude), m_wavelength(wavelength), m_shift(shift) { } ~KisTriangleWaveCurve() override {} double valueAt(int x, int y) override { return y + m_amplitude * pow(-1.0, (m_shift + x) / m_wavelength) *(0.5 - (double)((m_shift + x) % m_wavelength) / m_wavelength); } private: int m_amplitude, m_wavelength, m_shift; }; KritaWaveFilter::KritaWaveFilter(QObject *parent, const QVariantList &) : QObject(parent) { KisFilterRegistry::instance()->add(new KisFilterWave()); } KritaWaveFilter::~KritaWaveFilter() { } KisFilterWave::KisFilterWave() : KisFilter(id(), FiltersCategoryOtherId, i18n("&Wave...")) { setColorSpaceIndependence(FULLY_INDEPENDENT); setSupportsPainting(false); setSupportsAdjustmentLayers(false); } -KisFilterConfigurationSP KisFilterWave::factoryConfiguration() const +KisFilterConfigurationSP KisFilterWave::defaultConfiguration() const { - KisFilterConfigurationSP config = new KisFilterConfiguration("wave", 1); + KisFilterConfigurationSP config = factoryConfiguration(); config->setProperty("horizontalwavelength", 50); config->setProperty("horizontalshift", 50); config->setProperty("horizontalamplitude", 4); config->setProperty("horizontalshape", 0); config->setProperty("verticalwavelength", 50); config->setProperty("verticalshift", 50); config->setProperty("verticalamplitude", 4); config->setProperty("verticalshape", 0); return config; } KisConfigWidget * KisFilterWave::createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP, bool) const { return new KisWdgWave((KisFilter*)this, (QWidget*)parent); } void KisFilterWave::processImpl(KisPaintDeviceSP device, const QRect& applyRect, const KisFilterConfigurationSP config, KoUpdater* progressUpdater ) const { Q_ASSERT(device.data() != 0); QVariant value; int horizontalwavelength = (config && config->getProperty("horizontalwavelength", value)) ? value.toInt() : 50; int horizontalshift = (config && config->getProperty("horizontalshift", value)) ? value.toInt() : 50; int horizontalamplitude = (config && config->getProperty("horizontalamplitude", value)) ? value.toInt() : 4; int horizontalshape = (config && config->getProperty("horizontalshape", value)) ? value.toInt() : 0; int verticalwavelength = (config && config->getProperty("verticalwavelength", value)) ? value.toInt() : 50; int verticalshift = (config && config->getProperty("verticalshift", value)) ? value.toInt() : 50; int verticalamplitude = (config && config->getProperty("verticalamplitude", value)) ? value.toInt() : 4; int verticalshape = (config && config->getProperty("verticalshape", value)) ? value.toInt() : 0; KisWaveCurve* verticalcurve; if (verticalshape == 1) verticalcurve = new KisTriangleWaveCurve(verticalamplitude, verticalwavelength, verticalshift); else verticalcurve = new KisSinusoidalWaveCurve(verticalamplitude, verticalwavelength, verticalshift); KisWaveCurve* horizontalcurve; if (horizontalshape == 1) horizontalcurve = new KisTriangleWaveCurve(horizontalamplitude, horizontalwavelength, horizontalshift); else horizontalcurve = new KisSinusoidalWaveCurve(horizontalamplitude, horizontalwavelength, horizontalshift); KisSequentialIteratorProgress dstIt(device, applyRect, progressUpdater); KisRandomSubAccessorSP srcRSA = device->createRandomSubAccessor(); while (dstIt.nextPixel()) { double xv = horizontalcurve->valueAt(dstIt.y(), dstIt.x()); double yv = verticalcurve->valueAt(dstIt.x(), dstIt.y()); srcRSA->moveTo(QPointF(xv, yv)); srcRSA->sampledOldRawData(dstIt.rawData()); } delete horizontalcurve; delete verticalcurve; } QRect KisFilterWave::neededRect(const QRect& rect, const KisFilterConfigurationSP config, int lod) const { Q_UNUSED(lod); QVariant value; int horizontalamplitude = (config && config->getProperty("horizontalamplitude", value)) ? value.toInt() : 4; int verticalamplitude = (config && config->getProperty("verticalamplitude", value)) ? value.toInt() : 4; return rect.adjusted(-horizontalamplitude, -verticalamplitude, horizontalamplitude, verticalamplitude); } #include "wavefilter.moc" diff --git a/plugins/filters/wavefilter/wavefilter.h b/plugins/filters/wavefilter/wavefilter.h index 1465fa0003..9b7ecda564 100644 --- a/plugins/filters/wavefilter/wavefilter.h +++ b/plugins/filters/wavefilter/wavefilter.h @@ -1,61 +1,61 @@ /* * This file is part of Krita * * Copyright (c) 2006 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 WAVEFILTER_H #define WAVEFILTER_H #include #include #include "filter/kis_filter.h" class KisConfigWidget; class KritaWaveFilter : public QObject { Q_OBJECT public: KritaWaveFilter(QObject *parent, const QVariantList &); ~KritaWaveFilter() override; }; class KisFilterWave : public KisFilter { public: KisFilterWave(); public: void processImpl(KisPaintDeviceSP device, const QRect& applyRect, const KisFilterConfigurationSP config, KoUpdater* progressUpdater) const override; static inline KoID id() { return KoID("wave", i18n("Wave")); } - KisFilterConfigurationSP factoryConfiguration() const override; + KisFilterConfigurationSP defaultConfiguration() const override; public: QRect neededRect(const QRect& rect, const KisFilterConfigurationSP config = 0, int lod = 0) const override; KisConfigWidget * createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP dev, bool useForMasks) const override; }; #endif diff --git a/plugins/generators/pattern/patterngenerator.cpp b/plugins/generators/pattern/patterngenerator.cpp index 61d20f5875..60678895a5 100644 --- a/plugins/generators/pattern/patterngenerator.cpp +++ b/plugins/generators/pattern/patterngenerator.cpp @@ -1,121 +1,121 @@ /* * This file is part of the KDE project * * Copyright (c) 2008 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "patterngenerator.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_wdg_pattern.h" #include "ui_wdgpatternoptions.h" K_PLUGIN_FACTORY_WITH_JSON(KritaPatternGeneratorFactory, "kritapatterngenerator.json", registerPlugin();) KritaPatternGenerator::KritaPatternGenerator(QObject *parent, const QVariantList &) : QObject(parent) { KisGeneratorRegistry::instance()->add(new KoPatternGenerator()); } KritaPatternGenerator::~KritaPatternGenerator() { } KoPatternGenerator::KoPatternGenerator() : KisGenerator(id(), KoID("basic"), i18n("&Pattern...")) { setColorSpaceIndependence(FULLY_INDEPENDENT); setSupportsPainting(true); } -KisFilterConfigurationSP KoPatternGenerator::factoryConfiguration() const +KisFilterConfigurationSP KoPatternGenerator::defaultConfiguration() const { - KisFilterConfigurationSP config = new KisFilterConfiguration("pattern", 1); + KisFilterConfigurationSP config = factoryConfiguration(); QVariant v; v.setValue(QString("Grid01.pat")); config->setProperty("pattern", v); // v.setValue(KoColor()); // config->setProperty("color", v); return config; } KisConfigWidget * KoPatternGenerator::createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP dev, bool) const { Q_UNUSED(dev); return new KisWdgPattern(parent); } void KoPatternGenerator::generate(KisProcessingInformation dstInfo, const QSize& size, const KisFilterConfigurationSP config, KoUpdater* progressUpdater) const { KisPaintDeviceSP dst = dstInfo.paintDevice(); Q_ASSERT(!dst.isNull()); Q_ASSERT(config); if (!config) return; QString patternName = config->getString("pattern", "Grid01.pat"); KoResourceServer *rserver = KoResourceServerProvider::instance()->patternServer(); KoPattern *pattern = rserver->resourceByName(patternName); // KoColor c = config->getColor("color"); KisFillPainter gc(dst); gc.setPattern(pattern); // gc.setPaintColor(c); gc.setProgress(progressUpdater); gc.setChannelFlags(config->channelFlags()); gc.setOpacity(OPACITY_OPAQUE_U8); gc.setSelection(dstInfo.selection()); gc.setWidth(size.width()); gc.setHeight(size.height()); gc.setFillStyle(KisFillPainter::FillStylePattern); gc.fillRect(QRect(dstInfo.topLeft(), size), pattern); gc.end(); } #include "patterngenerator.moc" diff --git a/plugins/generators/pattern/patterngenerator.h b/plugins/generators/pattern/patterngenerator.h index 063edbe0d6..fc5ac5e8c2 100644 --- a/plugins/generators/pattern/patterngenerator.h +++ b/plugins/generators/pattern/patterngenerator.h @@ -1,59 +1,59 @@ /* * This file is part of Krita * * Copyright (c) 2006 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 PATTERN_GENERATOR_H #define PATTERN_GENERATOR_H #include #include #include "generator/kis_generator.h" class KisConfigWidget; class KritaPatternGenerator : public QObject { Q_OBJECT public: KritaPatternGenerator(QObject *parent, const QVariantList &); ~KritaPatternGenerator() override; }; class KoPatternGenerator : public KisGenerator { public: KoPatternGenerator(); using KisGenerator::generate; void generate(KisProcessingInformation dst, const QSize& size, const KisFilterConfigurationSP config, KoUpdater* progressUpdater ) const override; static inline KoID id() { return KoID("pattern", i18n("Pattern")); } - KisFilterConfigurationSP factoryConfiguration() const override; + KisFilterConfigurationSP defaultConfiguration() const override; KisConfigWidget * createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP dev, bool useForMasks) const override; }; #endif diff --git a/plugins/generators/simplexnoise/simplexnoisegenerator.cpp b/plugins/generators/simplexnoise/simplexnoisegenerator.cpp index 67499910e3..58a452489a 100644 --- a/plugins/generators/simplexnoise/simplexnoisegenerator.cpp +++ b/plugins/generators/simplexnoise/simplexnoisegenerator.cpp @@ -1,149 +1,149 @@ /* * KDE. Krita Project. * * Copyright (c) 2019 Eoin O'Neill * Copyright (c) 2019 Emmet O'Neill * * 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 "simplexnoisegenerator.h" #include "ui_wdgsimplexnoiseoptions.h" #include "kis_wdg_simplex_noise.h" #include "3rdparty/c-open-simplex/open-simplex-noise.h" #include #include #include #include #include #include #include K_PLUGIN_FACTORY_WITH_JSON(KritaSimplexNoiseGeneratorFactory, "kritasimplexnoisegenerator.json", registerPlugin();) KisSimplexNoiseGeneratorHandle::KisSimplexNoiseGeneratorHandle(QObject *parent, const QVariantList &) : QObject(parent) { KisGeneratorRegistry::instance()->add(new KisSimplexNoiseGenerator()); } KisSimplexNoiseGeneratorHandle::~KisSimplexNoiseGeneratorHandle() { } KisSimplexNoiseGenerator::KisSimplexNoiseGenerator() : KisGenerator(id(), KoID("basic"), i18n("&Simplex Noise...")) { setColorSpaceIndependence(FULLY_INDEPENDENT); setSupportsPainting(true); } void KisSimplexNoiseGenerator::generate(KisProcessingInformation dst, const QSize &size, const KisFilterConfigurationSP config, KoUpdater *progressUpdater) const { KisPaintDeviceSP device = dst.paintDevice(); Q_ASSERT(!device.isNull()); osn_context *noise_context; QRect bounds = QRect(dst.topLeft(), size); const KoColorSpace * cs = device->colorSpace(); KisSequentialIteratorProgress it(device, bounds, progressUpdater); QVariant property; const uint default_seed = (config->getProperty("seed", property)) ? property.toUInt() : 0; const QString custom_seed_string = (config->getProperty("custom_seed_string", property)) ? property.toString() : ""; const bool use_custom_seed = !custom_seed_string.trimmed().isEmpty(); const uint seed = use_custom_seed ? seedFromString(custom_seed_string) : default_seed; open_simplex_noise(seed, &noise_context); double frequency = (config && config->getProperty("frequency", property)) ? property.toDouble() : 25.0; double ratio_x = (config && config->getProperty("ratio_x", property)) ? property.toDouble() : 1.0; double ratio_y = (config && config->getProperty("ratio_y", property)) ? property.toDouble() : 1.0; bool looping = (config && config->getProperty("looping", property)) ? property.toBool() : false; if( looping ){ float major_radius = 0.5f * frequency * ratio_x; float minor_radius = 0.5f * frequency * ratio_y; while(it.nextPixel()){ double x_phase = (double)it.x() / (double)bounds.width() * M_PI * 2; double y_phase = (double)it.y() / (double)(bounds.height()) * M_PI * 2; double x_coordinate = major_radius * map_range(cos(x_phase), -1.0, 1.0, 0.0, 1.0); double y_coordinate = major_radius * map_range(sin(x_phase), -1.0, 1.0, 0.0, 1.0); double z_coordinate = minor_radius * map_range(cos(y_phase), -1.0, 1.0, 0.0, 1.0); double w_coordinate = minor_radius * map_range(sin(y_phase), -1.0, 1.0, 0.0, 1.0); double value = open_simplex_noise4(noise_context, x_coordinate, y_coordinate, z_coordinate, w_coordinate); value = map_range(value, -1.0, 1.0, 0.0, 255.0); QColor color = qRgb(static_cast(value), static_cast(value), static_cast(value)); cs->fromQColor(color, it.rawData()); } } else { while(it.nextPixel()){ double x_phase = (double)it.x() / (double)(bounds.width()) * ratio_x; double y_phase = (double)it.y() / (double)(bounds.height()) * ratio_y; double value = open_simplex_noise4(noise_context, x_phase * frequency, y_phase * frequency, x_phase * frequency, y_phase * frequency); value = map_range(value, -1.0, 1.0, 0.0, 255.0); QColor color = qRgb(static_cast(value), static_cast(value), static_cast(value)); cs->fromQColor(color, it.rawData()); } } open_simplex_noise_free(noise_context); } -KisFilterConfigurationSP KisSimplexNoiseGenerator::factoryConfiguration() const +KisFilterConfigurationSP KisSimplexNoiseGenerator::defaultConfiguration() const { - KisFilterConfigurationSP config = new KisFilterConfiguration("simplex_noise", 1); + KisFilterConfigurationSP config = factoryConfiguration(); config->setProperty("looping", false); config->setProperty("frequency", 25.0); uint seed = static_cast(rand()); config->setProperty("seed", seed); config->setProperty("custom_seed_string", ""); config->setProperty("ratio_x", 1.0f); config->setProperty("ratio_y", 1.0f); return config; } KisConfigWidget * KisSimplexNoiseGenerator::createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP dev, bool) const { Q_UNUSED(dev); return new KisWdgSimplexNoise((KisFilter*)this, (QWidget*)parent); } uint KisSimplexNoiseGenerator::seedFromString(const QString &string) const { QByteArray bytes = QCryptographicHash::hash(string.toUtf8(),QCryptographicHash::Md5); uint hash = 0; for( int index = 0; index < bytes.length(); index++){ hash += rotateLeft(bytes[index], index % 32); } return hash; } quint64 KisSimplexNoiseGenerator::rotateLeft(const quint64 input, uint shift) const { return (input << shift)|(input >> (64 - shift)); } #include "simplexnoisegenerator.moc" diff --git a/plugins/generators/simplexnoise/simplexnoisegenerator.h b/plugins/generators/simplexnoise/simplexnoisegenerator.h index a10afa84c4..9719507b00 100644 --- a/plugins/generators/simplexnoise/simplexnoisegenerator.h +++ b/plugins/generators/simplexnoise/simplexnoisegenerator.h @@ -1,65 +1,65 @@ /* * KDE. Krita Project. * * Copyright (c) 2019 Eoin O'Neill * Copyright (c) 2019 Emmet O'Neill * * 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 NOISEFILTER_H #define NOISEFILTER_H #include #include "generator/kis_generator.h" class KisConfigWidget; class KisSimplexNoiseGeneratorHandle : public QObject { Q_OBJECT public: KisSimplexNoiseGeneratorHandle(QObject *parent, const QVariantList &); ~KisSimplexNoiseGeneratorHandle() override; }; class KisSimplexNoiseGenerator : public KisGenerator { public: KisSimplexNoiseGenerator(); using KisGenerator::generate; virtual void generate(KisProcessingInformation dst, const QSize& size, const KisFilterConfigurationSP config, KoUpdater* progressUpdater ) const override; static inline KoID id() { return KoID("simplex_noise", i18n("Simplex Noise")); } - KisFilterConfigurationSP factoryConfiguration() const override; + KisFilterConfigurationSP defaultConfiguration() const override; KisConfigWidget * createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP dev, bool useForMasks) const override; uint seedFromString(const QString &string) const; quint64 rotateLeft(const quint64 input, uint d) const; static inline double map_range(double value, double curr_min, double curr_max, double new_min, double new_max ) { return (value - curr_min) * (new_max - new_min) / (curr_max - curr_min) + new_min; } }; #endif diff --git a/plugins/generators/solid/colorgenerator.cpp b/plugins/generators/solid/colorgenerator.cpp index f5df6bd65d..5ce44970db 100644 --- a/plugins/generators/solid/colorgenerator.cpp +++ b/plugins/generators/solid/colorgenerator.cpp @@ -1,103 +1,103 @@ /* * This file is part of the KDE project * * Copyright (c) 2008 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "colorgenerator.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_wdg_color.h" #include "ui_wdgcoloroptions.h" K_PLUGIN_FACTORY_WITH_JSON(KritaColorGeneratorFactory, "kritacolorgenerator.json", registerPlugin();) KritaColorGenerator::KritaColorGenerator(QObject *parent, const QVariantList &) : QObject(parent) { KisGeneratorRegistry::instance()->add(new KisColorGenerator()); } KritaColorGenerator::~KritaColorGenerator() { } KisColorGenerator::KisColorGenerator() : KisGenerator(id(), KoID("basic"), i18n("&Solid Color...")) { setColorSpaceIndependence(FULLY_INDEPENDENT); setSupportsPainting(true); } -KisFilterConfigurationSP KisColorGenerator::factoryConfiguration() const +KisFilterConfigurationSP KisColorGenerator::defaultConfiguration() const { - KisFilterConfigurationSP config = new KisFilterConfiguration("color", 1); + KisFilterConfigurationSP config = factoryConfiguration(); QVariant v; v.setValue(KoColor()); config->setProperty("color", v); return config; } KisConfigWidget * KisColorGenerator::createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP dev, bool) const { Q_UNUSED(dev); return new KisWdgColor(parent); } void KisColorGenerator::generate(KisProcessingInformation dstInfo, const QSize& size, const KisFilterConfigurationSP config, KoUpdater* progressUpdater) const { KisPaintDeviceSP dst = dstInfo.paintDevice(); Q_ASSERT(!dst.isNull()); Q_ASSERT(config); KoColor c; if (config) { c = config->getColor("color"); KisFillPainter gc(dst); gc.setProgress(progressUpdater); gc.setChannelFlags(config->channelFlags()); gc.setOpacity(100); gc.setSelection(dstInfo.selection()); gc.fillRect(QRect(dstInfo.topLeft(), size), c); gc.end(); } } #include "colorgenerator.moc" diff --git a/plugins/generators/solid/colorgenerator.h b/plugins/generators/solid/colorgenerator.h index e9c8c0759f..4411620760 100644 --- a/plugins/generators/solid/colorgenerator.h +++ b/plugins/generators/solid/colorgenerator.h @@ -1,59 +1,59 @@ /* * This file is part of Krita * * Copyright (c) 2006 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 COLOR_GENERATOR_H #define COLOR_GENERATOR_H #include #include #include "generator/kis_generator.h" class KisConfigWidget; class KritaColorGenerator : public QObject { Q_OBJECT public: KritaColorGenerator(QObject *parent, const QVariantList &); ~KritaColorGenerator() override; }; class KisColorGenerator : public KisGenerator { public: KisColorGenerator(); using KisGenerator::generate; void generate(KisProcessingInformation dst, const QSize& size, const KisFilterConfigurationSP config, KoUpdater* progressUpdater ) const override; static inline KoID id() { return KoID("color", i18n("Color")); } - KisFilterConfigurationSP factoryConfiguration() const override; + KisFilterConfigurationSP defaultConfiguration() const override; KisConfigWidget * createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP dev, bool useForMasks) const override; }; #endif diff --git a/plugins/impex/CMakeLists.txt b/plugins/impex/CMakeLists.txt index 51056ecf47..80961161b5 100644 --- a/plugins/impex/CMakeLists.txt +++ b/plugins/impex/CMakeLists.txt @@ -1,52 +1,56 @@ project(kritafilters) add_subdirectory(libkra) if(CMAKE_SIZEOF_VOID_P EQUAL 4) add_definitions( -DCPU_32_BITS ) endif() if(JPEG_FOUND AND HAVE_LCMS2) add_subdirectory(jpeg) endif() if(TIFF_FOUND) add_subdirectory(tiff) endif() if(PNG_FOUND) add_subdirectory(png) add_subdirectory(csv) endif() if(OPENEXR_FOUND) add_subdirectory(exr) endif() if(Poppler_Qt5_FOUND) add_subdirectory(pdf) endif() +if(OpenJPEG_FOUND) + add_subdirectory(jp2) +endif() + if(LIBRAW_FOUND) add_subdirectory(raw) endif() add_subdirectory(svg) add_subdirectory(qimageio) add_subdirectory(ora) add_subdirectory(xcf) add_subdirectory(psd) add_subdirectory(qml) add_subdirectory(tga) add_subdirectory(heightmap) add_subdirectory(brush) add_subdirectory(spriter) add_subdirectory(kra) if (GIF_FOUND) add_subdirectory(gif) endif() if (HEIF_FOUND) add_subdirectory(heif) endif() diff --git a/plugins/impex/exr/krita_exr.desktop b/plugins/impex/exr/krita_exr.desktop index 52600f6f3c..1bd2051c82 100644 --- a/plugins/impex/exr/krita_exr.desktop +++ b/plugins/impex/exr/krita_exr.desktop @@ -1,126 +1,126 @@ [Desktop Entry] Categories=Qt;KDE;Office;Graphics; Exec=krita %F GenericName=Application for Drawing and Handling of Images GenericName[ar]=تطبيق لرسم الصور والتعامل معها GenericName[bg]=Приложение за рисуване и обработка на изображения GenericName[bs]=Aplikacija za crtanje i upravljanje slikom GenericName[ca]=Aplicació per a dibuix i modificació d'imatges GenericName[ca@valencia]=Aplicació per a dibuix i modificació d'imatges GenericName[da]=Tegne- og billedbehandlingsprogram GenericName[de]=Programm zum Zeichnen und Bearbeiten von Bildern GenericName[el]=Εφαρμογή για επεξεργασία και χειρισμό εικόνων GenericName[en_GB]=Application for Drawing and Handling of Images GenericName[eo]=Aplikaĵo por Desegnado kaj Mastrumado de Bildoj GenericName[es]=Aplicación para dibujo y manipulación de imágenes GenericName[et]=Joonistamise ja pilditöötluse rakendus GenericName[eu]=Irudiak marrazteko eta manipulatzeko aplikazioa GenericName[fa]=کاربرد برای ترسیم و به کار بردن تصاویر GenericName[fi]=Ohjelma kuvien piirtämiseen ja käsittelyyn GenericName[fr]=Application pour dessiner et manipuler des images GenericName[fy]=Aplikaasje om ôfbyldings mei te tekenjen en te bewurkjen GenericName[ga]=Feidhmchlár le haghaidh Líníochta agus Láimhseála Íomhánna -GenericName[gl]=Aplicativo de debuxo e edición de imaxes +GenericName[gl]=Aplicación de debuxo e edición de imaxes GenericName[he]=יישום לצביעה וניהול תמונות GenericName[hi]=छवियों को ड्रा करने तथा उन्हें प्रबन्धित करने का अनुप्रयोग GenericName[hne]=फोटू मन ल ड्रा करे अउ ओ मन ल प्रबन्धित करे के अनुपरयोग GenericName[hu]=Rajzoló és képkezelő GenericName[is]=Teikni og myndvinnsluforrit GenericName[it]=Applicazione di disegno e gestione di immagini GenericName[ja]=描画と画像操作のためのアプリケーション GenericName[kk]=Кескінді салу және өңдеу бағдарламасы GenericName[ko]=그림 그리기 및 처리 프로그램 GenericName[lv]=Programma zīmēšanai un attēlu apstrādei GenericName[nb]=Program for tegning og bildehåndtering GenericName[nds]=Programm för't Teken un Bildhanteren GenericName[ne]=रेखाचित्र बनाउन र छविको ह्यान्डल गर्नका लागि अनुप्रयोग GenericName[nl]=Toepassing om afbeeldingen te tekenen en te bewerken GenericName[nn]=Program for teikning og handsaming av bilete GenericName[pl]=Program do rysowania i obróbki obrazów GenericName[pt]=Aplicação de Desenho e Manipulação de Imagens GenericName[pt_BR]=Aplicativo de desenho e manipulação de imagens GenericName[ru]=Приложение для рисования и редактирования изображений GenericName[sk]=Aplikácia na kresnenie a manilupáciu s obrázkami GenericName[sl]=Program za risanje in rokovanje s slikami GenericName[sv]=Program för att rita och hantera bilder GenericName[ta]=பிம்பங்களை கையாளுதல் மற்றும் வரைதலுக்கான பயன்னாடு GenericName[tr]=Çizim ve Resim İşleme Uygulaması GenericName[uk]=Програма для малювання і обробки зображень GenericName[uz]=Rasm chizish dasturi GenericName[uz@cyrillic]=Расм чизиш дастури GenericName[wa]=Programe po dessiner et apougnî des imådjes GenericName[x-test]=xxApplication for Drawing and Handling of Imagesxx GenericName[zh_CN]=用于绘制和处理图像的应用程序 GenericName[zh_TW]=繪圖與影像處理的應用程式 Icon=krita MimeType=image/exr; Name=Krita Name[af]=Krita Name[ar]=كريتا Name[bg]=Krita Name[br]=Krita Name[bs]=Krita Name[ca]=Krita Name[ca@valencia]=Krita Name[cs]=Krita Name[cy]=Krita Name[da]=Krita Name[de]=Krita Name[el]=Krita Name[en_GB]=Krita Name[eo]=Krita Name[es]=Krita Name[et]=Krita Name[eu]=Krita Name[fi]=Krita Name[fr]=Krita Name[fy]=Krita Name[ga]=Krita Name[gl]=Krita Name[he]=Krita Name[hi]=केरिता Name[hne]=केरिता Name[hr]=Krita Name[hu]=Krita Name[ia]=Krita Name[is]=Krita Name[it]=Krita Name[ja]=Krita Name[kk]=Krita Name[ko]=Krita Name[lt]=Krita Name[lv]=Krita Name[mr]=क्रिटा Name[ms]=Krita Name[nb]=Krita Name[nds]=Krita Name[ne]=क्रिता Name[nl]=Krita Name[nn]=Krita Name[pl]=Krita Name[pt]=Krita Name[pt_BR]=Krita Name[ro]=Krita Name[ru]=Krita Name[se]=Krita Name[sk]=Krita Name[sl]=Krita Name[sv]=Krita Name[ta]=கிரிட்டா Name[tg]=Krita Name[tr]=Krita Name[ug]=Krita Name[uk]=Krita Name[uz]=Krita Name[uz@cyrillic]=Krita Name[wa]=Krita Name[xh]=Krita Name[x-test]=xxKritaxx Name[zh_CN]=Krita Name[zh_TW]=Krita StartupNotify=true Terminal=false Type=Application X-KDE-SubstituteUID=false X-KDE-Username= NoDisplay=true diff --git a/plugins/impex/heif/krita_heif.desktop b/plugins/impex/heif/krita_heif.desktop index e0c46d0466..c2674b456f 100644 --- a/plugins/impex/heif/krita_heif.desktop +++ b/plugins/impex/heif/krita_heif.desktop @@ -1,126 +1,126 @@ [Desktop Entry] Categories=Qt;KDE;Office;Graphics; Exec=krita %F GenericName=Application for Drawing and Handling of Images GenericName[ar]=تطبيق لرسم الصور والتعامل معها GenericName[bg]=Приложение за рисуване и обработка на изображения GenericName[bs]=Aplikacija za crtanje i upravljanje slikom GenericName[ca]=Aplicació per a dibuix i modificació d'imatges GenericName[ca@valencia]=Aplicació per a dibuix i modificació d'imatges GenericName[da]=Tegne- og billedbehandlingsprogram GenericName[de]=Programm zum Zeichnen und Bearbeiten von Bildern GenericName[el]=Εφαρμογή για επεξεργασία και χειρισμό εικόνων GenericName[en_GB]=Application for Drawing and Handling of Images GenericName[eo]=Aplikaĵo por Desegnado kaj Mastrumado de Bildoj GenericName[es]=Aplicación para dibujo y manipulación de imágenes GenericName[et]=Joonistamise ja pilditöötluse rakendus GenericName[eu]=Irudiak marrazteko eta manipulatzeko aplikazioa GenericName[fa]=کاربرد برای ترسیم و به کار بردن تصاویر GenericName[fi]=Ohjelma kuvien piirtämiseen ja käsittelyyn GenericName[fr]=Application pour dessiner et manipuler des images GenericName[fy]=Aplikaasje om ôfbyldings mei te tekenjen en te bewurkjen GenericName[ga]=Feidhmchlár le haghaidh Líníochta agus Láimhseála Íomhánna -GenericName[gl]=Aplicativo de debuxo e edición de imaxes +GenericName[gl]=Aplicación de debuxo e edición de imaxes GenericName[he]=יישום לצביעה וניהול תמונות GenericName[hi]=छवियों को ड्रा करने तथा उन्हें प्रबन्धित करने का अनुप्रयोग GenericName[hne]=फोटू मन ल ड्रा करे अउ ओ मन ल प्रबन्धित करे के अनुपरयोग GenericName[hu]=Rajzoló és képkezelő GenericName[is]=Teikni og myndvinnsluforrit GenericName[it]=Applicazione di disegno e gestione di immagini GenericName[ja]=描画と画像操作のためのアプリケーション GenericName[kk]=Кескінді салу және өңдеу бағдарламасы GenericName[ko]=그림 그리기 및 처리 프로그램 GenericName[lv]=Programma zīmēšanai un attēlu apstrādei GenericName[nb]=Program for tegning og bildehåndtering GenericName[nds]=Programm för't Teken un Bildhanteren GenericName[ne]=रेखाचित्र बनाउन र छविको ह्यान्डल गर्नका लागि अनुप्रयोग GenericName[nl]=Toepassing om afbeeldingen te tekenen en te bewerken GenericName[nn]=Program for teikning og handsaming av bilete GenericName[pl]=Program do rysowania i obróbki obrazów GenericName[pt]=Aplicação de Desenho e Manipulação de Imagens GenericName[pt_BR]=Aplicativo de desenho e manipulação de imagens GenericName[ru]=Приложение для рисования и редактирования изображений GenericName[sk]=Aplikácia na kresnenie a manilupáciu s obrázkami GenericName[sl]=Program za risanje in rokovanje s slikami GenericName[sv]=Program för att rita och hantera bilder GenericName[ta]=பிம்பங்களை கையாளுதல் மற்றும் வரைதலுக்கான பயன்னாடு GenericName[tr]=Çizim ve Resim İşleme Uygulaması GenericName[uk]=Програма для малювання і обробки зображень GenericName[uz]=Rasm chizish dasturi GenericName[uz@cyrillic]=Расм чизиш дастури GenericName[wa]=Programe po dessiner et apougnî des imådjes GenericName[x-test]=xxApplication for Drawing and Handling of Imagesxx GenericName[zh_CN]=用于绘制和处理图像的应用程序 GenericName[zh_TW]=繪圖與影像處理的應用程式 Icon=krita MimeType=image/heic; Name=Krita Name[af]=Krita Name[ar]=كريتا Name[bg]=Krita Name[br]=Krita Name[bs]=Krita Name[ca]=Krita Name[ca@valencia]=Krita Name[cs]=Krita Name[cy]=Krita Name[da]=Krita Name[de]=Krita Name[el]=Krita Name[en_GB]=Krita Name[eo]=Krita Name[es]=Krita Name[et]=Krita Name[eu]=Krita Name[fi]=Krita Name[fr]=Krita Name[fy]=Krita Name[ga]=Krita Name[gl]=Krita Name[he]=Krita Name[hi]=केरिता Name[hne]=केरिता Name[hr]=Krita Name[hu]=Krita Name[ia]=Krita Name[is]=Krita Name[it]=Krita Name[ja]=Krita Name[kk]=Krita Name[ko]=Krita Name[lt]=Krita Name[lv]=Krita Name[mr]=क्रिटा Name[ms]=Krita Name[nb]=Krita Name[nds]=Krita Name[ne]=क्रिता Name[nl]=Krita Name[nn]=Krita Name[pl]=Krita Name[pt]=Krita Name[pt_BR]=Krita Name[ro]=Krita Name[ru]=Krita Name[se]=Krita Name[sk]=Krita Name[sl]=Krita Name[sv]=Krita Name[ta]=கிரிட்டா Name[tg]=Krita Name[tr]=Krita Name[ug]=Krita Name[uk]=Krita Name[uz]=Krita Name[uz@cyrillic]=Krita Name[wa]=Krita Name[xh]=Krita Name[x-test]=xxKritaxx Name[zh_CN]=Krita Name[zh_TW]=Krita StartupNotify=true Terminal=false Type=Application X-KDE-SubstituteUID=false X-KDE-Username= NoDisplay=true diff --git a/plugins/impex/jp2/CMakeLists.txt b/plugins/impex/jp2/CMakeLists.txt new file mode 100644 index 0000000000..63bf879f8a --- /dev/null +++ b/plugins/impex/jp2/CMakeLists.txt @@ -0,0 +1,31 @@ +add_subdirectory(tests) + +include_directories(SYSTEM ${OpenJPEG_INCLUDE_DIR}) + +set(libkritaconverter_LIB_SRCS + jp2_converter.cc +) + +set(kritajp2import_SOURCES + jp2_import.cc + ${libkritaconverter_LIB_SRCS} +) + +add_library(kritajp2import MODULE ${kritajp2import_SOURCES}) + +target_link_libraries(kritajp2import kritaui ${OpenJPEG_LIBRARIES} ) + +install(TARGETS kritajp2import DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) + +#set(kritajp2export_SOURCES +# jp2_export.cc +# ${libkritaconverter_LIB_SRCS} +#) +# +#ki18n_wrap_ui(kritajp2export_SOURCES kis_wdg_options_jp2.ui ) +# +#add_library(kritajp2export MODULE ${kritajp2export_SOURCES}) +# +#target_link_libraries(kritajp2export kritaui ${OPENJPEG_LIBRARIES} ) +#install(TARGETS kritajp2export DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) +install( PROGRAMS krita_jp2.desktop DESTINATION ${XDG_APPS_INSTALL_DIR}) diff --git a/plugins/impex/jp2/jp2_converter.cc b/plugins/impex/jp2/jp2_converter.cc new file mode 100644 index 0000000000..db133db1fe --- /dev/null +++ b/plugins/impex/jp2/jp2_converter.cc @@ -0,0 +1,423 @@ +/* + * Copyright (c) 2019 Aaron Boxer + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "jp2_converter.h" + +#include + +#include +#include + +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include "kis_iterator_ng.h" +#include + +#include +#include + +#define J2K_CFMT 0 +#define JP2_CFMT 1 + +JP2Converter::JP2Converter(KisDocument *doc) { + m_doc = doc; + m_stop = false; +} + +JP2Converter::~JP2Converter() { +} + +/** + * sample error callback expecting a FILE* client object + * */ +static void error_callback(const char *msg, void *client_data) { + JP2Converter *converter = (JP2Converter*) client_data; + converter->addErrorString(msg); +} + +/** + * sample warning callback expecting a FILE* client object + * */ +static void warning_callback(const char *msg, void *client_data) { + JP2Converter *converter = (JP2Converter*) client_data; + converter->addWarningString(msg); +} + +/** + * sample debug callback expecting no client object + * */ +static void info_callback(const char *msg, void *client_data) { + JP2Converter *converter = (JP2Converter*) client_data; + converter->addInfoString(msg); +} + +static int getFileFormat(const char *filename) { + unsigned int i; + static const char *extension[] = { "j2k", "jp2", "j2c", "jpc" }; + static const int format[] = { J2K_CFMT, JP2_CFMT, J2K_CFMT, J2K_CFMT }; + const char *ext = strrchr(filename, '.'); + if (ext == NULL) { + return -1; + } + ext++; + if (*ext) { + for (i = 0; i < sizeof(format) / sizeof(*format); i++) { + if (strcasecmp(ext, extension[i]) == 0) { + return format[i]; + } + } + } + + return -1; +} + +#define JP2_RFC3745_MAGIC "\x00\x00\x00\x0c\x6a\x50\x20\x20\x0d\x0a\x87\x0a" +#define JP2_MAGIC "\x0d\x0a\x87\x0a" +#define J2K_CODESTREAM_MAGIC "\xff\x4f\xff\x51" + +int JP2Converter::infile_format(const char *fname) { + FILE *reader; + const char *s, *magic_s; + int ext_format, magic_format; + unsigned char buf[12]; + OPJ_SIZE_T l_nb_read; + + reader = fopen(fname, "rb"); + + if (reader == NULL) { + return -2; + } + + memset(buf, 0, 12); + l_nb_read = fread(buf, 1, 12, reader); + fclose(reader); + if (l_nb_read != 12) { + return -1; + } + ext_format = getFileFormat(fname); + if (memcmp(buf, JP2_RFC3745_MAGIC, 12) == 0 + || memcmp(buf, JP2_MAGIC, 4) == 0) { + magic_format = JP2_CFMT; + magic_s = ".jp2"; + } else if (memcmp(buf, J2K_CODESTREAM_MAGIC, 4) == 0) { + magic_format = J2K_CFMT; + magic_s = ".j2k or .jpc or .j2c"; + } else { + return -1; + } + + if (magic_format == ext_format) { + return ext_format; + } + + if (strlen(fname) >= 4) { + s = fname + strlen(fname) - 4; + std::ostringstream buffer; + buffer << "The extension of this file is incorrect.\n" + << "Found " << s << " while it should be " << magic_s << "."; + addErrorString(buffer.str()); + } + return magic_format; +} + +KisImportExportErrorCode JP2Converter::buildImage(const QString &filename) { + KisImportExportErrorCode res = ImportExportCodes::OK; + const char *file_str = filename.toUtf8().data(); + opj_codec_t *l_codec = 0; + opj_dparameters_t parameters; + bool hasColorSpaceInfo = false; + opj_stream_t *l_stream = NULL; + opj_image_t *image = NULL; + int pos = 0; + KisHLineIteratorSP it = NULL; + unsigned int numComponents = 0; + unsigned int precision = 0; + const KoColorSpace *colorSpace = 0; + QVector channelorder; + KisPaintLayerSP layer; + bool isSigned; + int32_t signedCorrection = 0; + uint32_t w=0, h=0; + + // decompression parameters + opj_set_default_decoder_parameters(¶meters); + // Determine the type + parameters.decod_format = infile_format(file_str); + if (parameters.decod_format == -1) { + addErrorString("Not a JPEG 2000 file."); + res = ImportExportCodes::FileFormatIncorrect; + goto beach; + } + + // Decode the file + /* get a decoder handle */ + switch (parameters.decod_format) { + case J2K_CFMT: { + l_codec = opj_create_decompress(OPJ_CODEC_J2K); + break; + } + case JP2_CFMT: { + l_codec = opj_create_decompress(OPJ_CODEC_JP2); + hasColorSpaceInfo = true; + break; + } + } + Q_ASSERT(l_codec); + + opj_codec_set_threads( l_codec,QThread::idealThreadCount() ); + + /* setup the decoder decoding parameters using user parameters */ + opj_setup_decoder(l_codec, ¶meters); + + l_stream = opj_stream_create_default_file_stream(file_str, 1); + if (!l_stream) { + addErrorString("Failed to create the stream"); + res = ImportExportCodes::ErrorWhileReading; + goto beach; + } + + // Setup an event handling + opj_set_info_handler(l_codec, info_callback, this); + opj_set_error_handler(l_codec, error_callback, this); + opj_set_warning_handler(l_codec, warning_callback, this); + + if (!opj_read_header(l_stream, l_codec, &image)) { + addErrorString("Failed to read the header"); + res = ImportExportCodes::ErrorWhileReading; + goto beach; + } + + /* Get the decoded image */ + if (!(opj_decode(l_codec, l_stream, image) + && opj_end_decompress(l_codec, l_stream))) { + addErrorString("Failed to decode image"); + res = ImportExportCodes::ErrorWhileReading; + goto beach; + } + + // Look for the colorspace + numComponents = image->numcomps; + if (image->numcomps == 0) { + addErrorString("Image must have at least one component"); + res = ImportExportCodes::Failure; + goto beach; + } + precision = image->comps[0].prec; + for (uint32_t i = 1; i < numComponents; ++i) { + if (image->comps[i].prec != precision) { + std::ostringstream buffer; + buffer << "All components must have the same bit depth " + << precision; + addErrorString(buffer.str()); + res = ImportExportCodes::FormatFeaturesUnsupported; + goto beach; + } + } + isSigned = false; + for (uint32_t i = 0; i < numComponents; ++i) { + if ((image->comps[i].dx != 1) || (image->comps[i].dy != 1)) { + addErrorString("Sub-sampling not supported"); + res = ImportExportCodes::FormatFeaturesUnsupported; + goto beach; + } + isSigned = isSigned || (image->comps[0].sgnd); + } + if (isSigned) + signedCorrection = 1 << (precision - 1); + + dbgFile + << "Image has " << numComponents << " numComponents and a bit depth of " + << precision << " for color space " << image->color_space; + channelorder = QVector(numComponents); + if (!hasColorSpaceInfo) { + if (numComponents == 3) { + image->color_space = OPJ_CLRSPC_SRGB; + } else if (numComponents == 1) { + image->color_space = OPJ_CLRSPC_GRAY; + } + } + switch (image->color_space) { + case OPJ_CLRSPC_UNKNOWN: + case OPJ_CLRSPC_UNSPECIFIED: + break; + case OPJ_CLRSPC_SRGB: { + if (precision == 16 || precision == 12) { + colorSpace = KoColorSpaceRegistry::instance()->rgb16(); + } else if (precision == 8) { + colorSpace = KoColorSpaceRegistry::instance()->rgb8(); + } + if (numComponents != 3) { + std::ostringstream buffer; + buffer << "sRGB: number of numComponents " << numComponents + << " does not equal 3"; + addErrorString(buffer.str()); + res = ImportExportCodes::FormatFeaturesUnsupported; + goto beach; + } + channelorder[0] = KoBgrU16Traits::red_pos; + channelorder[1] = KoBgrU16Traits::green_pos; + channelorder[2] = KoBgrU16Traits::blue_pos; + break; + } + case OPJ_CLRSPC_GRAY: { + if (precision == 16 || precision == 12) { + colorSpace = KoColorSpaceRegistry::instance()->colorSpace( + GrayAColorModelID.id(), Integer16BitsColorDepthID.id(), ""); + } else if (precision == 8) { + colorSpace = KoColorSpaceRegistry::instance()->colorSpace( + GrayAColorModelID.id(), Integer8BitsColorDepthID.id(), ""); + } + if (numComponents != 1) { + std::ostringstream buffer; + buffer << "Grayscale: number of numComponents " << numComponents + << " greater than 1"; + addErrorString(buffer.str()); + res = ImportExportCodes::FormatFeaturesUnsupported; + goto beach; + } + channelorder[0] = 0; + break; + } + case OPJ_CLRSPC_SYCC: + addErrorString("YUV color space not supported"); + res = ImportExportCodes::FormatColorSpaceUnsupported; + goto beach; + break; + case OPJ_CLRSPC_EYCC: + addErrorString("eYCC color space not supported"); + res = ImportExportCodes::FormatColorSpaceUnsupported; + goto beach; + break; + case OPJ_CLRSPC_CMYK: + addErrorString("CMYK color space not supported"); + res = ImportExportCodes::FormatColorSpaceUnsupported; + goto beach; + break; + default: + break; + } + + if (!colorSpace) { + addErrorString("No color space found for image"); + res = ImportExportCodes::FormatColorSpaceUnsupported; + goto beach; + } + + // Create the image + w = (uint32_t)(image->x1 - image->x0); + h = (uint32_t)(image->y1 - image->y0); + if (m_image == 0) { + m_image = new KisImage(m_doc->createUndoStore(), w, h, + colorSpace, "built image"); + } + + // Create the layer + layer = new KisPaintLayer(m_image, m_image->nextLayerName(), + OPACITY_OPAQUE_U8); + m_image->addNode(layer); + + // Set the data + it = layer->paintDevice()->createHLineIteratorNG(0, 0, w); + for (OPJ_UINT32 v = 0; v < image->y1; ++v) { + if (precision == 16 || precision == 12) { + do { + quint16 *px = reinterpret_cast(it->rawData()); + for (uint32_t i = 0; i < numComponents; ++i) { + px[channelorder[i]] = image->comps[i].data[pos] + + signedCorrection; + } + colorSpace->setOpacity(it->rawData(), OPACITY_OPAQUE_U8, 1); + ++pos; + + } while (it->nextPixel()); + } else if (precision == 8) { + do { + quint8 *px = it->rawData(); + for (uint32_t i = 0; i < numComponents; ++i) { + px[channelorder[i]] = image->comps[i].data[pos] + + signedCorrection; + } + colorSpace->setOpacity(px, OPACITY_OPAQUE_U8, 1); + ++pos; + + } while (it->nextPixel()); + } + it->nextRow(); + } + +beach: + if (l_stream) + opj_stream_destroy(l_stream); + if (l_codec) + opj_destroy_codec(l_codec); + if (image) + opj_image_destroy(image); + if (!err.empty()) + m_doc->setErrorMessage(i18n(err.c_str())); + if (!warn.empty()) + m_doc->setWarningMessage(i18n(warn.c_str())); + return res; +} + +KisImageWSP JP2Converter::image() { + return m_image; +} + +KisImportExportErrorCode JP2Converter::buildFile(const QString &filename, + KisPaintLayerSP layer, const JP2ConvertOptions &options) { + (void) layer; + (void) filename; + (void) options; + return ImportExportCodes::Failure; +} + +void JP2Converter::cancel() { + m_stop = true; +} + +void JP2Converter::addWarningString(const std::string &str) { + if (!warn.empty()) + warn += "\n"; + warn += str; +} +void JP2Converter::addInfoString(const std::string &str) { + dbgFile + << str.c_str(); +} + +void JP2Converter::addErrorString(const std::string &str) { + if (!err.empty()) + err += "\n"; + err += str; +} + diff --git a/plugins/impex/jp2/jp2_converter.h b/plugins/impex/jp2/jp2_converter.h new file mode 100644 index 0000000000..e0a1dbc155 --- /dev/null +++ b/plugins/impex/jp2/jp2_converter.h @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2009 Cyrille Berger + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef _JP2_CONVERTER_H_ +#define _JP2_CONVERTER_H_ + +#include + +#include +#include + +#include "kis_types.h" +#include "kis_global.h" +#include "kis_annotation.h" +#include + +class KisDocument; + +struct JP2ConvertOptions { + int rate; + int numberresolution; +}; + +class JP2Converter: public QObject { +Q_OBJECT +public: + JP2Converter(KisDocument *doc); + virtual ~JP2Converter(); +public: + KisImportExportErrorCode buildImage(const QString &filename); + KisImportExportErrorCode buildFile(const QString &filename, + KisPaintLayerSP layer, const JP2ConvertOptions &options); + /** + * Retrieve the constructed image + */ + KisImageWSP image(); + void addErrorString(const std::string& str); + void addWarningString(const std::string& str); + void addInfoString(const std::string& str); + +private: + KisImportExportErrorCode decode(const QString &filename); + int infile_format(const char *fname); +public Q_SLOTS: + virtual void cancel(); +private: + KisImageSP m_image; + KisDocument *m_doc; + bool m_stop; + std::string err; + std::string warn; +}; + +#endif diff --git a/plugins/impex/jp2/jp2_export.cc b/plugins/impex/jp2/jp2_export.cc new file mode 100644 index 0000000000..a7f92b35a2 --- /dev/null +++ b/plugins/impex/jp2/jp2_export.cc @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2009 Cyrille Berger + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "jp2_export.h" + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "jp2_converter.h" + +#include "ui_kis_wdg_options_jp2.h" + +class KisExternalLayer; + +K_PLUGIN_FACTORY_WITH_JSON(ExportFactory, "krita_jp2_export.json", registerPlugin();) + +jp2Export::jp2Export(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) +{ +} + +jp2Export::~jp2Export() +{ +} + +KisImportExportFilter::ConversionStatus jp2Export::convert(const QByteArray& from, const QByteArray& to) +{ + dbgFile << "JP2 export! From:" << from << ", To:" << to << ""; + + if (from != "application/x-krita") + return KisImportExportFilter::NotImplemented; + + KisDocument *input = inputDocument(); + QString filename = outputFile(); + + if (!input) + return KisImportExportFilter::NoDocumentCreated; + + KisImageWSP image = input->image(); + Q_CHECK_PTR(image); + + if (filename.isEmpty()) return KisImportExportFilter::FileNotFound; + + KoDialog* kdb = new KoDialog(0); + kdb->setWindowTitle(i18n("JPEG 2000 Export Options")); + kdb->setButtons(KoDialog::Ok | KoDialog::Cancel); + + Ui::WdgOptionsJP2 optionsJP2; + + QWidget* wdg = new QWidget(kdb); + optionsJP2.setupUi(wdg); + + QString filterConfig = KisConfig().exportConfiguration("JP2"); + KisPropertiesConfiguration cfg; + cfg.fromXML(filterConfig); + optionsJP2.numberResolutions->setValue(cfg.getInt("number_resolutions", 6)); + optionsJP2.qualityLevel->setValue(cfg.getInt("quality", 100)); + + kdb->setMainWidget(wdg); + QApplication::restoreOverrideCursor(); + + if (!getBatchMode()) { + if (kdb->exec() == QDialog::Rejected) { + return KisImportExportFilter::UserCancelled; + } + } + + JP2ConvertOptions options; + options.numberresolution = optionsJP2.numberResolutions->value(); + cfg.setProperty("number_resolutions", options.numberresolution); + options.rate = optionsJP2.qualityLevel->value(); + cfg.setProperty("quality", options.rate); + + KisConfig().setExportConfiguration("JP2", cfg); + + + // the image must be locked at the higher levels + KIS_SAFE_ASSERT_RECOVER_NOOP(input->image()->locked()); + jp2Converter kpc(input); + + KisPaintDeviceSP pd = new KisPaintDevice(*image->projection()); + KisPaintLayerSP l = new KisPaintLayer(image, "projection", OPACITY_OPAQUE_U8, pd); + + KisImageBuilder_Result res; + + if ((res = kpc.buildFile(filename, l, options)) == KisImageBuilder_RESULT_OK) { + dbgFile << "success !"; + return KisImportExportFilter::OK; + } + dbgFile << " Result =" << res; + return KisImportExportFilter::InternalError; +} + +#include + diff --git a/plugins/impex/jp2/jp2_export.h b/plugins/impex/jp2/jp2_export.h new file mode 100644 index 0000000000..d55ef897fe --- /dev/null +++ b/plugins/impex/jp2/jp2_export.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2009 Cyrille Berger + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef _JP2_EXPORT_H_ +#define _JP2_EXPORT_H_ + +#include + +#include + +class jp2Export : public KisImportExportFilter +{ + Q_OBJECT +public: + jp2Export(QObject *parent, const QVariantList &); + virtual ~jp2Export(); +public: + virtual KisImportExportFilter::ConversionStatus convert(const QByteArray& from, const QByteArray& to); +}; + +#endif diff --git a/plugins/impex/jp2/jp2_import.cc b/plugins/impex/jp2/jp2_import.cc new file mode 100644 index 0000000000..c7178521aa --- /dev/null +++ b/plugins/impex/jp2/jp2_import.cc @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2009 Cyrille Berger + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "jp2_import.h" + +#include +#include + +#include +#include + +#include "jp2_converter.h" + +K_PLUGIN_FACTORY_WITH_JSON(ImportFactory, "krita_jp2_import.json", registerPlugin();) + +jp2Import::jp2Import(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) +{ +} + +jp2Import::~jp2Import() +{ +} + +KisImportExportErrorCode jp2Import::convert(KisDocument *document, QIODevice */*io*/, KisPropertiesConfigurationSP /*configuration*/) +{ + JP2Converter converter(document); + KisImportExportErrorCode result = converter.buildImage(filename()); + if (result.isOk()) { + document->setCurrentImage(converter.image()); + } + return result; +} + +#include + diff --git a/plugins/impex/jp2/jp2_import.h b/plugins/impex/jp2/jp2_import.h new file mode 100644 index 0000000000..3cbfaf6ad5 --- /dev/null +++ b/plugins/impex/jp2/jp2_import.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2009 Cyrille Berger + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef JP2_IMPORT_H_ +#define JP2_IMPORT_H_ + +#include + +#include + +class jp2Import : public KisImportExportFilter +{ + Q_OBJECT +public: + jp2Import(QObject *parent, const QVariantList &); + virtual ~jp2Import(); +public: + KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; +}; + +#endif diff --git a/plugins/impex/jp2/kis_wdg_options_jp2.ui b/plugins/impex/jp2/kis_wdg_options_jp2.ui new file mode 100644 index 0000000000..da40c9d2ea --- /dev/null +++ b/plugins/impex/jp2/kis_wdg_options_jp2.ui @@ -0,0 +1,146 @@ + + + WdgOptionsJP2 + + + + 0 + 0 + 400 + 300 + + + + + + + + + Compression + + + + + + + <html><head/><body><p>These settings determine how much information is lost during compression. Low: small files, bad quality. High: big files, good quality.</p></body></html> + + + 0 + + + 100 + + + 10 + + + 1 + + + 100 + + + Qt::Horizontal + + + QSlider::TicksBelow + + + 10 + + + + + + + 100 + + + 0 + + + 100 + + + + + + + + + + + Number of resolutions: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + 1 + + + 20 + + + 6 + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + qualityLevel + valueChanged(int) + intQuality + setValue(int) + + + 322 + 22 + + + 367 + 19 + + + + + intQuality + valueChanged(int) + qualityLevel + setValue(int) + + + 373 + 21 + + + 328 + 18 + + + + + diff --git a/plugins/impex/exr/krita_exr.desktop b/plugins/impex/jp2/krita_jp2.desktop similarity index 88% copy from plugins/impex/exr/krita_exr.desktop copy to plugins/impex/jp2/krita_jp2.desktop index 52600f6f3c..2d25482673 100644 --- a/plugins/impex/exr/krita_exr.desktop +++ b/plugins/impex/jp2/krita_jp2.desktop @@ -1,126 +1,117 @@ [Desktop Entry] Categories=Qt;KDE;Office;Graphics; -Exec=krita %F GenericName=Application for Drawing and Handling of Images -GenericName[ar]=تطبيق لرسم الصور والتعامل معها GenericName[bg]=Приложение за рисуване и обработка на изображения GenericName[bs]=Aplikacija za crtanje i upravljanje slikom GenericName[ca]=Aplicació per a dibuix i modificació d'imatges GenericName[ca@valencia]=Aplicació per a dibuix i modificació d'imatges GenericName[da]=Tegne- og billedbehandlingsprogram GenericName[de]=Programm zum Zeichnen und Bearbeiten von Bildern GenericName[el]=Εφαρμογή για επεξεργασία και χειρισμό εικόνων GenericName[en_GB]=Application for Drawing and Handling of Images GenericName[eo]=Aplikaĵo por Desegnado kaj Mastrumado de Bildoj GenericName[es]=Aplicación para dibujo y manipulación de imágenes GenericName[et]=Joonistamise ja pilditöötluse rakendus GenericName[eu]=Irudiak marrazteko eta manipulatzeko aplikazioa GenericName[fa]=کاربرد برای ترسیم و به کار بردن تصاویر GenericName[fi]=Ohjelma kuvien piirtämiseen ja käsittelyyn GenericName[fr]=Application pour dessiner et manipuler des images GenericName[fy]=Aplikaasje om ôfbyldings mei te tekenjen en te bewurkjen GenericName[ga]=Feidhmchlár le haghaidh Líníochta agus Láimhseála Íomhánna GenericName[gl]=Aplicativo de debuxo e edición de imaxes GenericName[he]=יישום לצביעה וניהול תמונות GenericName[hi]=छवियों को ड्रा करने तथा उन्हें प्रबन्धित करने का अनुप्रयोग GenericName[hne]=फोटू मन ल ड्रा करे अउ ओ मन ल प्रबन्धित करे के अनुपरयोग GenericName[hu]=Rajzoló és képkezelő GenericName[is]=Teikni og myndvinnsluforrit GenericName[it]=Applicazione di disegno e gestione di immagini -GenericName[ja]=描画と画像操作のためのアプリケーション GenericName[kk]=Кескінді салу және өңдеу бағдарламасы GenericName[ko]=그림 그리기 및 처리 프로그램 GenericName[lv]=Programma zīmēšanai un attēlu apstrādei GenericName[nb]=Program for tegning og bildehåndtering GenericName[nds]=Programm för't Teken un Bildhanteren GenericName[ne]=रेखाचित्र बनाउन र छविको ह्यान्डल गर्नका लागि अनुप्रयोग GenericName[nl]=Toepassing om afbeeldingen te tekenen en te bewerken -GenericName[nn]=Program for teikning og handsaming av bilete GenericName[pl]=Program do rysowania i obróbki obrazów GenericName[pt]=Aplicação de Desenho e Manipulação de Imagens GenericName[pt_BR]=Aplicativo de desenho e manipulação de imagens -GenericName[ru]=Приложение для рисования и редактирования изображений +GenericName[ru]=Растровые изображения GenericName[sk]=Aplikácia na kresnenie a manilupáciu s obrázkami GenericName[sl]=Program za risanje in rokovanje s slikami GenericName[sv]=Program för att rita och hantera bilder GenericName[ta]=பிம்பங்களை கையாளுதல் மற்றும் வரைதலுக்கான பயன்னாடு GenericName[tr]=Çizim ve Resim İşleme Uygulaması GenericName[uk]=Програма для малювання і обробки зображень GenericName[uz]=Rasm chizish dasturi GenericName[uz@cyrillic]=Расм чизиш дастури GenericName[wa]=Programe po dessiner et apougnî des imådjes GenericName[x-test]=xxApplication for Drawing and Handling of Imagesxx -GenericName[zh_CN]=用于绘制和处理图像的应用程序 -GenericName[zh_TW]=繪圖與影像處理的應用程式 -Icon=krita -MimeType=image/exr; +GenericName[zh_CN]=绘制和操纵图像的应用程序 +GenericName[zh_TW]=影像繪製與處理應用程式 Name=Krita Name[af]=Krita -Name[ar]=كريتا Name[bg]=Krita Name[br]=Krita Name[bs]=Krita Name[ca]=Krita Name[ca@valencia]=Krita Name[cs]=Krita Name[cy]=Krita Name[da]=Krita Name[de]=Krita Name[el]=Krita Name[en_GB]=Krita Name[eo]=Krita Name[es]=Krita Name[et]=Krita Name[eu]=Krita Name[fi]=Krita Name[fr]=Krita Name[fy]=Krita Name[ga]=Krita Name[gl]=Krita Name[he]=Krita Name[hi]=केरिता Name[hne]=केरिता Name[hr]=Krita Name[hu]=Krita Name[ia]=Krita Name[is]=Krita Name[it]=Krita -Name[ja]=Krita Name[kk]=Krita Name[ko]=Krita Name[lt]=Krita Name[lv]=Krita Name[mr]=क्रिटा Name[ms]=Krita Name[nb]=Krita Name[nds]=Krita Name[ne]=क्रिता Name[nl]=Krita -Name[nn]=Krita Name[pl]=Krita Name[pt]=Krita Name[pt_BR]=Krita Name[ro]=Krita Name[ru]=Krita Name[se]=Krita Name[sk]=Krita Name[sl]=Krita Name[sv]=Krita Name[ta]=கிரிட்டா Name[tg]=Krita Name[tr]=Krita Name[ug]=Krita Name[uk]=Krita Name[uz]=Krita Name[uz@cyrillic]=Krita Name[wa]=Krita Name[xh]=Krita Name[x-test]=xxKritaxx Name[zh_CN]=Krita -Name[zh_TW]=Krita -StartupNotify=true -Terminal=false +Name[zh_TW]=繪圖_Krita +Exec=krita %u +MimeType=image/jp2; Type=Application -X-KDE-SubstituteUID=false -X-KDE-Username= +Icon=krita +StartupNotify=true NoDisplay=true diff --git a/plugins/impex/jp2/krita_jp2_export.json b/plugins/impex/jp2/krita_jp2_export.json new file mode 100644 index 0000000000..31c18a1af3 --- /dev/null +++ b/plugins/impex/jp2/krita_jp2_export.json @@ -0,0 +1,12 @@ +{ + "Id": "Krita jpeg2000 Export Filter", + "NoDisplay": "true", + "Type": "Service", + "X-KDE-Export": "image/jp2", + "X-KDE-Library": "kritajp2export", + "X-KDE-ServiceTypes": [ + "Krita/FileFilter" + ], + "X-KDE-Weight": "1", + "X-KDE-Extensions" : "jp2" +} diff --git a/plugins/impex/jp2/krita_jp2_import.json b/plugins/impex/jp2/krita_jp2_import.json new file mode 100644 index 0000000000..c790c0459a --- /dev/null +++ b/plugins/impex/jp2/krita_jp2_import.json @@ -0,0 +1,12 @@ +{ + "Id": "Krita jpeg2000 Import Filter", + "NoDisplay": "true", + "Type": "Service", + "X-KDE-Import": "image/jp2,image/j2k", + "X-KDE-Library": "kritajp2import", + "X-KDE-ServiceTypes": [ + "Krita/FileFilter" + ], + "X-KDE-Weight": "1", + "X-KDE-Extensions" : "jp2,j2k" +} diff --git a/plugins/impex/jp2/tests/CMakeLists.txt b/plugins/impex/jp2/tests/CMakeLists.txt new file mode 100644 index 0000000000..596045d947 --- /dev/null +++ b/plugins/impex/jp2/tests/CMakeLists.txt @@ -0,0 +1,11 @@ +set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) +include_directories( ${CMAKE_SOURCE_DIR}/sdk/tests ) + +include(KritaAddBrokenUnitTest) + +macro_add_unittest_definitions() + +ecm_add_test(KisJP2Test.cpp + TEST_NAME KisJP2Test + LINK_LIBRARIES kritaui Qt5::Test + NAME_PREFIX "plugins-impex-") diff --git a/plugins/impex/jp2/tests/KisJP2Test.cpp b/plugins/impex/jp2/tests/KisJP2Test.cpp new file mode 100644 index 0000000000..0b37ee520a --- /dev/null +++ b/plugins/impex/jp2/tests/KisJP2Test.cpp @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2019 Agata Cacko + * + * 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 "filestest.h" + +#ifndef FILES_DATA_DIR +#error "FILES_DATA_DIR not set. A directory with the data used for testing the importing of files in krita" +#endif + + +const QString JP2Mimetype = "image/jp2"; + + + +void KisJP2Test::testImportFromWriteonly() +{ + TestUtil::testImportFromWriteonly(QString(FILES_DATA_DIR), JP2Mimetype); +} + + +void KisJP2Test::testExportToReadonly() +{ + TestUtil::testExportToReadonly(QString(FILES_DATA_DIR), JP2Mimetype); +} + + +void KisJP2Test::testImportIncorrectFormat() +{ + TestUtil::testImportIncorrectFormat(QString(FILES_DATA_DIR), JP2Mimetype); +} + + + +KISTEST_MAIN(KisJP2Test) + + diff --git a/plugins/impex/qimageio/kis_qimageio_export.h b/plugins/impex/jp2/tests/KisJP2Test.h similarity index 52% copy from plugins/impex/qimageio/kis_qimageio_export.h copy to plugins/impex/jp2/tests/KisJP2Test.h index 423f9876b5..c60f218a5d 100644 --- a/plugins/impex/qimageio/kis_qimageio_export.h +++ b/plugins/impex/jp2/tests/KisJP2Test.h @@ -1,38 +1,35 @@ /* - * Copyright (c) 2007 Boudewijn Rempt + * Copyright (C) 2019 Agata Cacko * * 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. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifndef _KIS_BMP_EXPORT_H_ -#define _KIS_BMP_EXPORT_H_ +#ifndef _KIS_JP2_TEST_H_ +#define _KIS_JP2_TEST_H_ -#include +#include -#include - -class KisQImageIOExport : public KisImportExportFilter +class KisJP2Test : public QObject { Q_OBJECT -public: - KisQImageIOExport(QObject *parent, const QVariantList &); - ~KisQImageIOExport() override; -public: - KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; - void initializeCapabilities() override; +private Q_SLOTS: + + void testImportFromWriteonly(); + void testExportToReadonly(); + void testImportIncorrectFormat(); }; -#endif +#endif // _KIS_JP2_TEST_H_ + diff --git a/plugins/impex/jp2/tests/data/incorrectFormatFile.txt b/plugins/impex/jp2/tests/data/incorrectFormatFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/jp2/tests/data/readonlyFile.txt b/plugins/impex/jp2/tests/data/readonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/jp2/tests/data/writeonlyFile.txt b/plugins/impex/jp2/tests/data/writeonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/libkra/kis_kra_load_visitor.cpp b/plugins/impex/libkra/kis_kra_load_visitor.cpp index 17386086e0..51716bcebc 100644 --- a/plugins/impex/libkra/kis_kra_load_visitor.cpp +++ b/plugins/impex/libkra/kis_kra_load_visitor.cpp @@ -1,783 +1,784 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2005 C. Boemann * * 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_kra_load_visitor.h" #include "kis_kra_tags.h" #include "flake/kis_shape_layer.h" #include "flake/KisReferenceImagesLayer.h" #include "KisReferenceImage.h" #include #include #include #include #include #include #include #include #include #include #include #include #include // kritaimage #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_transform_mask_params_factory_registry.h" #include #include #include #include #include "kis_shape_selection.h" #include "kis_colorize_dom_utils.h" #include "kis_dom_utils.h" #include "kis_raster_keyframe_channel.h" #include "kis_paint_device_frames_interface.h" #include "kis_filter_registry.h" using namespace KRA; QString expandEncodedDirectory(const QString& _intern) { QString intern = _intern; QString result; int pos; while ((pos = intern.indexOf('/')) != -1) { if (QChar(intern.at(0)).isDigit()) result += "part"; result += intern.left(pos + 1); // copy numbers (or "pictures") + "/" intern = intern.mid(pos + 1); // remove the dir we just processed } if (!intern.isEmpty() && QChar(intern.at(0)).isDigit()) result += "part"; result += intern; return result; } KisKraLoadVisitor::KisKraLoadVisitor(KisImageSP image, KoStore *store, KoShapeControllerBase *shapeController, QMap &layerFilenames, QMap &keyframeFilenames, const QString & name, int syntaxVersion) : KisNodeVisitor() , m_image(image) , m_store(store) , m_external(false) , m_layerFilenames(layerFilenames) , m_keyframeFilenames(keyframeFilenames) , m_name(name) , m_shapeController(shapeController) { m_store->pushDirectory(); if (!m_store->enterDirectory(m_name)) { QStringList directories = m_store->directoryList(); dbgKrita << directories; if (directories.size() > 0) { dbgFile << "Could not locate the directory, maybe some encoding issue? Grab the first directory, that'll be the image one." << m_name << directories; m_name = directories.first(); } else { dbgFile << "Could not enter directory" << m_name << ", probably an old-style file with 'part' added."; m_name = expandEncodedDirectory(m_name); } } else { m_store->popDirectory(); } m_syntaxVersion = syntaxVersion; } void KisKraLoadVisitor::setExternalUri(const QString &uri) { m_external = true; m_uri = uri; } bool KisKraLoadVisitor::visit(KisExternalLayer * layer) { bool result = false; if (auto *referencesLayer = dynamic_cast(layer)) { Q_FOREACH(KoShape *shape, referencesLayer->shapes()) { auto *reference = dynamic_cast(shape); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(reference, false); while (!reference->loadImage(m_store)) { if (reference->embed()) { m_errorMessages << i18n("Could not load embedded reference image %1 ", reference->internalFile()); break; } else { QString msg = i18nc( "@info", "A reference image linked to an external file could not be loaded.\n\n" "Path: %1\n\n" "Do you want to select another location?", reference->filename()); int locateManually = QMessageBox::warning(0, i18nc("@title:window", "File not found"), msg, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); QString url; if (locateManually == QMessageBox::Yes) { KoFileDialog dialog(0, KoFileDialog::OpenFile, "OpenDocument"); dialog.setMimeTypeFilters(KisImportExportManager::supportedMimeTypes(KisImportExportManager::Import)); url = dialog.filename(); } if (url.isEmpty()) { break; } else { reference->setFilename(url); } } } } } else if (KisShapeLayer *shapeLayer = dynamic_cast(layer)) { if (!loadMetaData(layer)) { return false; } m_store->pushDirectory(); m_store->enterDirectory(getLocation(layer, DOT_SHAPE_LAYER)) ; result = shapeLayer->loadLayer(m_store); m_store->popDirectory(); } result = visitAll(layer) && result; return result; } bool KisKraLoadVisitor::visit(KisPaintLayer *layer) { loadNodeKeyframes(layer); if (!loadPaintDevice(layer->paintDevice(), getLocation(layer))) { return false; } if (!loadProfile(layer->paintDevice(), getLocation(layer, DOT_ICC))) { return false; } if (!loadMetaData(layer)) { return false; } if (m_syntaxVersion == 1) { // Check whether there is a file with a .mask extension in the // layer directory, if so, it's an old-style transparency mask // that should be converted. QString location = getLocation(layer, ".mask"); if (m_store->open(location)) { KisSelectionSP selection = KisSelectionSP(new KisSelection()); KisPixelSelectionSP pixelSelection = selection->pixelSelection(); if (!pixelSelection->read(m_store->device())) { pixelSelection->disconnect(); } else { KisTransparencyMask* mask = new KisTransparencyMask(); mask->setSelection(selection); m_image->addNode(mask, layer, layer->firstChild()); } m_store->close(); } } bool result = visitAll(layer); return result; } bool KisKraLoadVisitor::visit(KisGroupLayer *layer) { if (*layer->colorSpace() != *m_image->colorSpace()) { layer->resetCache(m_image->colorSpace()); } if (!loadMetaData(layer)) { return false; } bool result = visitAll(layer); return result; } bool KisKraLoadVisitor::visit(KisAdjustmentLayer* layer) { loadNodeKeyframes(layer); // Adjustmentlayers are tricky: there's the 1.x style and the 2.x // style, which has selections with selection components bool result = true; if (m_syntaxVersion == 1) { KisSelectionSP selection = new KisSelection(); KisPixelSelectionSP pixelSelection = selection->pixelSelection(); result = loadPaintDevice(pixelSelection, getLocation(layer, ".selection")); layer->setInternalSelection(selection); } else if (m_syntaxVersion == 2) { result = loadSelection(getLocation(layer), layer->internalSelection()); } else { // We use the default, empty selection } if (!loadMetaData(layer)) { return false; } - loadFilterConfiguration(layer->filter(), getLocation(layer, DOT_FILTERCONFIG)); + loadFilterConfiguration(layer, getLocation(layer, DOT_FILTERCONFIG)); fixOldFilterConfigurations(layer->filter()); result = visitAll(layer); return result; } bool KisKraLoadVisitor::visit(KisGeneratorLayer *layer) { if (!loadMetaData(layer)) { return false; } bool result = true; loadNodeKeyframes(layer); result = loadSelection(getLocation(layer), layer->internalSelection()); // HACK ALERT: we set the same filter again to ensure the layer // is correctly updated - KisFilterConfigurationSP filter = layer->filter(); - result = loadFilterConfiguration(filter.data(), getLocation(layer, DOT_FILTERCONFIG)); - layer->setFilter(filter); + result = loadFilterConfiguration(layer, getLocation(layer, DOT_FILTERCONFIG)); + layer->setFilter(layer->filter()); result = visitAll(layer); return result; } bool KisKraLoadVisitor::visit(KisCloneLayer *layer) { if (!loadMetaData(layer)) { return false; } // the layer might have already been lazily initialized // from the mask loading code if (layer->copyFrom()) { return true; } KisNodeSP srcNode = layer->copyFromInfo().findNode(m_image->rootLayer()); KisLayerSP srcLayer = qobject_cast(srcNode.data()); Q_ASSERT(srcLayer); layer->setCopyFrom(srcLayer); // Clone layers have no data except for their masks bool result = visitAll(layer); return result; } void KisKraLoadVisitor::initSelectionForMask(KisMask *mask) { KisLayer *cloneLayer = dynamic_cast(mask->parent().data()); if (cloneLayer) { // the clone layers should be initialized out of order // and lazily, because their original() is still not // initialized cloneLayer->accept(*this); } KisLayer *parentLayer = qobject_cast(mask->parent().data()); // the KisKraLoader must have already set the parent for us Q_ASSERT(parentLayer); mask->initSelection(parentLayer); } bool KisKraLoadVisitor::visit(KisFilterMask *mask) { initSelectionForMask(mask); loadNodeKeyframes(mask); bool result = true; result = loadSelection(getLocation(mask), mask->selection()); - result = loadFilterConfiguration(mask->filter(), getLocation(mask, DOT_FILTERCONFIG)); + result = loadFilterConfiguration(mask, getLocation(mask, DOT_FILTERCONFIG)); fixOldFilterConfigurations(mask->filter()); return result; } bool KisKraLoadVisitor::visit(KisTransformMask *mask) { QString location = getLocation(mask, DOT_TRANSFORMCONFIG); if (m_store->hasFile(location)) { QByteArray data; m_store->open(location); data = m_store->read(m_store->size()); m_store->close(); if (!data.isEmpty()) { QDomDocument doc; doc.setContent(data); QDomElement rootElement = doc.documentElement(); QDomElement main; if (!KisDomUtils::findOnlyElement(rootElement, "main", &main/*, &m_errorMessages*/)) { return false; } QString id = main.attribute("id", "not-valid"); if (id == "not-valid") { m_errorMessages << i18n("Could not load \"id\" of the transform mask"); return false; } QDomElement data; if (!KisDomUtils::findOnlyElement(rootElement, "data", &data, &m_errorMessages)) { return false; } KisTransformMaskParamsInterfaceSP params = KisTransformMaskParamsFactoryRegistry::instance()->createParams(id, data); if (!params) { m_errorMessages << i18n("Could not create transform mask params"); return false; } mask->setTransformParams(params); loadNodeKeyframes(mask); params->clearChangedFlag(); return true; } } return false; } bool KisKraLoadVisitor::visit(KisTransparencyMask *mask) { initSelectionForMask(mask); loadNodeKeyframes(mask); return loadSelection(getLocation(mask), mask->selection()); } bool KisKraLoadVisitor::visit(KisSelectionMask *mask) { initSelectionForMask(mask); return loadSelection(getLocation(mask), mask->selection()); } bool KisKraLoadVisitor::visit(KisColorizeMask *mask) { m_store->pushDirectory(); QString location = getLocation(mask, DOT_COLORIZE_MASK); m_store->enterDirectory(location) ; QByteArray data; if (!m_store->extractFile("content.xml", data)) return false; QDomDocument doc; if (!doc.setContent(data)) return false; QVector strokes; if (!KisDomUtils::loadValue(doc.documentElement(), COLORIZE_KEYSTROKES_SECTION, &strokes, mask->colorSpace())) return false; int i = 0; Q_FOREACH (const KisLazyFillTools::KeyStroke &stroke, strokes) { const QString fileName = QString("%1_%2").arg(COLORIZE_KEYSTROKE).arg(i++); loadPaintDevice(stroke.dev, fileName); } mask->setKeyStrokesDirect(QList::fromVector(strokes)); loadPaintDevice(mask->coloringProjection(), COLORIZE_COLORING_DEVICE); mask->resetCache(); m_store->popDirectory(); return true; } QStringList KisKraLoadVisitor::errorMessages() const { return m_errorMessages; } QStringList KisKraLoadVisitor::warningMessages() const { return m_warningMessages; } struct SimpleDevicePolicy { bool read(KisPaintDeviceSP dev, QIODevice *stream) { return dev->read(stream); } void setDefaultPixel(KisPaintDeviceSP dev, const KoColor &defaultPixel) const { return dev->setDefaultPixel(defaultPixel); } }; struct FramedDevicePolicy { FramedDevicePolicy(int frameId) : m_frameId(frameId) {} bool read(KisPaintDeviceSP dev, QIODevice *stream) { return dev->framesInterface()->readFrame(stream, m_frameId); } void setDefaultPixel(KisPaintDeviceSP dev, const KoColor &defaultPixel) const { return dev->framesInterface()->setFrameDefaultPixel(defaultPixel, m_frameId); } int m_frameId; }; bool KisKraLoadVisitor::loadPaintDevice(KisPaintDeviceSP device, const QString& location) { // Layer data KisPaintDeviceFramesInterface *frameInterface = device->framesInterface(); QList frames; if (frameInterface) { frames = device->framesInterface()->frames(); } if (!frameInterface || frames.count() <= 1) { return loadPaintDeviceFrame(device, location, SimpleDevicePolicy()); } else { KisRasterKeyframeChannel *keyframeChannel = device->keyframeChannel(); for (int i = 0; i < frames.count(); i++) { int id = frames[i]; if (keyframeChannel->frameFilename(id).isEmpty()) { m_warningMessages << i18n("Could not find keyframe pixel data for frame %1 in %2.", id, location); } else { Q_ASSERT(!keyframeChannel->frameFilename(id).isEmpty()); QString frameFilename = getLocation(keyframeChannel->frameFilename(id)); Q_ASSERT(!frameFilename.isEmpty()); if (!loadPaintDeviceFrame(device, frameFilename, FramedDevicePolicy(id))) { m_warningMessages << i18n("Could not load keyframe pixel data for frame %1 in %2.", id, location); } } } } return true; } template bool KisKraLoadVisitor::loadPaintDeviceFrame(KisPaintDeviceSP device, const QString &location, DevicePolicy policy) { { const int pixelSize = device->colorSpace()->pixelSize(); KoColor color(Qt::transparent, device->colorSpace()); if (m_store->open(location + ".defaultpixel")) { if (m_store->size() == pixelSize) { m_store->read((char*)color.data(), pixelSize); } m_store->close(); } policy.setDefaultPixel(device, color); } if (m_store->open(location)) { if (!policy.read(device, m_store->device())) { m_warningMessages << i18n("Could not read pixel data: %1.", location); device->disconnect(); m_store->close(); return true; } m_store->close(); } else { m_warningMessages << i18n("Could not load pixel data: %1.", location); return true; } return true; } bool KisKraLoadVisitor::loadProfile(KisPaintDeviceSP device, const QString& location) { if (m_store->hasFile(location)) { m_store->open(location); QByteArray data; data.resize(m_store->size()); dbgFile << "Data to load: " << m_store->size() << " from " << location << " with color space " << device->colorSpace()->id(); int read = m_store->read(data.data(), m_store->size()); dbgFile << "Profile size: " << data.size() << " " << m_store->atEnd() << " " << m_store->device()->bytesAvailable() << " " << read; m_store->close(); KoHashGenerator *hashGenerator = KoHashGeneratorProvider::instance()->getGenerator("MD5"); QByteArray hash = hashGenerator->generateHash(data); if (m_profileCache.contains(hash)) { if (device->setProfile(m_profileCache[hash])) { return true; } } else { // Create a colorspace with the embedded profile const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(device->colorSpace()->colorModelId().id(), device->colorSpace()->colorDepthId().id(), data); m_profileCache[hash] = profile; if (device->setProfile(profile)) { return true; } } } m_warningMessages << i18n("Could not load profile: %1.", location); return true; } -bool KisKraLoadVisitor::loadFilterConfiguration(KisFilterConfigurationSP kfc, const QString& location) +bool KisKraLoadVisitor::loadFilterConfiguration(KisNodeFilterInterface *nodeInterface, const QString& location) { + KisFilterConfigurationSP kfc = nodeInterface->filter(); + if (m_store->hasFile(location)) { QByteArray data; m_store->open(location); data = m_store->read(m_store->size()); m_store->close(); if (!data.isEmpty()) { QDomDocument doc; doc.setContent(data); QDomElement e = doc.documentElement(); if (e.tagName() == "filterconfig") { kfc->fromLegacyXML(e); } else { kfc->fromXML(e); } loadDeprecatedFilter(kfc); return true; } } m_warningMessages << i18n("Could not filter configuration %1.", location); return true; } void KisKraLoadVisitor::fixOldFilterConfigurations(KisFilterConfigurationSP kfc) { KisFilterSP filter = KisFilterRegistry::instance()->value(kfc->name()); KIS_SAFE_ASSERT_RECOVER_RETURN(filter); if (!filter->configurationAllowedForMask(kfc)) { filter->fixLoadedFilterConfigurationForMasks(kfc); } KIS_SAFE_ASSERT_RECOVER_NOOP(filter->configurationAllowedForMask(kfc)); } bool KisKraLoadVisitor::loadMetaData(KisNode* node) { KisLayer* layer = qobject_cast(node); if (!layer) return true; KisMetaData::IOBackend* backend = KisMetaData::IOBackendRegistry::instance()->get("xmp"); if (!backend || !backend->supportLoading()) { if (backend) dbgFile << "Backend " << backend->id() << " does not support loading."; else dbgFile << "Could not load the XMP backend at all"; return true; } QString location = getLocation(node, QString(".") + backend->id() + DOT_METADATA); dbgFile << "going to load " << backend->id() << ", " << backend->name() << " from " << location; if (m_store->hasFile(location)) { QByteArray data; m_store->open(location); data = m_store->read(m_store->size()); m_store->close(); QBuffer buffer(&data); if (!backend->loadFrom(layer->metaData(), &buffer)) { m_warningMessages << i18n("Could not load metadata for layer %1.", layer->name()); } } return true; } bool KisKraLoadVisitor::loadSelection(const QString& location, KisSelectionSP dstSelection) { // by default the selection is expected to be fully transparent { KisPixelSelectionSP pixelSelection = dstSelection->pixelSelection(); KoColor transparent(Qt::transparent, pixelSelection->colorSpace()); pixelSelection->setDefaultPixel(transparent); } // Pixel selection bool result = true; QString pixelSelectionLocation = location + DOT_PIXEL_SELECTION; if (m_store->hasFile(pixelSelectionLocation)) { KisPixelSelectionSP pixelSelection = dstSelection->pixelSelection(); result = loadPaintDevice(pixelSelection, pixelSelectionLocation); if (!result) { m_warningMessages << i18n("Could not load raster selection %1.", location); } pixelSelection->invalidateOutlineCache(); } // Shape selection QString shapeSelectionLocation = location + DOT_SHAPE_SELECTION; if (m_store->hasFile(shapeSelectionLocation + "/content.svg") || m_store->hasFile(shapeSelectionLocation + "/content.xml")) { m_store->pushDirectory(); m_store->enterDirectory(shapeSelectionLocation) ; KisShapeSelection* shapeSelection = new KisShapeSelection(m_shapeController, m_image, dstSelection); dstSelection->setShapeSelection(shapeSelection); result = shapeSelection->loadSelection(m_store); m_store->popDirectory(); if (!result) { m_warningMessages << i18n("Could not load vector selection %1.", location); } } return true; } QString KisKraLoadVisitor::getLocation(KisNode* node, const QString& suffix) { return getLocation(m_layerFilenames[node], suffix); } QString KisKraLoadVisitor::getLocation(const QString &filename, const QString& suffix) { QString location = m_external ? QString() : m_uri; location += m_name + LAYER_PATH + filename + suffix; return location; } void KisKraLoadVisitor::loadNodeKeyframes(KisNode *node) { if (!m_keyframeFilenames.contains(node)) return; node->enableAnimation(); const QString &location = getLocation(m_keyframeFilenames[node]); if (!m_store->open(location)) { m_errorMessages << i18n("Could not load keyframes from %1.", location); return; } QString errorMsg; int errorLine; int errorColumn; QDomDocument dom; bool ok = dom.setContent(m_store->device(), &errorMsg, &errorLine, &errorColumn); m_store->close(); if (!ok) { m_errorMessages << i18n("parsing error in the keyframe file %1 at line %2, column %3\nError message: %4", location, errorLine, errorColumn, i18n(errorMsg.toUtf8())); return; } QDomElement root = dom.firstChildElement(); for (QDomElement child = root.firstChildElement(); !child.isNull(); child = child.nextSiblingElement()) { if (child.nodeName().toUpper() == "CHANNEL") { QString id = child.attribute("name"); KisKeyframeChannel *channel = node->getKeyframeChannel(id, true); if (!channel) { m_warningMessages << i18n("unknown keyframe channel type: %1 in %2", id, location); continue; } channel->loadXML(child); } } } void KisKraLoadVisitor::loadDeprecatedFilter(KisFilterConfigurationSP cfg) { if (cfg->getString("legacy") == "left edge detections") { cfg->setProperty("horizRadius", 1); cfg->setProperty("vertRadius", 1); cfg->setProperty("type", "prewitt"); cfg->setProperty("output", "yFall"); cfg->setProperty("lockAspect", true); cfg->setProperty("transparency", false); } else if (cfg->getString("legacy") == "right edge detections") { cfg->setProperty("horizRadius", 1); cfg->setProperty("vertRadius", 1); cfg->setProperty("type", "prewitt"); cfg->setProperty("output", "yGrowth"); cfg->setProperty("lockAspect", true); cfg->setProperty("transparency", false); } else if (cfg->getString("legacy") == "top edge detections") { cfg->setProperty("horizRadius", 1); cfg->setProperty("vertRadius", 1); cfg->setProperty("type", "prewitt"); cfg->setProperty("output", "xGrowth"); cfg->setProperty("lockAspect", true); cfg->setProperty("transparency", false); } else if (cfg->getString("legacy") == "bottom edge detections") { cfg->setProperty("horizRadius", 1); cfg->setProperty("vertRadius", 1); cfg->setProperty("type", "prewitt"); cfg->setProperty("output", "xFall"); cfg->setProperty("lockAspect", true); cfg->setProperty("transparency", false); } } diff --git a/plugins/impex/libkra/kis_kra_load_visitor.h b/plugins/impex/libkra/kis_kra_load_visitor.h index 0739bb7e62..d0d675caca 100644 --- a/plugins/impex/libkra/kis_kra_load_visitor.h +++ b/plugins/impex/libkra/kis_kra_load_visitor.h @@ -1,112 +1,113 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2005 C. Boemann * Copyright (c) 2007 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_KRA_LOAD_VISITOR_H_ #define KIS_KRA_LOAD_VISITOR_H_ #include #include // kritaimage #include "kis_types.h" #include "kis_node_visitor.h" #include "kritalibkra_export.h" class KisFilterConfiguration; class KoStore; class KoShapeControllerBase; class KoColorProfile; +class KisNodeFilterInterface; class KRITALIBKRA_EXPORT KisKraLoadVisitor : public KisNodeVisitor { public: KisKraLoadVisitor(KisImageSP image, KoStore *store, KoShapeControllerBase *shapeController, QMap &layerFilenames, QMap &keyframeFilenames, const QString & name, int syntaxVersion); public: void setExternalUri(const QString &uri); bool visit(KisNode*) override { return true; } bool visit(KisExternalLayer *) override; bool visit(KisPaintLayer *layer) override; bool visit(KisGroupLayer *layer) override; bool visit(KisAdjustmentLayer* layer) override; bool visit(KisGeneratorLayer* layer) override; bool visit(KisCloneLayer *layer) override; bool visit(KisFilterMask *mask) override; bool visit(KisTransformMask *mask) override; bool visit(KisTransparencyMask *mask) override; bool visit(KisSelectionMask *mask) override; bool visit(KisColorizeMask *mask) override; QStringList errorMessages() const; QStringList warningMessages() const; private: bool loadPaintDevice(KisPaintDeviceSP device, const QString& location); template bool loadPaintDeviceFrame(KisPaintDeviceSP device, const QString &location, DevicePolicy policy); bool loadProfile(KisPaintDeviceSP device, const QString& location); - bool loadFilterConfiguration(KisFilterConfigurationSP kfc, const QString& location); + bool loadFilterConfiguration(KisNodeFilterInterface *nodeInterface, const QString& location); void fixOldFilterConfigurations(KisFilterConfigurationSP kfc); bool loadMetaData(KisNode* node); void initSelectionForMask(KisMask *mask); bool loadSelection(const QString& location, KisSelectionSP dstSelection); QString getLocation(KisNode* node, const QString& suffix = QString()); QString getLocation(const QString &filename, const QString &suffix = QString()); void loadNodeKeyframes(KisNode *node); /** * Load deprecated filters. * Most deprecated filters can be handled by this, but the brightnesscontact to perchannels * conversion needs to be handled in the perchannel class because those filters * have their own xml loading functionality. */ void loadDeprecatedFilter(KisFilterConfigurationSP cfg); private: KisImageSP m_image; KoStore *m_store; bool m_external; QString m_uri; QMap m_layerFilenames; QMap m_keyframeFilenames; QString m_name; int m_syntaxVersion; QStringList m_errorMessages; QStringList m_warningMessages; KoShapeControllerBase *m_shapeController; QMap m_profileCache; }; #endif // KIS_KRA_LOAD_VISITOR_H_ diff --git a/plugins/impex/libkra/kis_kra_loader.cpp b/plugins/impex/libkra/kis_kra_loader.cpp index 17562dfa73..46237c8506 100644 --- a/plugins/impex/libkra/kis_kra_loader.cpp +++ b/plugins/impex/libkra/kis_kra_loader.cpp @@ -1,1265 +1,1265 @@ /* This file is part of the KDE project * Copyright (C) Boudewijn Rempt , (C) 2007 * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_kra_loader.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "lazybrush/kis_colorize_mask.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KisResourceServerProvider.h" #include "kis_keyframe_channel.h" #include #include "KisReferenceImagesLayer.h" #include "KisReferenceImage.h" #include #include "KisDocument.h" #include "kis_config.h" #include "kis_kra_tags.h" #include "kis_kra_utils.h" #include "kis_kra_load_visitor.h" #include "kis_dom_utils.h" #include "kis_image_animation_interface.h" #include "kis_time_range.h" #include "kis_grid_config.h" #include "kis_guides_config.h" #include "kis_image_config.h" #include "KisProofingConfiguration.h" #include "kis_layer_properties_icons.h" #include "kis_node_view_color_scheme.h" #include "KisMirrorAxisConfig.h" /* Color model id comparison through the ages: 2.4 2.5 2.6 ideal ALPHA ALPHA ALPHA ALPHAU8 CMYK CMYK CMYK CMYKAU8 CMYKAF32 CMYKAF32 CMYKA16 CMYKAU16 CMYKAU16 GRAYA GRAYA GRAYA GRAYAU8 GrayF32 GRAYAF32 GRAYAF32 GRAYA16 GRAYAU16 GRAYAU16 LABA LABA LABA LABAU16 LABAF32 LABAF32 LABAU8 LABAU8 RGBA RGBA RGBA RGBAU8 RGBA16 RGBA16 RGBA16 RGBAU16 RgbAF32 RGBAF32 RGBAF32 RgbAF16 RgbAF16 RGBAF16 XYZA16 XYZA16 XYZA16 XYZAU16 XYZA8 XYZA8 XYZAU8 XyzAF16 XyzAF16 XYZAF16 XyzAF32 XYZAF32 XYZAF32 YCbCrA YCBCRA8 YCBCRA8 YCBCRAU8 YCbCrAU16 YCBCRAU16 YCBCRAU16 YCBCRF32 YCBCRF32 */ using namespace KRA; struct KisKraLoader::Private { public: KisDocument* document; QString imageName; // used to be stored in the image, is now in the documentInfo block QString imageComment; // used to be stored in the image, is now in the documentInfo block QMap layerFilenames; // temp storage during loading int syntaxVersion; // version of the fileformat we are loading vKisNodeSP selectedNodes; // the nodes that were active when saving the document. QMap assistantsFilenames; QList assistants; QMap keyframeFilenames; QVector paletteFilenames; QStringList errorMessages; QStringList warningMessages; }; void convertColorSpaceNames(QString &colorspacename, QString &profileProductName) { if (colorspacename == "Grayscale + Alpha") { colorspacename = "GRAYA"; profileProductName.clear(); } else if (colorspacename == "RgbAF32") { colorspacename = "RGBAF32"; profileProductName.clear(); } else if (colorspacename == "RgbAF16") { colorspacename = "RGBAF16"; profileProductName.clear(); } else if (colorspacename == "CMYKA16") { colorspacename = "CMYKAU16"; } else if (colorspacename == "GrayF32") { colorspacename = "GRAYAF32"; profileProductName.clear(); } else if (colorspacename == "GRAYA16") { colorspacename = "GRAYAU16"; } else if (colorspacename == "XyzAF16") { colorspacename = "XYZAF16"; profileProductName.clear(); } else if (colorspacename == "XyzAF32") { colorspacename = "XYZAF32"; profileProductName.clear(); } else if (colorspacename == "YCbCrA") { colorspacename = "YCBCRA8"; } else if (colorspacename == "YCbCrAU16") { colorspacename = "YCBCRAU16"; } } KisKraLoader::KisKraLoader(KisDocument * document, int syntaxVersion) : m_d(new Private()) { m_d->document = document; m_d->syntaxVersion = syntaxVersion; } KisKraLoader::~KisKraLoader() { delete m_d; } KisImageSP KisKraLoader::loadXML(const KoXmlElement& element) { QString attr; KisImageSP image = 0; qint32 width; qint32 height; QString profileProductName; double xres; double yres; QString colorspacename; const KoColorSpace * cs; if ((attr = element.attribute(MIME)) == NATIVE_MIMETYPE) { if ((m_d->imageName = element.attribute(NAME)).isNull()) { m_d->errorMessages << i18n("Image does not have a name."); return KisImageSP(0); } if ((attr = element.attribute(WIDTH)).isNull()) { m_d->errorMessages << i18n("Image does not specify a width."); return KisImageSP(0); } width = KisDomUtils::toInt(attr); if ((attr = element.attribute(HEIGHT)).isNull()) { m_d->errorMessages << i18n("Image does not specify a height."); return KisImageSP(0); } height = KisDomUtils::toInt(attr); m_d->imageComment = element.attribute(DESCRIPTION); xres = 100.0 / 72.0; if (!(attr = element.attribute(X_RESOLUTION)).isNull()) { qreal value = KisDomUtils::toDouble(attr); if (value > 1.0) { xres = value / 72.0; } } yres = 100.0 / 72.0; if (!(attr = element.attribute(Y_RESOLUTION)).isNull()) { qreal value = KisDomUtils::toDouble(attr); if (value > 1.0) { yres = value / 72.0; } } if ((colorspacename = element.attribute(COLORSPACE_NAME)).isNull()) { // An old file: take a reasonable default. // Krita didn't support anything else in those // days anyway. colorspacename = "RGBA"; } profileProductName = element.attribute(PROFILE); // A hack for an old colorspacename convertColorSpaceNames(colorspacename, profileProductName); QString colorspaceModel = KoColorSpaceRegistry::instance()->colorSpaceColorModelId(colorspacename).id(); QString colorspaceDepth = KoColorSpaceRegistry::instance()->colorSpaceColorDepthId(colorspacename).id(); if (profileProductName.isNull()) { // no mention of profile so get default profile"; cs = KoColorSpaceRegistry::instance()->colorSpace(colorspaceModel, colorspaceDepth, ""); } else { cs = KoColorSpaceRegistry::instance()->colorSpace(colorspaceModel, colorspaceDepth, profileProductName); } if (cs == 0) { // try once more without the profile cs = KoColorSpaceRegistry::instance()->colorSpace(colorspaceModel, colorspaceDepth, ""); if (cs == 0) { m_d->errorMessages << i18n("Image specifies an unsupported color model: %1.", colorspacename); return KisImageSP(0); } } KisProofingConfigurationSP proofingConfig = KisImageConfig(true).defaultProofingconfiguration(); if (!(attr = element.attribute(PROOFINGPROFILENAME)).isNull()) { proofingConfig->proofingProfile = attr; } if (!(attr = element.attribute(PROOFINGMODEL)).isNull()) { proofingConfig->proofingModel = attr; } if (!(attr = element.attribute(PROOFINGDEPTH)).isNull()) { proofingConfig->proofingDepth = attr; } if (!(attr = element.attribute(PROOFINGINTENT)).isNull()) { proofingConfig->intent = (KoColorConversionTransformation::Intent) KisDomUtils::toInt(attr); } if (!(attr = element.attribute(PROOFINGADAPTATIONSTATE)).isNull()) { proofingConfig->adaptationState = KisDomUtils::toDouble(attr); } if (m_d->document) { image = new KisImage(m_d->document->createUndoStore(), width, height, cs, m_d->imageName); } else { image = new KisImage(0, width, height, cs, m_d->imageName); } image->setResolution(xres, yres); loadNodes(element, image, const_cast(image->rootLayer().data())); KoXmlNode child; for (child = element.lastChild(); !child.isNull(); child = child.previousSibling()) { KoXmlElement e = child.toElement(); if(e.tagName() == CANVASPROJECTIONCOLOR) { if (e.hasAttribute(COLORBYTEDATA)) { QByteArray colorData = QByteArray::fromBase64(e.attribute(COLORBYTEDATA).toLatin1()); KoColor color((const quint8*)colorData.data(), image->colorSpace()); image->setDefaultProjectionColor(color); } } if(e.tagName() == GLOBALASSISTANTSCOLOR) { if (e.hasAttribute(SIMPLECOLORDATA)) { QString colorData = e.attribute(SIMPLECOLORDATA); m_d->document->setAssistantsGlobalColor(KisDomUtils::qStringToQColor(colorData)); } } if(e.tagName()== PROOFINGWARNINGCOLOR) { QDomDocument dom; KoXml::asQDomElement(dom, e); QDomElement eq = dom.firstChildElement(); proofingConfig->warningColor = KoColor::fromXML(eq.firstChildElement(), Integer8BitsColorDepthID.id()); } if (e.tagName().toLower() == "animation") { loadAnimationMetadata(e, image); } } image->setProofingConfiguration(proofingConfig); for (child = element.lastChild(); !child.isNull(); child = child.previousSibling()) { KoXmlElement e = child.toElement(); if(e.tagName() == "compositions") { loadCompositions(e, image); } } } KoXmlNode child; for (child = element.lastChild(); !child.isNull(); child = child.previousSibling()) { KoXmlElement e = child.toElement(); if (e.tagName() == "grid") { loadGrid(e); } else if (e.tagName() == "guides") { loadGuides(e); } else if (e.tagName() == MIRROR_AXIS) { loadMirrorAxis(e); } else if (e.tagName() == "assistants") { loadAssistantsList(e); } else if (e.tagName() == "audio") { loadAudio(e, image); } } // reading palettes from XML for (child = element.lastChild(); !child.isNull(); child = child.previousSibling()) { QDomElement e = child.toElement(); if (e.tagName() == PALETTES) { for (QDomElement paletteElement = e.lastChildElement(); !paletteElement.isNull(); paletteElement = paletteElement.previousSiblingElement()) { QString paletteName = paletteElement.attribute("filename"); m_d->paletteFilenames.append(paletteName); } break; } } return image; } void KisKraLoader::loadBinaryData(KoStore * store, KisImageSP image, const QString & uri, bool external) { // icc profile: if present, this overrides the profile product name loaded in loadXML. QString location = external ? QString() : uri; location += m_d->imageName + ICC_PATH; if (store->hasFile(location)) { if (store->open(location)) { QByteArray data; data.resize(store->size()); bool res = (store->read(data.data(), store->size()) > -1); store->close(); if (res) { const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(image->colorSpace()->colorModelId().id(), image->colorSpace()->colorDepthId().id(), data); if (profile && profile->valid()) { res = image->assignImageProfile(profile); } if (!res) { const QString defaultProfileId = KoColorSpaceRegistry::instance()->defaultProfileForColorSpace(image->colorSpace()->id()); profile = KoColorSpaceRegistry::instance()->profileByName(defaultProfileId); Q_ASSERT(profile && profile->valid()); image->assignImageProfile(profile); } } } } //load the embed proofing profile, it only needs to be loaded into Krita, not assigned. location = external ? QString() : uri; location += m_d->imageName + ICC_PROOFING_PATH; if (store->hasFile(location)) { if (store->open(location)) { QByteArray proofingData; proofingData.resize(store->size()); bool proofingProfileRes = (store->read(proofingData.data(), store->size())>-1); store->close(); KisProofingConfigurationSP proofingConfig = image->proofingConfiguration(); if (!proofingConfig) { proofingConfig = KisImageConfig(true).defaultProofingconfiguration(); } if (proofingProfileRes) { const KoColorProfile *proofingProfile = KoColorSpaceRegistry::instance()->createColorProfile(proofingConfig->proofingModel, proofingConfig->proofingDepth, proofingData); if (proofingProfile->valid()){ KoColorSpaceRegistry::instance()->addProfile(proofingProfile); } } } } // Load the layers data: if there is a profile associated with a layer it will be set now. KisKraLoadVisitor visitor(image, store, m_d->document->shapeController(), m_d->layerFilenames, m_d->keyframeFilenames, m_d->imageName, m_d->syntaxVersion); if (external) { visitor.setExternalUri(uri); } image->rootLayer()->accept(visitor); if (!visitor.errorMessages().isEmpty()) { m_d->errorMessages.append(visitor.errorMessages()); } if (!visitor.warningMessages().isEmpty()) { m_d->warningMessages.append(visitor.warningMessages()); } // annotations // exif location = external ? QString() : uri; location += m_d->imageName + EXIF_PATH; if (store->hasFile(location)) { QByteArray data; store->open(location); data = store->read(store->size()); store->close(); image->addAnnotation(KisAnnotationSP(new KisAnnotation("exif", "", data))); } // layer styles location = external ? QString() : uri; location += m_d->imageName + LAYER_STYLES_PATH; if (store->hasFile(location)) { KisPSDLayerStyleCollectionResource *collection = new KisPSDLayerStyleCollectionResource("Embedded Styles.asl"); collection->setName(i18nc("Auto-generated layer style collection name for embedded styles (collection)", "<%1> (embedded)", m_d->imageName)); KIS_ASSERT_RECOVER_NOOP(!collection->valid()); store->open(location); { KoStoreDevice device(store); device.open(QIODevice::ReadOnly); /** * ASL loading code cannot work with non-sequential IO devices, * so convert the device beforehand! */ QByteArray buf = device.readAll(); QBuffer raDevice(&buf); raDevice.open(QIODevice::ReadOnly); collection->loadFromDevice(&raDevice); } store->close(); if (collection->valid()) { KoResourceServer *server = KisResourceServerProvider::instance()->layerStyleCollectionServer(); server->addResource(collection, false); collection->assignAllLayerStyles(image->root()); } else { warnKrita << "WARNING: Couldn't load layer styles library from .kra!"; delete collection; } } if (m_d->document && m_d->document->documentInfo()->aboutInfo("title").isNull()) m_d->document->documentInfo()->setAboutInfo("title", m_d->imageName); if (m_d->document && m_d->document->documentInfo()->aboutInfo("comment").isNull()) m_d->document->documentInfo()->setAboutInfo("comment", m_d->imageComment); loadAssistants(store, uri, external); } void KisKraLoader::loadPalettes(KoStore *store, KisDocument *doc) { QList list; Q_FOREACH (const QString &filename, m_d->paletteFilenames) { KoColorSet *newPalette = new KoColorSet(filename); store->open(m_d->imageName + PALETTE_PATH + filename); QByteArray data = store->read(store->size()); newPalette->fromByteArray(data); newPalette->setIsGlobal(false); newPalette->setIsEditable(true); store->close(); list.append(newPalette); } doc->setPaletteList(list); } vKisNodeSP KisKraLoader::selectedNodes() const { return m_d->selectedNodes; } QList KisKraLoader::assistants() const { return m_d->assistants; } QStringList KisKraLoader::errorMessages() const { return m_d->errorMessages; } QStringList KisKraLoader::warningMessages() const { return m_d->warningMessages; } QString KisKraLoader::imageName() const { return m_d->imageName; } void KisKraLoader::loadAssistants(KoStore *store, const QString &uri, bool external) { QString file_path; QString location; QMap handleMap; KisPaintingAssistant* assistant = 0; const QColor globalColor = m_d->document->assistantsGlobalColor(); QMap::const_iterator loadedAssistant = m_d->assistantsFilenames.constBegin(); while (loadedAssistant != m_d->assistantsFilenames.constEnd()){ const KisPaintingAssistantFactory* factory = KisPaintingAssistantFactoryRegistry::instance()->get(loadedAssistant.value()); if (factory) { assistant = factory->createPaintingAssistant(); location = external ? QString() : uri; location += m_d->imageName + ASSISTANTS_PATH; file_path = location + loadedAssistant.key(); assistant->loadXml(store, handleMap, file_path); assistant->setAssistantGlobalColorCache(globalColor); //If an assistant has too few handles than it should according to it's own setup, just don't load it// if (assistant->handles().size()==assistant->numHandles()){ m_d->assistants.append(toQShared(assistant)); } } loadedAssistant++; } } void KisKraLoader::loadAnimationMetadata(const KoXmlElement &element, KisImageSP image) { QDomDocument qDom; KoXml::asQDomElement(qDom, element); QDomElement qElement = qDom.firstChildElement(); float framerate; KisTimeRange range; int currentTime; KisImageAnimationInterface *animation = image->animationInterface(); if (KisDomUtils::loadValue(qElement, "framerate", &framerate)) { animation->setFramerate(framerate); } if (KisDomUtils::loadValue(qElement, "range", &range)) { animation->setFullClipRange(range); } if (KisDomUtils::loadValue(qElement, "currentTime", ¤tTime)) { animation->switchCurrentTimeAsync(currentTime); } } KisNodeSP KisKraLoader::loadNodes(const KoXmlElement& element, KisImageSP image, KisNodeSP parent) { KoXmlNode node = element.firstChild(); KoXmlNode child; if (!node.isNull()) { if (node.isElement()) { if (node.nodeName().toUpper() == LAYERS.toUpper() || node.nodeName().toUpper() == MASKS.toUpper()) { for (child = node.lastChild(); !child.isNull(); child = child.previousSibling()) { KisNodeSP node = loadNode(child.toElement(), image); if (node) { image->nextLayerName(); // Make sure the nameserver is current with the number of nodes. image->addNode(node, parent); if (node->inherits("KisLayer") && KoXml::childNodesCount(child) > 0) { loadNodes(child.toElement(), image, node); } } } } } } return parent; } KisNodeSP KisKraLoader::loadNode(const KoXmlElement& element, KisImageSP image) { // Nota bene: If you add new properties to layers, you should // ALWAYS define a default value in case the property is not // present in the layer definition: this helps a LOT with backward // compatibility. QString name = element.attribute(NAME, "No Name"); QUuid id = QUuid(element.attribute(UUID, QUuid().toString())); qint32 x = element.attribute(X, "0").toInt(); qint32 y = element.attribute(Y, "0").toInt(); qint32 opacity = element.attribute(OPACITY, QString::number(OPACITY_OPAQUE_U8)).toInt(); if (opacity < OPACITY_TRANSPARENT_U8) opacity = OPACITY_TRANSPARENT_U8; if (opacity > OPACITY_OPAQUE_U8) opacity = OPACITY_OPAQUE_U8; const KoColorSpace* colorSpace = 0; if ((element.attribute(COLORSPACE_NAME)).isNull()) { dbgFile << "No attribute color space for layer: " << name; colorSpace = image->colorSpace(); } else { QString colorspacename = element.attribute(COLORSPACE_NAME); QString profileProductName; convertColorSpaceNames(colorspacename, profileProductName); QString colorspaceModel = KoColorSpaceRegistry::instance()->colorSpaceColorModelId(colorspacename).id(); QString colorspaceDepth = KoColorSpaceRegistry::instance()->colorSpaceColorDepthId(colorspacename).id(); dbgFile << "Searching color space: " << colorspacename << colorspaceModel << colorspaceDepth << " for layer: " << name; // use default profile - it will be replaced later in completeLoading colorSpace = KoColorSpaceRegistry::instance()->colorSpace(colorspaceModel, colorspaceDepth, ""); dbgFile << "found colorspace" << colorSpace; if (!colorSpace) { m_d->warningMessages << i18n("Layer %1 specifies an unsupported color model: %2.", name, colorspacename); return 0; } } const bool visible = element.attribute(VISIBLE, "1") == "0" ? false : true; const bool locked = element.attribute(LOCKED, "0") == "0" ? false : true; const bool collapsed = element.attribute(COLLAPSED, "0") == "0" ? false : true; int colorLabelIndex = element.attribute(COLOR_LABEL, "0").toInt(); QVector labels = KisNodeViewColorScheme::instance()->allColorLabels(); if (colorLabelIndex >= labels.size()) { colorLabelIndex = labels.size() - 1; } // Now find out the layer type and do specific handling QString nodeType; if (m_d->syntaxVersion == 1) { nodeType = element.attribute("layertype"); if (nodeType.isEmpty()) { nodeType = PAINT_LAYER; } } else { nodeType = element.attribute(NODE_TYPE); } if (nodeType.isEmpty()) { m_d->warningMessages << i18n("Layer %1 has an unsupported type.", name); return 0; } KisNodeSP node = 0; if (nodeType == PAINT_LAYER) node = loadPaintLayer(element, image, name, colorSpace, opacity); else if (nodeType == GROUP_LAYER) node = loadGroupLayer(element, image, name, colorSpace, opacity); else if (nodeType == ADJUSTMENT_LAYER) node = loadAdjustmentLayer(element, image, name, colorSpace, opacity); else if (nodeType == SHAPE_LAYER) node = loadShapeLayer(element, image, name, colorSpace, opacity); else if (nodeType == GENERATOR_LAYER) node = loadGeneratorLayer(element, image, name, colorSpace, opacity); else if (nodeType == CLONE_LAYER) node = loadCloneLayer(element, image, name, colorSpace, opacity); else if (nodeType == FILTER_MASK) node = loadFilterMask(element); else if (nodeType == TRANSFORM_MASK) node = loadTransformMask(element); else if (nodeType == TRANSPARENCY_MASK) node = loadTransparencyMask(element); else if (nodeType == SELECTION_MASK) node = loadSelectionMask(image, element); else if (nodeType == COLORIZE_MASK) node = loadColorizeMask(image, element, colorSpace); else if (nodeType == FILE_LAYER) node = loadFileLayer(element, image, name, opacity); else if (nodeType == REFERENCE_IMAGES_LAYER) node = loadReferenceImagesLayer(element, image); else { m_d->warningMessages << i18n("Layer %1 has an unsupported type: %2.", name, nodeType); return 0; } // Loading the node went wrong. Return empty node and leave to // upstream to complain to the user if (!node) { m_d->warningMessages << i18n("Failure loading layer %1 of type: %2.", name, nodeType); return 0; } node->setVisible(visible, true); node->setUserLocked(locked); node->setCollapsed(collapsed); node->setColorLabelIndex(colorLabelIndex); node->setX(x); node->setY(y); node->setName(name); if (! id.isNull()) // if no uuid in file, new one has been generated already node->setUuid(id); if (node->inherits("KisLayer") || node->inherits("KisColorizeMask")) { QString compositeOpName = element.attribute(COMPOSITE_OP, "normal"); node->setCompositeOpId(compositeOpName); } if (node->inherits("KisLayer")) { KisLayer* layer = qobject_cast(node.data()); QBitArray channelFlags = stringToFlags(element.attribute(CHANNEL_FLAGS, ""), colorSpace->channelCount()); layer->setChannelFlags(channelFlags); if (element.hasAttribute(LAYER_STYLE_UUID)) { QString uuidString = element.attribute(LAYER_STYLE_UUID); QUuid uuid(uuidString); if (!uuid.isNull()) { KisPSDLayerStyleSP dumbLayerStyle(new KisPSDLayerStyle()); dumbLayerStyle->setUuid(uuid); layer->setLayerStyle(dumbLayerStyle); } else { warnKrita << "WARNING: Layer style for layer" << layer->name() << "contains invalid UUID" << uuidString; } } } if (node->inherits("KisGroupLayer")) { if (element.hasAttribute(PASS_THROUGH_MODE)) { bool value = element.attribute(PASS_THROUGH_MODE, "0") != "0"; KisGroupLayer *group = qobject_cast(node.data()); group->setPassThroughMode(value); } } const bool timelineEnabled = element.attribute(VISIBLE_IN_TIMELINE, "0") == "0" ? false : true; node->setUseInTimeline(timelineEnabled); if (node->inherits("KisPaintLayer")) { KisPaintLayer* layer = qobject_cast(node.data()); QBitArray channelLockFlags = stringToFlags(element.attribute(CHANNEL_LOCK_FLAGS, ""), colorSpace->channelCount()); layer->setChannelLockFlags(channelLockFlags); bool onionEnabled = element.attribute(ONION_SKIN_ENABLED, "0") == "0" ? false : true; layer->setOnionSkinEnabled(onionEnabled); } if (element.attribute(FILE_NAME).isNull()) { m_d->layerFilenames[node.data()] = name; } else { m_d->layerFilenames[node.data()] = element.attribute(FILE_NAME); } if (element.hasAttribute("selected") && element.attribute("selected") == "true") { m_d->selectedNodes.append(node); } if (element.hasAttribute(KEYFRAME_FILE)) { m_d->keyframeFilenames.insert(node.data(), element.attribute(KEYFRAME_FILE)); } return node; } KisNodeSP KisKraLoader::loadPaintLayer(const KoXmlElement& element, KisImageSP image, const QString& name, const KoColorSpace* cs, quint32 opacity) { Q_UNUSED(element); KisPaintLayer* layer; layer = new KisPaintLayer(image, name, opacity, cs); Q_CHECK_PTR(layer); return layer; } KisNodeSP KisKraLoader::loadFileLayer(const KoXmlElement& element, KisImageSP image, const QString& name, quint32 opacity) { QString filename = element.attribute("source", QString()); if (filename.isNull()) return 0; bool scale = (element.attribute("scale", "true") == "true"); int scalingMethod = element.attribute("scalingmethod", "-1").toInt(); if (scalingMethod < 0) { if (scale) { scalingMethod = KisFileLayer::ToImagePPI; } else { scalingMethod = KisFileLayer::None; } } QString documentPath; if (m_d->document) { documentPath = m_d->document->url().toLocalFile(); } QFileInfo info(documentPath); QString basePath = info.absolutePath(); QString fullPath = QDir(basePath).filePath(QDir::cleanPath(filename)); if (!QFileInfo(fullPath).exists()) { qApp->setOverrideCursor(Qt::ArrowCursor); QString msg = i18nc( "@info", "The file associated to a file layer with the name \"%1\" is not found.\n\n" "Expected path:\n" "%2\n\n" "Do you want to locate it manually?", name, fullPath); int result = QMessageBox::warning(0, i18nc("@title:window", "File not found"), msg, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); if (result == QMessageBox::Yes) { KoFileDialog dialog(0, KoFileDialog::OpenFile, "OpenDocument"); dialog.setMimeTypeFilters(KisImportExportManager::supportedMimeTypes(KisImportExportManager::Import)); dialog.setDefaultDir(basePath); QString url = dialog.filename(); if (!QFileInfo(basePath).exists()) { filename = url; } else { QDir d(basePath); filename = d.relativeFilePath(url); } } qApp->restoreOverrideCursor(); } KisLayer *layer = new KisFileLayer(image, basePath, filename, (KisFileLayer::ScalingMethod)scalingMethod, name, opacity); Q_CHECK_PTR(layer); return layer; } KisNodeSP KisKraLoader::loadGroupLayer(const KoXmlElement& element, KisImageSP image, const QString& name, const KoColorSpace* cs, quint32 opacity) { Q_UNUSED(element); Q_UNUSED(cs); QString attr; KisGroupLayer* layer; layer = new KisGroupLayer(image, name, opacity); Q_CHECK_PTR(layer); return layer; } KisNodeSP KisKraLoader::loadAdjustmentLayer(const KoXmlElement& element, KisImageSP image, const QString& name, const KoColorSpace* cs, quint32 opacity) { // XXX: do something with filterversion? Q_UNUSED(cs); QString attr; KisAdjustmentLayer* layer; QString filtername; QString legacy = filtername; if ((filtername = element.attribute(FILTER_NAME)).isNull()) { // XXX: Invalid adjustmentlayer! We should warn about it! warnFile << "No filter in adjustment layer"; return 0; } //get deprecated filters. if (filtername=="brightnesscontrast") { legacy = filtername; filtername = "perchannel"; } if (filtername=="left edge detections" || filtername=="right edge detections" || filtername=="top edge detections" || filtername=="bottom edge detections") { legacy = filtername; filtername = "edge detection"; } KisFilterSP f = KisFilterRegistry::instance()->value(filtername); if (!f) { warnFile << "No filter for filtername" << filtername << ""; return 0; // XXX: We don't have this filter. We should warn about it! } - KisFilterConfigurationSP kfc = f->defaultConfiguration(); + KisFilterConfigurationSP kfc = f->factoryConfiguration(); kfc->setProperty("legacy", legacy); if (legacy=="brightnesscontrast") { kfc->setProperty("colorModel", cs->colorModelId().id()); } // We'll load the configuration and the selection later. layer = new KisAdjustmentLayer(image, name, kfc, 0); Q_CHECK_PTR(layer); layer->setOpacity(opacity); return layer; } KisNodeSP KisKraLoader::loadShapeLayer(const KoXmlElement& element, KisImageSP image, const QString& name, const KoColorSpace* cs, quint32 opacity) { Q_UNUSED(element); Q_UNUSED(cs); QString attr; KoShapeControllerBase * shapeController = 0; if (m_d->document) { shapeController = m_d->document->shapeController(); } KisShapeLayer* layer = new KisShapeLayer(shapeController, image, name, opacity); Q_CHECK_PTR(layer); return layer; } KisNodeSP KisKraLoader::loadGeneratorLayer(const KoXmlElement& element, KisImageSP image, const QString& name, const KoColorSpace* cs, quint32 opacity) { Q_UNUSED(cs); // XXX: do something with generator version? KisGeneratorLayer* layer; QString generatorname = element.attribute(GENERATOR_NAME); if (generatorname.isNull()) { // XXX: Invalid generator layer! We should warn about it! warnFile << "No generator in generator layer"; return 0; } KisGeneratorSP generator = KisGeneratorRegistry::instance()->value(generatorname); if (!generator) { warnFile << "No generator for generatorname" << generatorname << ""; return 0; // XXX: We don't have this generator. We should warn about it! } - KisFilterConfigurationSP kgc = generator->defaultConfiguration(); + KisFilterConfigurationSP kgc = generator->factoryConfiguration(); // We'll load the configuration and the selection later. layer = new KisGeneratorLayer(image, name, kgc, 0); Q_CHECK_PTR(layer); layer->setOpacity(opacity); return layer; } KisNodeSP KisKraLoader::loadCloneLayer(const KoXmlElement& element, KisImageSP image, const QString& name, const KoColorSpace* cs, quint32 opacity) { Q_UNUSED(cs); KisCloneLayerSP layer = new KisCloneLayer(0, image, name, opacity); KisNodeUuidInfo info; if (! (element.attribute(CLONE_FROM_UUID)).isNull()) { info = KisNodeUuidInfo(QUuid(element.attribute(CLONE_FROM_UUID))); } else { if ((element.attribute(CLONE_FROM)).isNull()) { return 0; } else { info = KisNodeUuidInfo(element.attribute(CLONE_FROM)); } } layer->setCopyFromInfo(info); if ((element.attribute(CLONE_TYPE)).isNull()) { return 0; } else { layer->setCopyType((CopyLayerType) element.attribute(CLONE_TYPE).toInt()); } return layer; } KisNodeSP KisKraLoader::loadFilterMask(const KoXmlElement& element) { QString attr; KisFilterMask* mask; QString filtername; // XXX: should we check the version? if ((filtername = element.attribute(FILTER_NAME)).isNull()) { // XXX: Invalid filter layer! We should warn about it! warnFile << "No filter in filter layer"; return 0; } KisFilterSP f = KisFilterRegistry::instance()->value(filtername); if (!f) { warnFile << "No filter for filtername" << filtername << ""; return 0; // XXX: We don't have this filter. We should warn about it! } - KisFilterConfigurationSP kfc = f->defaultConfiguration(); + KisFilterConfigurationSP kfc = f->factoryConfiguration(); // We'll load the configuration and the selection later. mask = new KisFilterMask(); mask->setFilter(kfc); Q_CHECK_PTR(mask); return mask; } KisNodeSP KisKraLoader::loadTransformMask(const KoXmlElement& element) { Q_UNUSED(element); KisTransformMask* mask; /** * We'll load the transform configuration later on a stage * of binary data loading */ mask = new KisTransformMask(); Q_CHECK_PTR(mask); return mask; } KisNodeSP KisKraLoader::loadTransparencyMask(const KoXmlElement& element) { Q_UNUSED(element); KisTransparencyMask* mask = new KisTransparencyMask(); Q_CHECK_PTR(mask); return mask; } KisNodeSP KisKraLoader::loadSelectionMask(KisImageSP image, const KoXmlElement& element) { KisSelectionMaskSP mask = new KisSelectionMask(image); bool active = element.attribute(ACTIVE, "1") == "0" ? false : true; mask->setActive(active); Q_CHECK_PTR(mask); return mask; } KisNodeSP KisKraLoader::loadColorizeMask(KisImageSP image, const KoXmlElement& element, const KoColorSpace *colorSpace) { KisColorizeMaskSP mask = new KisColorizeMask(); const bool editKeystrokes = element.attribute(COLORIZE_EDIT_KEYSTROKES, "1") == "0" ? false : true; const bool showColoring = element.attribute(COLORIZE_SHOW_COLORING, "1") == "0" ? false : true; KisLayerPropertiesIcons::setNodeProperty(mask, KisLayerPropertiesIcons::colorizeEditKeyStrokes, editKeystrokes, image); KisLayerPropertiesIcons::setNodeProperty(mask, KisLayerPropertiesIcons::colorizeShowColoring, showColoring, image); const bool useEdgeDetection = KisDomUtils::toInt(element.attribute(COLORIZE_USE_EDGE_DETECTION, "0")); const qreal edgeDetectionSize = KisDomUtils::toDouble(element.attribute(COLORIZE_EDGE_DETECTION_SIZE, "4")); const qreal radius = KisDomUtils::toDouble(element.attribute(COLORIZE_FUZZY_RADIUS, "0")); const int cleanUp = KisDomUtils::toInt(element.attribute(COLORIZE_CLEANUP, "0")); const bool limitToDevice = KisDomUtils::toInt(element.attribute(COLORIZE_LIMIT_TO_DEVICE, "0")); mask->setUseEdgeDetection(useEdgeDetection); mask->setEdgeDetectionSize(edgeDetectionSize); mask->setFuzzyRadius(radius); mask->setCleanUpAmount(qreal(cleanUp) / 100.0); mask->setLimitToDeviceBounds(limitToDevice); delete mask->setColorSpace(colorSpace); mask->setImage(image); return mask; } void KisKraLoader::loadCompositions(const KoXmlElement& elem, KisImageSP image) { KoXmlNode child; for (child = elem.firstChild(); !child.isNull(); child = child.nextSibling()) { KoXmlElement e = child.toElement(); QString name = e.attribute("name"); bool exportEnabled = e.attribute("exportEnabled", "1") == "0" ? false : true; KisLayerCompositionSP composition(new KisLayerComposition(image, name)); composition->setExportEnabled(exportEnabled); KoXmlNode value; for (value = child.lastChild(); !value.isNull(); value = value.previousSibling()) { KoXmlElement e = value.toElement(); QUuid uuid(e.attribute("uuid")); bool visible = e.attribute("visible", "1") == "0" ? false : true; composition->setVisible(uuid, visible); bool collapsed = e.attribute("collapsed", "1") == "0" ? false : true; composition->setCollapsed(uuid, collapsed); } image->addComposition(composition); } } void KisKraLoader::loadAssistantsList(const KoXmlElement &elem) { KoXmlNode child; int count = 0; for (child = elem.firstChild(); !child.isNull(); child = child.nextSibling()) { KoXmlElement e = child.toElement(); QString type = e.attribute("type"); QString file_name = e.attribute("filename"); m_d->assistantsFilenames.insert(file_name,type); count++; } } void KisKraLoader::loadGrid(const KoXmlElement& elem) { QDomDocument dom; KoXml::asQDomElement(dom, elem); QDomElement domElement = dom.firstChildElement(); KisGridConfig config; config.loadDynamicDataFromXml(domElement); config.loadStaticData(); m_d->document->setGridConfig(config); } void KisKraLoader::loadGuides(const KoXmlElement& elem) { QDomDocument dom; KoXml::asQDomElement(dom, elem); QDomElement domElement = dom.firstChildElement(); KisGuidesConfig guides; guides.loadFromXml(domElement); m_d->document->setGuidesConfig(guides); } void KisKraLoader::loadMirrorAxis(const KoXmlElement &elem) { QDomDocument dom; KoXml::asQDomElement(dom, elem); QDomElement domElement = dom.firstChildElement(); KisMirrorAxisConfig mirrorAxis; mirrorAxis.loadFromXml(domElement); m_d->document->setMirrorAxisConfig(mirrorAxis); } void KisKraLoader::loadAudio(const KoXmlElement& elem, KisImageSP image) { QDomDocument dom; KoXml::asQDomElement(dom, elem); QDomElement qElement = dom.firstChildElement(); QString fileName; if (KisDomUtils::loadValue(qElement, "masterChannelPath", &fileName)) { fileName = QDir::toNativeSeparators(fileName); QDir baseDirectory = QFileInfo(m_d->document->localFilePath()).absoluteDir(); fileName = baseDirectory.absoluteFilePath(fileName); QFileInfo info(fileName); if (!info.exists()) { qApp->setOverrideCursor(Qt::ArrowCursor); QString msg = i18nc( "@info", "Audio channel file \"%1\" doesn't exist!\n\n" "Expected path:\n" "%2\n\n" "Do you want to locate it manually?", info.fileName(), info.absoluteFilePath()); int result = QMessageBox::warning(0, i18nc("@title:window", "File not found"), msg, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); if (result == QMessageBox::Yes) { info.setFile(KisImportExportManager::askForAudioFileName(info.absolutePath(), 0)); } qApp->restoreOverrideCursor(); } if (info.exists()) { image->animationInterface()->setAudioChannelFileName(info.absoluteFilePath()); } } bool audioMuted = false; if (KisDomUtils::loadValue(qElement, "audioMuted", &audioMuted)) { image->animationInterface()->setAudioMuted(audioMuted); } qreal audioVolume = 0.5; if (KisDomUtils::loadValue(qElement, "audioVolume", &audioVolume)) { image->animationInterface()->setAudioVolume(audioVolume); } } KisNodeSP KisKraLoader::loadReferenceImagesLayer(const KoXmlElement &elem, KisImageSP image) { KisSharedPtr layer = new KisReferenceImagesLayer(m_d->document->shapeController(), image); m_d->document->setReferenceImagesLayer(layer, false); for (QDomElement child = elem.firstChildElement(); !child.isNull(); child = child.nextSiblingElement()) { if (child.nodeName().toLower() == "referenceimage") { auto* reference = KisReferenceImage::fromXml(child); layer->addShape(reference); } } return layer; } diff --git a/plugins/impex/libkra/tests/kis_kra_saver_test.cpp b/plugins/impex/libkra/tests/kis_kra_saver_test.cpp index e9afb6f14c..ec4e6885eb 100644 --- a/plugins/impex/libkra/tests/kis_kra_saver_test.cpp +++ b/plugins/impex/libkra/tests/kis_kra_saver_test.cpp @@ -1,552 +1,552 @@ /* * Copyright (c) 2007 Boudewijn Rempt boud@valdyas.org * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_kra_saver_test.h" #include #include #include #include #include #include #include "filter/kis_filter_registry.h" #include "filter/kis_filter_configuration.h" #include "filter/kis_filter.h" #include "kis_image.h" #include "kis_pixel_selection.h" #include "kis_group_layer.h" #include "kis_paint_layer.h" #include "kis_clone_layer.h" #include "kis_adjustment_layer.h" #include "kis_shape_layer.h" #include "kis_filter_mask.h" #include "kis_transparency_mask.h" #include "kis_selection_mask.h" #include "kis_selection.h" #include "kis_fill_painter.h" #include "kis_shape_selection.h" #include "util.h" #include "testutil.h" #include "kis_keyframe_channel.h" #include "kis_image_animation_interface.h" #include "kis_layer_properties_icons.h" #include "kis_transform_mask_params_interface.h" #include #include #include #include const QString KraMimetype = "application/x-krita"; void KisKraSaverTest::initTestCase() { KoResourcePaths::addResourceDir("ko_patterns", QString(SYSTEM_RESOURCES_DATA_DIR) + "/patterns"); KisFilterRegistry::instance(); KisGeneratorRegistry::instance(); } void KisKraSaverTest::testCrashyShapeLayer() { /** * KisShapeLayer used to call setImage from its destructor and * therefore causing an infinite recursion (when at least one transparency * mask was preset. This testcase just checks that. */ //QScopedPointer doc(createCompleteDocument(true)); //Q_UNUSED(doc); } void KisKraSaverTest::testRoundTrip() { KisDocument* doc = createCompleteDocument(); KoColor bgColor(Qt::red, doc->image()->colorSpace()); doc->image()->setDefaultProjectionColor(bgColor); doc->exportDocumentSync(QUrl::fromLocalFile("roundtriptest.kra"), doc->mimeType()); QStringList list; KisCountVisitor cv1(list, KoProperties()); doc->image()->rootLayer()->accept(cv1); KisDocument *doc2 = KisPart::instance()->createDocument(); bool result = doc2->loadNativeFormat("roundtriptest.kra"); QVERIFY(result); KisCountVisitor cv2(list, KoProperties()); doc2->image()->rootLayer()->accept(cv2); QCOMPARE(cv1.count(), cv2.count()); // check whether the BG color is saved correctly QCOMPARE(doc2->image()->defaultProjectionColor(), bgColor); // test round trip of a transform mask KisNode* tnode = TestUtil::findNode(doc2->image()->rootLayer(), "testTransformMask").data(); QVERIFY(tnode); KisTransformMask *tmask = dynamic_cast(tnode); QVERIFY(tmask); KisDumbTransformMaskParams *params = dynamic_cast(tmask->transformParams().data()); QVERIFY(params); QTransform t = params->testingGetTransform(); QCOMPARE(t, createTestingTransform()); delete doc2; delete doc; } void KisKraSaverTest::testSaveEmpty() { KisDocument* doc = createEmptyDocument(); doc->exportDocumentSync(QUrl::fromLocalFile("emptytest.kra"), doc->mimeType()); QStringList list; KisCountVisitor cv1(list, KoProperties()); doc->image()->rootLayer()->accept(cv1); KisDocument *doc2 = KisPart::instance()->createDocument(); doc2->loadNativeFormat("emptytest.kra"); KisCountVisitor cv2(list, KoProperties()); doc2->image()->rootLayer()->accept(cv2); QCOMPARE(cv1.count(), cv2.count()); delete doc2; delete doc; } #include void testRoundTripFillLayerImpl(const QString &testName, KisFilterConfigurationSP config) { TestUtil::ReferenceImageChecker chk(testName, "fill_layer"); chk.setFuzzy(2); QScopedPointer doc(KisPart::instance()->createDocument()); // mask parent should be destructed before the document! QRect refRect(0,0,512,512); TestUtil::MaskParent p(refRect); doc->setCurrentImage(p.image); doc->documentInfo()->setAboutInfo("title", p.image->objectName()); KisSelectionSP selection; KisGeneratorLayerSP glayer = new KisGeneratorLayer(p.image, "glayer", config, selection); p.image->addNode(glayer, p.image->root(), KisNodeSP()); glayer->setDirty(); p.image->waitForDone(); chk.checkImage(p.image, "00_initial_layer_update"); doc->exportDocumentSync(QUrl::fromLocalFile("roundtrip_fill_layer_test.kra"), doc->mimeType()); QScopedPointer doc2(KisPart::instance()->createDocument()); doc2->loadNativeFormat("roundtrip_fill_layer_test.kra"); doc2->image()->waitForDone(); chk.checkImage(doc2->image(), "01_fill_layer_round_trip"); QVERIFY(chk.testPassed()); } void KisKraSaverTest::testRoundTripFillLayerColor() { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisGeneratorSP generator = KisGeneratorRegistry::instance()->get("color"); Q_ASSERT(generator); // warning: we pass null paint device to the default constructed value - KisFilterConfigurationSP config = generator->factoryConfiguration(); + KisFilterConfigurationSP config = generator->defaultConfiguration(); Q_ASSERT(config); QVariant v; v.setValue(KoColor(Qt::red, cs)); config->setProperty("color", v); testRoundTripFillLayerImpl("fill_layer_color", config); } void KisKraSaverTest::testRoundTripFillLayerPattern() { KisGeneratorSP generator = KisGeneratorRegistry::instance()->get("pattern"); QVERIFY(generator); // warning: we pass null paint device to the default constructed value - KisFilterConfigurationSP config = generator->factoryConfiguration(); + KisFilterConfigurationSP config = generator->defaultConfiguration(); QVERIFY(config); QVariant v; v.setValue(QString("11_drawed_furry.png")); config->setProperty("pattern", v); testRoundTripFillLayerImpl("fill_layer_pattern", config); } #include "kis_psd_layer_style.h" void KisKraSaverTest::testRoundTripLayerStyles() { TestUtil::ReferenceImageChecker chk("kra_saver_test", "layer_styles"); QRect imageRect(0,0,512,512); // the document should be created before the image! QScopedPointer doc(KisPart::instance()->createDocument()); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(new KisSurrogateUndoStore(), imageRect.width(), imageRect.height(), cs, "test image"); KisPaintLayerSP layer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8); KisPaintLayerSP layer2 = new KisPaintLayer(image, "paint2", OPACITY_OPAQUE_U8); KisPaintLayerSP layer3 = new KisPaintLayer(image, "paint3", OPACITY_OPAQUE_U8); image->addNode(layer1); image->addNode(layer2); image->addNode(layer3); doc->setCurrentImage(image); doc->documentInfo()->setAboutInfo("title", image->objectName()); layer1->paintDevice()->fill(QRect(100, 100, 100, 100), KoColor(Qt::red, cs)); layer2->paintDevice()->fill(QRect(200, 200, 100, 100), KoColor(Qt::green, cs)); layer3->paintDevice()->fill(QRect(300, 300, 100, 100), KoColor(Qt::blue, cs)); KisPSDLayerStyleSP style(new KisPSDLayerStyle()); style->dropShadow()->setEffectEnabled(true); style->dropShadow()->setAngle(-90); style->dropShadow()->setUseGlobalLight(false); layer1->setLayerStyle(style->clone()); style->dropShadow()->setAngle(180); style->dropShadow()->setUseGlobalLight(true); layer2->setLayerStyle(style->clone()); style->dropShadow()->setAngle(90); style->dropShadow()->setUseGlobalLight(false); layer3->setLayerStyle(style->clone()); image->initialRefreshGraph(); chk.checkImage(image, "00_initial_layers"); doc->exportDocumentSync(QUrl::fromLocalFile("roundtrip_layer_styles.kra"), doc->mimeType()); QScopedPointer doc2(KisPart::instance()->createDocument()); doc2->loadNativeFormat("roundtrip_layer_styles.kra"); doc2->image()->waitForDone(); chk.checkImage(doc2->image(), "00_initial_layers"); QVERIFY(chk.testPassed()); } void KisKraSaverTest::testRoundTripAnimation() { QScopedPointer doc(KisPart::instance()->createDocument()); QRect imageRect(0,0,512,512); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(new KisSurrogateUndoStore(), imageRect.width(), imageRect.height(), cs, "test image"); KisPaintLayerSP layer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8); image->addNode(layer1); layer1->paintDevice()->fill(QRect(100, 100, 50, 50), KoColor(Qt::black, cs)); layer1->paintDevice()->setDefaultPixel(KoColor(Qt::red, cs)); KUndo2Command parentCommand; layer1->enableAnimation(); KisKeyframeChannel *rasterChannel = layer1->getKeyframeChannel(KisKeyframeChannel::Content.id(), true); QVERIFY(rasterChannel); rasterChannel->addKeyframe(10, &parentCommand); image->animationInterface()->switchCurrentTimeAsync(10); image->waitForDone(); layer1->paintDevice()->fill(QRect(200, 50, 10, 10), KoColor(Qt::black, cs)); layer1->paintDevice()->moveTo(25, 15); layer1->paintDevice()->setDefaultPixel(KoColor(Qt::green, cs)); rasterChannel->addKeyframe(20, &parentCommand); image->animationInterface()->switchCurrentTimeAsync(20); image->waitForDone(); layer1->paintDevice()->fill(QRect(150, 200, 30, 30), KoColor(Qt::black, cs)); layer1->paintDevice()->moveTo(100, 50); layer1->paintDevice()->setDefaultPixel(KoColor(Qt::blue, cs)); QVERIFY(!layer1->useInTimeline()); layer1->setUseInTimeline(true); doc->setCurrentImage(image); doc->exportDocumentSync(QUrl::fromLocalFile("roundtrip_animation.kra"), doc->mimeType()); QScopedPointer doc2(KisPart::instance()->createDocument()); doc2->loadNativeFormat("roundtrip_animation.kra"); KisImageSP image2 = doc2->image(); KisNodeSP node = image2->root()->firstChild(); QVERIFY(node->inherits("KisPaintLayer")); KisPaintLayerSP layer2 = qobject_cast(node.data()); cs = layer2->paintDevice()->colorSpace(); QCOMPARE(image2->animationInterface()->currentTime(), 20); KisKeyframeChannel *channel = layer2->getKeyframeChannel(KisKeyframeChannel::Content.id()); QVERIFY(channel); QCOMPARE(channel->keyframeCount(), 3); image2->animationInterface()->switchCurrentTimeAsync(0); image2->waitForDone(); QCOMPARE(layer2->paintDevice()->nonDefaultPixelArea(), QRect(64, 64, 128, 128)); QCOMPARE(layer2->paintDevice()->x(), 0); QCOMPARE(layer2->paintDevice()->y(), 0); QCOMPARE(layer2->paintDevice()->defaultPixel(), KoColor(Qt::red, cs)); image2->animationInterface()->switchCurrentTimeAsync(10); image2->waitForDone(); QCOMPARE(layer2->paintDevice()->nonDefaultPixelArea(), QRect(217, 15, 64, 64)); QCOMPARE(layer2->paintDevice()->x(), 25); QCOMPARE(layer2->paintDevice()->y(), 15); QCOMPARE(layer2->paintDevice()->defaultPixel(), KoColor(Qt::green, cs)); image2->animationInterface()->switchCurrentTimeAsync(20); image2->waitForDone(); QCOMPARE(layer2->paintDevice()->nonDefaultPixelArea(), QRect(228, 242, 64, 64)); QCOMPARE(layer2->paintDevice()->x(), 100); QCOMPARE(layer2->paintDevice()->y(), 50); QCOMPARE(layer2->paintDevice()->defaultPixel(), KoColor(Qt::blue, cs)); QVERIFY(layer2->useInTimeline()); } #include "lazybrush/kis_lazy_fill_tools.h" void KisKraSaverTest::testRoundTripColorizeMask() { QRect imageRect(0,0,512,512); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); const KoColorSpace * weirdCS = KoColorSpaceRegistry::instance()->rgb16(); QScopedPointer doc(KisPart::instance()->createDocument()); KisImageSP image = new KisImage(new KisSurrogateUndoStore(), imageRect.width(), imageRect.height(), cs, "test image"); doc->setCurrentImage(image); KisPaintLayerSP layer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8, weirdCS); image->addNode(layer1); KisColorizeMaskSP mask = new KisColorizeMask(); image->addNode(mask, layer1); mask->initializeCompositeOp(); delete mask->setColorSpace(layer1->colorSpace()); { KisPaintDeviceSP key1 = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8()); key1->fill(QRect(50,50,10,20), KoColor(Qt::black, key1->colorSpace())); mask->testingAddKeyStroke(key1, KoColor(Qt::green, layer1->colorSpace())); // KIS_DUMP_DEVICE_2(key1, refRect, "key1", "dd"); } { KisPaintDeviceSP key2 = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8()); key2->fill(QRect(150,50,10,20), KoColor(Qt::black, key2->colorSpace())); mask->testingAddKeyStroke(key2, KoColor(Qt::red, layer1->colorSpace())); // KIS_DUMP_DEVICE_2(key2, refRect, "key2", "dd"); } { KisPaintDeviceSP key3 = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8()); key3->fill(QRect(0,0,10,10), KoColor(Qt::black, key3->colorSpace())); mask->testingAddKeyStroke(key3, KoColor(Qt::blue, layer1->colorSpace()), true); // KIS_DUMP_DEVICE_2(key3, refRect, "key3", "dd"); } KisLayerPropertiesIcons::setNodeProperty(mask, KisLayerPropertiesIcons::colorizeEditKeyStrokes, false, image); KisLayerPropertiesIcons::setNodeProperty(mask, KisLayerPropertiesIcons::colorizeShowColoring, false, image); doc->exportDocumentSync(QUrl::fromLocalFile("roundtrip_colorize.kra"), doc->mimeType()); QScopedPointer doc2(KisPart::instance()->createDocument()); doc2->loadNativeFormat("roundtrip_colorize.kra"); KisImageSP image2 = doc2->image(); KisNodeSP node = image2->root()->firstChild()->firstChild(); KisColorizeMaskSP mask2 = dynamic_cast(node.data()); QVERIFY(mask2); QCOMPARE(mask2->compositeOpId(), mask->compositeOpId()); QCOMPARE(mask2->colorSpace(), mask->colorSpace()); QCOMPARE(KisLayerPropertiesIcons::nodeProperty(mask, KisLayerPropertiesIcons::colorizeEditKeyStrokes, true).toBool(), false); QCOMPARE(KisLayerPropertiesIcons::nodeProperty(mask, KisLayerPropertiesIcons::colorizeShowColoring, true).toBool(), false); QList strokes = mask->fetchKeyStrokesDirect(); qDebug() << ppVar(strokes.size()); QCOMPARE(strokes[0].dev->exactBounds(), QRect(50,50,10,20)); QCOMPARE(strokes[0].isTransparent, false); QCOMPARE(strokes[0].color.colorSpace(), weirdCS); QCOMPARE(strokes[1].dev->exactBounds(), QRect(150,50,10,20)); QCOMPARE(strokes[1].isTransparent, false); QCOMPARE(strokes[1].color.colorSpace(), weirdCS); QCOMPARE(strokes[2].dev->exactBounds(), QRect(0,0,10,10)); QCOMPARE(strokes[2].isTransparent, true); QCOMPARE(strokes[2].color.colorSpace(), weirdCS); } #include void KisKraSaverTest::testRoundTripShapeLayer() { TestUtil::ReferenceImageChecker chk("kra_saver_test", "shape_layer"); QRect refRect(0,0,512,512); QScopedPointer doc(KisPart::instance()->createDocument()); TestUtil::MaskParent p(refRect); const qreal resolution = 144.0 / 72.0; p.image->setResolution(resolution, resolution); doc->setCurrentImage(p.image); doc->documentInfo()->setAboutInfo("title", p.image->objectName()); KoPathShape* path = new KoPathShape(); path->setShapeId(KoPathShapeId); path->moveTo(QPointF(10, 10)); path->lineTo(QPointF( 10, 110)); path->lineTo(QPointF(110, 110)); path->lineTo(QPointF(110, 10)); path->close(); path->normalize(); path->setBackground(toQShared(new KoColorBackground(Qt::red))); path->setName("my_precious_shape"); KisShapeLayerSP shapeLayer = new KisShapeLayer(doc->shapeController(), p.image, "shapeLayer1", 75); shapeLayer->addShape(path); p.image->addNode(shapeLayer); shapeLayer->setDirty(); qApp->processEvents(); p.image->waitForDone(); chk.checkImage(p.image, "00_initial_layer_update"); doc->exportDocumentSync(QUrl::fromLocalFile("roundtrip_shapelayer_test.kra"), doc->mimeType()); QScopedPointer doc2(KisPart::instance()->createDocument()); doc2->loadNativeFormat("roundtrip_shapelayer_test.kra"); qApp->processEvents(); doc2->image()->waitForDone(); QCOMPARE(doc2->image()->xRes(), resolution); QCOMPARE(doc2->image()->yRes(), resolution); chk.checkImage(doc2->image(), "01_shape_layer_round_trip"); QVERIFY(chk.testPassed()); } void KisKraSaverTest::testRoundTripShapeSelection() { TestUtil::ReferenceImageChecker chk("kra_saver_test", "shape_selection"); QRect refRect(0,0,512,512); QScopedPointer doc(KisPart::instance()->createDocument()); TestUtil::MaskParent p(refRect); doc->setCurrentImage(p.image); const qreal resolution = 144.0 / 72.0; p.image->setResolution(resolution, resolution); doc->setCurrentImage(p.image); doc->documentInfo()->setAboutInfo("title", p.image->objectName()); p.layer->paintDevice()->setDefaultPixel(KoColor(Qt::green, p.layer->colorSpace())); KisSelectionSP selection = new KisSelection(p.layer->paintDevice()->defaultBounds()); KisShapeSelection *shapeSelection = new KisShapeSelection(doc->shapeController(), p.image, selection); selection->setShapeSelection(shapeSelection); KoPathShape* path = new KoPathShape(); path->setShapeId(KoPathShapeId); path->moveTo(QPointF(10, 10)); path->lineTo(QPointF( 10, 110)); path->lineTo(QPointF(110, 110)); path->lineTo(QPointF(110, 10)); path->close(); path->normalize(); path->setBackground(toQShared(new KoColorBackground(Qt::red))); path->setName("my_precious_shape"); shapeSelection->addShape(path); KisTransparencyMaskSP tmask = new KisTransparencyMask(); tmask->setSelection(selection); p.image->addNode(tmask, p.layer); tmask->setDirty(p.image->bounds()); qApp->processEvents(); p.image->waitForDone(); chk.checkImage(p.image, "00_initial_shape_selection"); doc->exportDocumentSync(QUrl::fromLocalFile("roundtrip_shapeselection_test.kra"), doc->mimeType()); QScopedPointer doc2(KisPart::instance()->createDocument()); doc2->loadNativeFormat("roundtrip_shapeselection_test.kra"); qApp->processEvents(); doc2->image()->waitForDone(); QCOMPARE(doc2->image()->xRes(), resolution); QCOMPARE(doc2->image()->yRes(), resolution); chk.checkImage(doc2->image(), "00_initial_shape_selection"); KisNodeSP node = doc2->image()->root()->firstChild()->firstChild(); KisTransparencyMask *newMask = dynamic_cast(node.data()); QVERIFY(newMask); QVERIFY(newMask->selection()->hasShapeSelection()); QVERIFY(chk.testPassed()); } void KisKraSaverTest::testExportToReadonly() { TestUtil::testExportToReadonly(QString(FILES_DATA_DIR), KraMimetype); } KISTEST_MAIN(KisKraSaverTest) diff --git a/plugins/impex/ora/kis_open_raster_stack_load_visitor.cpp b/plugins/impex/ora/kis_open_raster_stack_load_visitor.cpp index 70a9cf3edf..589a5c07f3 100644 --- a/plugins/impex/ora/kis_open_raster_stack_load_visitor.cpp +++ b/plugins/impex/ora/kis_open_raster_stack_load_visitor.cpp @@ -1,251 +1,251 @@ /* * Copyright (c) 2006-2007,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. */ #include "kis_open_raster_stack_load_visitor.h" #include #include #include // Includes from krita/image #include #include #include #include #include #include #include #include #include #include #include #include "KisDocument.h" #include "kis_open_raster_load_context.h" struct KisOpenRasterStackLoadVisitor::Private { KisImageSP image; vKisNodeSP activeNodes; KisUndoStore* undoStore; KisOpenRasterLoadContext* loadContext; double xRes; double yRes; }; KisOpenRasterStackLoadVisitor::KisOpenRasterStackLoadVisitor(KisUndoStore* undoStore, KisOpenRasterLoadContext* orlc) : d(new Private) { d->undoStore = undoStore; d->loadContext = orlc; } KisOpenRasterStackLoadVisitor::~KisOpenRasterStackLoadVisitor() { delete d; } KisImageSP KisOpenRasterStackLoadVisitor::image() { return d->image; } vKisNodeSP KisOpenRasterStackLoadVisitor::activeNodes() { return d->activeNodes; } void KisOpenRasterStackLoadVisitor::loadImage() { QDomDocument doc = d->loadContext->loadStack(); for (QDomNode node = doc.firstChild(); !node.isNull(); node = node.nextSibling()) { if (node.isElement() && node.nodeName() == "image") { // it's the image root QDomElement subelem = node.toElement(); int width = 0; if (!subelem.attribute("w").isNull()) { width = subelem.attribute("w").toInt(); } int height = 0; if (!subelem.attribute("h").isNull()) { height = subelem.attribute("h").toInt(); } d->xRes = 75.0/72; // Setting the default value of the X Resolution = 75ppi if (!subelem.attribute("xres").isNull()){ d->xRes = (KisDomUtils::toDouble(subelem.attribute("xres")) / 72); } d->yRes = 75.0/72; if (!subelem.attribute("yres").isNull()){ d->yRes = (KisDomUtils::toDouble(subelem.attribute("yres")) / 72); } dbgFile << ppVar(width) << ppVar(height); d->image = new KisImage(d->undoStore, width, height, KoColorSpaceRegistry::instance()->rgb8(), "OpenRaster Image (name)"); for (QDomNode node2 = node.firstChild(); !node2.isNull(); node2 = node2.nextSibling()) { if (node2.isElement() && node2.nodeName() == "stack") { // it's the root layer ! QDomElement subelem2 = node2.toElement(); loadGroupLayer(subelem2, d->image->rootLayer()); break; } } } } } void KisOpenRasterStackLoadVisitor::loadLayerInfo(const QDomElement& elem, KisLayerSP layer) { layer->setName(elem.attribute("name")); layer->setX(elem.attribute("x").toInt()); layer->setY(elem.attribute("y").toInt()); if (elem.attribute("visibility") == "hidden") { layer->setVisible(false); } else { layer->setVisible(true); } if (elem.hasAttribute("edit-locked")) { layer->setUserLocked(elem.attribute("edit-locked") == "true"); } if (elem.hasAttribute("selected") && elem.attribute("selected") == "true") { d->activeNodes.append(layer); } QString compop = elem.attribute("composite-op"); if (compop.startsWith("svg:")) { //we don't have a 'composite op clear' despite the registery reserving a string for it, doesn't matter, ora doesn't use it. //if (compop == "svg:clear") layer->setCompositeOpId(COMPOSITE_CLEAR); if (compop == "svg:src-over") layer->setCompositeOpId(COMPOSITE_OVER); //not part of the spec. //if (compop == "svg:dst-over") layer->setCompositeOpId(COMPOSITE_BEHIND); //dst-in "The source that overlaps the destination, replaces the destination." if (compop == "svg:dst-in") layer->setCompositeOpId(COMPOSITE_DESTINATION_IN); //dst-out "dst is placed, where it falls outside of the source." if (compop == "svg:dst-out") layer->setCompositeOpId(COMPOSITE_ERASE); //src-atop "Destination which overlaps the source replaces the source. Source is placed elsewhere." //this is basically our alpha-inherit. if (compop == "svg:src-atop") layer->disableAlphaChannel(true); //dst-atop if (compop == "svg:dst-atop") layer->setCompositeOpId(COMPOSITE_DESTINATION_ATOP); //plus is svg standard's way of saying addition... photoshop calls this linear dodge, btw, maybe make a similar alias? if (compop == "svg:plus") layer->setCompositeOpId(COMPOSITE_ADD); if (compop == "svg:multiply") layer->setCompositeOpId(COMPOSITE_MULT); if (compop == "svg:screen") layer->setCompositeOpId(COMPOSITE_SCREEN); if (compop == "svg:overlay") layer->setCompositeOpId(COMPOSITE_OVERLAY); if (compop == "svg:darken") layer->setCompositeOpId(COMPOSITE_DARKEN); if (compop == "svg:lighten") layer->setCompositeOpId(COMPOSITE_LIGHTEN); if (compop == "svg:color-dodge") layer->setCompositeOpId(COMPOSITE_DODGE); if (compop == "svg:color-burn") layer->setCompositeOpId(COMPOSITE_BURN); if (compop == "svg:hard-light") layer->setCompositeOpId(COMPOSITE_HARD_LIGHT); if (compop == "svg:soft-light") layer->setCompositeOpId(COMPOSITE_SOFT_LIGHT_SVG); if (compop == "svg:difference") layer->setCompositeOpId(COMPOSITE_DIFF); if (compop == "svg:color") layer->setCompositeOpId(COMPOSITE_COLOR); if (compop == "svg:luminosity") layer->setCompositeOpId(COMPOSITE_LUMINIZE); if (compop == "svg:hue") layer->setCompositeOpId(COMPOSITE_HUE); if (compop == "svg:saturation") layer->setCompositeOpId(COMPOSITE_SATURATION); //Exclusion isn't in the official list. //if (compop == "svg:exclusion") layer->setCompositeOpId(COMPOSITE_EXCLUSION); } else if (compop.startsWith("krita:")) { compop = compop.remove(0, 6); layer->setCompositeOpId(compop); } else { // to fix old bugs in krita's ora export if (compop == "color-dodge") layer->setCompositeOpId(COMPOSITE_DODGE); if (compop == "difference") layer->setCompositeOpId(COMPOSITE_DIFF); if (compop == "svg:add") layer->setCompositeOpId(COMPOSITE_ADD); } } void KisOpenRasterStackLoadVisitor::loadAdjustmentLayer(const QDomElement& elem, KisAdjustmentLayerSP aL) { loadLayerInfo(elem, aL); } void KisOpenRasterStackLoadVisitor::loadPaintLayer(const QDomElement& elem, KisPaintLayerSP pL) { loadLayerInfo(elem, pL); dbgFile << "Loading was unsuccessful"; } void KisOpenRasterStackLoadVisitor::loadGroupLayer(const QDomElement& elem, KisGroupLayerSP groupLayer) { dbgFile << "Loading group layer" << d->image; loadLayerInfo(elem, groupLayer); for (QDomNode node = elem.firstChild(); !node.isNull(); node = node.nextSibling()) { if (node.isElement()) { QDomElement subelem = node.toElement(); if (node.nodeName() == "stack") { double opacity = 1.0; if (!subelem.attribute("opacity").isNull()) { opacity = KisDomUtils::toDouble(subelem.attribute("opacity", "1.0")); } KisGroupLayerSP layer = new KisGroupLayer(d->image, "", opacity * 255); bool passThrough = true; if (subelem.attribute("isolation")=="isolate") { passThrough = false; } layer->setPassThroughMode(passThrough); d->image->addNode(layer, groupLayer.data(), 0); loadGroupLayer(subelem, layer); } else if (node.nodeName() == "layer") { QString filename = subelem.attribute("src"); if (!filename.isNull()) { const qreal opacity = KisDomUtils::toDouble(subelem.attribute("opacity", "1.0")); KisImageSP pngImage = d->loadContext->loadDeviceData(filename); if (pngImage) { // If ORA doesn't have resolution info, load the default value(75 ppi) else fetch from stack.xml d->image->setResolution(d->xRes, d->yRes); // now get the device KisPaintDeviceSP device = pngImage->projection(); KisPaintLayerSP layer = new KisPaintLayer(groupLayer->image() , "", opacity * 255, device); d->image->addNode(layer, groupLayer, 0); loadPaintLayer(subelem, layer); dbgFile << "Loading was successful"; } } } else if (node.nodeName() == "filter") { QString filterType = subelem.attribute("type"); QStringList filterTypeSplit = filterType.split(':'); KisFilterSP f = 0; if (filterTypeSplit[0] == "applications" && filterTypeSplit[1] == "krita") { f = KisFilterRegistry::instance()->value(filterTypeSplit[2]); } - KisFilterConfigurationSP kfc = f->defaultConfiguration(); + KisFilterConfigurationSP kfc = f->factoryConfiguration(); KisAdjustmentLayerSP layer = new KisAdjustmentLayer(groupLayer->image() , "", kfc, KisSelectionSP(0)); d->image->addNode(layer.data(), groupLayer.data(), 0); loadAdjustmentLayer(subelem, layer); } else { dbgFile << "Unknown element : " << node.nodeName(); } } } } diff --git a/plugins/impex/qimageio/CMakeLists.txt b/plugins/impex/qimageio/CMakeLists.txt index 07221802c9..c916af4870 100644 --- a/plugins/impex/qimageio/CMakeLists.txt +++ b/plugins/impex/qimageio/CMakeLists.txt @@ -1,27 +1,27 @@ add_subdirectory(tests) set(kritaqimageioexport_SOURCES kis_qimageio_export.cpp ) +ki18n_wrap_ui(kritaqimageioexport_SOURCES kis_wdg_options_qimageio.ui) ki18n_wrap_ui(kritaqimageioexport_SOURCES ) add_library(kritaqimageioexport MODULE ${kritaqimageioexport_SOURCES}) target_link_libraries(kritaqimageioexport kritaui kritaimpex) install(TARGETS kritaqimageioexport DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) set(kritaqimageioimport_SOURCES kis_qimageio_import.cpp ) -ki18n_wrap_ui(kritaqimageioimport_SOURCES ) add_library(kritaqimageioimport MODULE ${kritaqimageioimport_SOURCES}) target_link_libraries(kritaqimageioimport kritaui) install(TARGETS kritaqimageioimport DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) install( PROGRAMS krita_qimageio.desktop DESTINATION ${XDG_APPS_INSTALL_DIR}) diff --git a/plugins/impex/qimageio/kis_qimageio_export.cpp b/plugins/impex/qimageio/kis_qimageio_export.cpp index 55db76bcc3..08fa9b96ff 100644 --- a/plugins/impex/qimageio/kis_qimageio_export.cpp +++ b/plugins/impex/qimageio/kis_qimageio_export.cpp @@ -1,69 +1,127 @@ /* * 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_qimageio_export.h" +#include "ui_kis_wdg_options_qimageio.h" #include #include #include #include #include #include #include #include #include #include #include +#include K_PLUGIN_FACTORY_WITH_JSON(KisQImageIOExportFactory, "krita_qimageio_export.json", registerPlugin();) +class KisWdgOptionsQImageIO : public KisConfigWidget, public Ui::KisWdgOptionsQImageIO +{ + Q_OBJECT + +public: + KisWdgOptionsQImageIO(QWidget *parent); + + void setConfiguration(const KisPropertiesConfigurationSP cfg) override; + KisPropertiesConfigurationSP configuration() const override; +}; + +KisWdgOptionsQImageIO::KisWdgOptionsQImageIO(QWidget *parent) + : KisConfigWidget(parent) +{ + setupUi(this); + imageQuality->setRange(1, 100, 0); +} + +KisPropertiesConfigurationSP KisWdgOptionsQImageIO::configuration() const +{ + KisPropertiesConfigurationSP cfg(new KisPropertiesConfiguration()); + + cfg->setProperty("quality", (int)imageQuality->value()); + return cfg; +} + +void KisWdgOptionsQImageIO::setConfiguration(const KisPropertiesConfigurationSP cfg) +{ + imageQuality->setValue(cfg->getInt("quality", 75)); +} + KisQImageIOExport::KisQImageIOExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KisQImageIOExport::~KisQImageIOExport() { } -KisImportExportErrorCode KisQImageIOExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) +KisImportExportErrorCode KisQImageIOExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration) { QRect rc = document->savingImage()->bounds(); QImage image = document->savingImage()->projection()->convertToQImage(0, 0, 0, rc.width(), rc.height(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); - bool result = image.save(io, QFileInfo(filename()).suffix().toLatin1()); + int quality = -1; + if (configuration) { + quality = configuration->getInt("quality", -1); + } + + bool result = image.save(io, QFileInfo(filename()).suffix().toLatin1(), quality); if (result) { return ImportExportCodes::OK; } else { return ImportExportCodes::FileFormatIncorrect; } } void KisQImageIOExport::initializeCapabilities() { QList > supportedColorModels; supportedColorModels << QPair() << QPair(RGBAColorModelID, Integer8BitsColorDepthID); addSupportedColorModels(supportedColorModels, KisMimeDatabase::descriptionForMimeType(mimeType())); addCapability(KisExportCheckRegistry::instance()->get("ColorModelPerLayerCheck/" + RGBAColorModelID.id() + "/" + Integer8BitsColorDepthID.id())->create(KisExportCheckBase::SUPPORTED)); } +KisConfigWidget *KisQImageIOExport::createConfigurationWidget(QWidget *parent, const QByteArray& /*from*/, const QByteArray& /*to*/) const +{ + if (mimeType() == "image/webp") { + return new KisWdgOptionsQImageIO(parent); + } + + return 0; +} + +KisPropertiesConfigurationSP KisQImageIOExport::defaultConfiguration(const QByteArray &, const QByteArray &) const +{ + if (mimeType() == "image/webp") { + KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration(); + // the Qt webp plugin chooses 75 when passing -1 (default argument) to QImage::save() + cfg->setProperty("quality", 75); + return cfg; + } + return 0; +} + #include "kis_qimageio_export.moc" diff --git a/plugins/impex/qimageio/kis_qimageio_export.h b/plugins/impex/qimageio/kis_qimageio_export.h index 423f9876b5..60b80ce90c 100644 --- a/plugins/impex/qimageio/kis_qimageio_export.h +++ b/plugins/impex/qimageio/kis_qimageio_export.h @@ -1,38 +1,40 @@ /* * 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_BMP_EXPORT_H_ #define _KIS_BMP_EXPORT_H_ #include #include class KisQImageIOExport : public KisImportExportFilter { Q_OBJECT public: KisQImageIOExport(QObject *parent, const QVariantList &); ~KisQImageIOExport() override; -public: + KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; + KisConfigWidget *createConfigurationWidget(QWidget *parent, const QByteArray& from, const QByteArray& to) const override; + KisPropertiesConfigurationSP defaultConfiguration(const QByteArray &, const QByteArray &) const override; void initializeCapabilities() override; }; #endif diff --git a/plugins/impex/qimageio/kis_wdg_options_qimageio.ui b/plugins/impex/qimageio/kis_wdg_options_qimageio.ui new file mode 100644 index 0000000000..2e8b763a50 --- /dev/null +++ b/plugins/impex/qimageio/kis_wdg_options_qimageio.ui @@ -0,0 +1,127 @@ + + + KisWdgOptionsQImageIO + + + + 0 + 0 + 489 + 70 + + + + WebP Options + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 6 + + + + + <p>Adjust the compression time. Better compression takes longer. +<br>Note: the compression level does not change the quality of the result.</p> + + + Highest quality + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + <p>Adjust the compression time. Better compression takes longer. +<br>Note: the compression level does not change the quality of the result.</p> + + + Lowest quality + + + + + + + Note: 100 uses lossless compression, everything below is lossy. + + + <p>Adjust image quality. The lower the quality, the more compression artifacts get introduced. <br/>Note: A quality of 100 switches to lossless compression similar to (but often more efficient than) PNG.</p> + + + Quality: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + Note: 100 uses lossless compression, everything below is lossy. + + + <p>Adjust image quality. The lower the quality, the more compression artifacts get introduced. <br/>Note: A quality of 100 switches to lossless compression similar to (but often more efficient than) PNG.</p> + + + + + + + + + Qt::Vertical + + + QSizePolicy::Expanding + + + + 414 + 16 + + + + + + + + + KisDoubleSliderSpinBox + QWidget +
kis_slider_spin_box.h
+ 1 +
+
+ + +
diff --git a/plugins/impex/raw/3rdparty/libkdcraw/data/pics/CMakeLists.txt b/plugins/impex/raw/3rdparty/libkdcraw/data/pics/CMakeLists.txt index 08de01eaef..eafa03fd85 100644 --- a/plugins/impex/raw/3rdparty/libkdcraw/data/pics/CMakeLists.txt +++ b/plugins/impex/raw/3rdparty/libkdcraw/data/pics/CMakeLists.txt @@ -1,8 +1,8 @@ # # Copyright (c) 2015, Shourya Singh Gupta, # # Redistribution and use is allowed according to the terms of the BSD license. # For details see the accompanying COPYING-CMAKE-SCRIPTS file. install(FILES process-working.png - DESTINATION ${DATA_INSTALL_DIR}/libkdcraw/pics) \ No newline at end of file + DESTINATION ${DATA_INSTALL_DIR}/libkdcraw/pics) diff --git a/plugins/python/comics_project_management_tools/comics_metadata_dialog.py b/plugins/python/comics_project_management_tools/comics_metadata_dialog.py index e9ebc923aa..c73b67fd37 100644 --- a/plugins/python/comics_project_management_tools/comics_metadata_dialog.py +++ b/plugins/python/comics_project_management_tools/comics_metadata_dialog.py @@ -1,784 +1,784 @@ """ Copyright (c) 2017 Wolthera van Hövell tot Westerflier This file is part of the Comics Project Management Tools(CPMT). CPMT 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 3 of the License, or (at your option) any later version. CPMT 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 the CPMT. If not, see . """ """ This is a metadata editor that helps out setting the proper metadata """ import sys import os # For finding the script location. import csv import re import types from pathlib import Path # For reading all the files in a directory. from PyQt5.QtGui import QStandardItem, QStandardItemModel, QImage, QIcon, QPixmap, QPainter, QPalette, QFontDatabase from PyQt5.QtWidgets import QComboBox, QCompleter, QStyledItemDelegate, QLineEdit, QDialog, QDialogButtonBox, QVBoxLayout, QFormLayout, QTabWidget, QWidget, QPlainTextEdit, QHBoxLayout, QSpinBox, QDateEdit, QPushButton, QLabel, QTableView from PyQt5.QtCore import QDir, QLocale, QStringListModel, Qt, QDate, QSize, QUuid """ multi entry completer cobbled together from the two examples on stackoverflow:3779720 This allows us to let people type in comma-separated lists and get completion for those. """ class multi_entry_completer(QCompleter): punctuation = "," def __init__(self, parent=None): super(QCompleter, self).__init__(parent) def pathFromIndex(self, index): path = QCompleter.pathFromIndex(self, index) string = str(self.widget().text()) split = string.split(self.punctuation) if len(split) > 1: path = "%s, %s" % (",".join(split[:-1]), path) return path def splitPath(self, path): split = str(path.split(self.punctuation)[-1]) if split.startswith(" "): split = split[1:] if split.endswith(" "): split = split[:-1] return [split] """ Language combobox that can take locale codes and get the right language for it and visa-versa. """ class language_combo_box(QComboBox): languageList = [] codesList = [] def __init__(self, parent=None): super(QComboBox, self).__init__(parent) for i in range(1, 357): locale = QLocale(i) if locale and QLocale.languageToString(locale.language()) != "C": codeName = locale.name().split("_")[0] if codeName not in self.codesList: self.codesList.append(codeName) self.codesList.sort() for lang in self.codesList: locale = QLocale(lang) if locale: languageName = QLocale.languageToString(locale.language()) self.languageList.append(languageName.title()) self.setIconSize(QSize(32, 22)) codeIcon = QImage(self.iconSize(), QImage.Format_ARGB32) painter = QPainter(codeIcon) painter.setBrush(Qt.transparent) codeIcon.fill(Qt.transparent) font = QFontDatabase().systemFont(QFontDatabase.FixedFont) painter.setFont(font) painter.setPen(self.palette().color(QPalette.Text)) painter.drawText(codeIcon.rect(), Qt.AlignCenter,lang) painter.end() self.addItem(QIcon(QPixmap.fromImage(codeIcon)), languageName.title()) def codeForCurrentEntry(self): if self.currentText() in self.languageList: return self.codesList[self.languageList.index(self.currentText())] def setEntryToCode(self, code): if (code == "C" and "en" in self.codesList): self.setCurrentIndex(self.codesList.index("en")) if code in self.codesList: self.setCurrentIndex(self.codesList.index(code)) class country_combo_box(QComboBox): countryList = [] codesList = [] def __init__(self, parent=None): super(QComboBox, self).__init__(parent) def set_country_for_locale(self, languageCode): self.clear() self.codesList = [] self.countryList = [] for locale in QLocale.matchingLocales(QLocale(languageCode).language(), QLocale.AnyScript, QLocale.AnyCountry): codeName = locale.name().split("_")[-1] if codeName not in self.codesList: self.codesList.append(codeName) self.codesList.sort() for country in self.codesList: locale = QLocale(languageCode+"-"+country) if locale: countryName = locale.nativeCountryName() self.countryList.append(countryName.title()) self.setIconSize(QSize(32, 22)) codeIcon = QImage(self.iconSize(), QImage.Format_ARGB32) painter = QPainter(codeIcon) painter.setBrush(Qt.transparent) codeIcon.fill(Qt.transparent) font = QFontDatabase().systemFont(QFontDatabase.FixedFont) painter.setFont(font) painter.setPen(self.palette().color(QPalette.Text)) painter.drawText(codeIcon.rect(), Qt.AlignCenter,country) painter.end() self.addItem(QIcon(QPixmap.fromImage(codeIcon)), countryName.title()) def codeForCurrentEntry(self): if self.currentText() in self.countryList: return self.codesList[self.countryList.index(self.currentText())] def setEntryToCode(self, code): if code == "C": self.setCurrentIndex(0) elif code in self.codesList: self.setCurrentIndex(self.codesList.index(code)) """ A combobox that fills up with licenses from a CSV, and also sets tooltips from that csv. """ class license_combo_box(QComboBox): def __init__(self, parent=None): super(QComboBox, self).__init__(parent) mainP = os.path.dirname(__file__) languageP = os.path.join(mainP, "LicenseList.csv") model = QStandardItemModel() if (os.path.exists(languageP)): file = open(languageP, "r", newline="", encoding="utf8") languageReader = csv.reader(file) for row in languageReader: license = QStandardItem(row[0]) license.setToolTip(row[1]) model.appendRow(license) file.close() self.setModel(model) """ Allows us to set completers on the author roles. """ class author_delegate(QStyledItemDelegate): completerStrings = [] completerColumn = 0 languageColumn = 0 def __init__(self, parent=None): super(QStyledItemDelegate, self).__init__(parent) def setCompleterData(self, completerStrings=[str()], completerColumn=0): self.completerStrings = completerStrings self.completerColumn = completerColumn def setLanguageData(self, languageColumn=0): self.languageColumn = languageColumn def createEditor(self, parent, option, index): if index.column() != self.languageColumn: editor = QLineEdit(parent) else: editor = QComboBox(parent) editor.addItem("") for i in range(2, 356): if QLocale(i, QLocale.AnyScript, QLocale.AnyCountry) is not None: languagecode = QLocale(i, QLocale.AnyScript, QLocale.AnyCountry).name().split("_")[0] if languagecode != "C": editor.addItem(languagecode) editor.model().sort(0) if index.column() == self.completerColumn: editor.setCompleter(QCompleter(self.completerStrings)) editor.completer().setCaseSensitivity(False) return editor """ A comic project metadata editing dialog that can take our config diactionary and set all the relevant information. To help our user, the dialog loads up lists of keywords to populate several autocompletion methods. """ class comic_meta_data_editor(QDialog): configGroup = "ComicsProjectManagementTools" # Translatable genre dictionary that has it's translated entries added to the genrelist and from which the untranslated items are taken. acbfGenreList = {"science_fiction": str(i18n("Science Fiction")), "fantasy": str(i18n("Fantasy")), "adventure": str(i18n("Adventure")), "horror": str(i18n("Horror")), "mystery": str(i18n("Mystery")), "crime": str(i18n("Crime")), "military": str(i18n("Military")), "real_life": str(i18n("Real Life")), "superhero": str(i18n("Superhero")), "humor": str(i18n("Humor")), "western": str(i18n("Western")), "manga": str(i18n("Manga")), "politics": str(i18n("Politics")), "caricature": str(i18n("Caricature")), "sports": str(i18n("Sports")), "history": str(i18n("History")), "biography": str(i18n("Biography")), "education": str(i18n("Education")), "computer": str(i18n("Computer")), "religion": str(i18n("Religion")), "romance": str(i18n("Romance")), "children": str(i18n("Children")), "non-fiction": str(i18n("Non Fiction")), "adult": str(i18n("Adult")), "alternative": str(i18n("Alternative")), "artbook": str(i18n("Artbook")), "other": str(i18n("Other"))} acbfAuthorRolesList = {"Writer": str(i18n("Writer")), "Adapter": str(i18n("Adapter")), "Artist": str(i18n("Artist")), "Penciller": str(i18n("Penciller")), "Inker": str(i18n("Inker")), "Colorist": str(i18n("Colorist")), "Letterer": str(i18n("Letterer")), "Cover Artist": str(i18n("Cover Artist")), "Photographer": str(i18n("Photographer")), "Editor": str(i18n("Editor")), "Assistant Editor": str(i18n("Assistant Editor")), "Designer": str(i18n("Designer")), "Translator": str(i18n("Translator")), "Other": str(i18n("Other"))} def __init__(self): super().__init__() # Get the keys for the autocompletion. self.genreKeysList = [] self.characterKeysList = [] self.ratingKeysList = {} self.formatKeysList = [] self.otherKeysList = [] self.authorRoleList = [] for g in self.acbfGenreList.values(): self.genreKeysList.append(g) for r in self.acbfAuthorRolesList.values(): self.authorRoleList.append(r) mainP = Path(os.path.abspath(__file__)).parent self.get_auto_completion_keys(mainP) extraKeyP = Path(QDir.homePath()) / Application.readSetting(self.configGroup, "extraKeysLocation", str()) self.get_auto_completion_keys(extraKeyP) # Setup the dialog. self.setLayout(QVBoxLayout()) mainWidget = QTabWidget() self.layout().addWidget(mainWidget) self.setWindowTitle(i18n("Comic Metadata")) buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.layout().addWidget(buttons) buttons.accepted.connect(self.accept) buttons.rejected.connect(self.reject) # Title, concept, summary, genre, characters, format, rating, language, series, other keywords metadataPage = QWidget() mformLayout = QFormLayout() metadataPage.setLayout(mformLayout) self.lnTitle = QLineEdit() self.lnTitle.setToolTip(i18n("The proper title of the comic.")) self.teSummary = QPlainTextEdit() self.teSummary.setToolTip(i18n("What will you tell others to entice them to read your comic?")) self.lnGenre = QLineEdit() genreCompletion = multi_entry_completer() genreCompletion.setModel(QStringListModel(self.genreKeysList)) self.lnGenre.setCompleter(genreCompletion) genreCompletion.setCaseSensitivity(False) self.lnGenre.setToolTip(i18n("The genre of the work. Prefilled values are from the ACBF, but you can fill in your own. Separate genres with commas. Try to limit the amount to about two or three.")) self.lnCharacters = QLineEdit() characterCompletion = multi_entry_completer() characterCompletion.setModel(QStringListModel(self.characterKeysList)) characterCompletion.setCaseSensitivity(False) characterCompletion.setFilterMode(Qt.MatchContains) # So that if there is a list of names with last names, people can type in a last name. self.lnCharacters.setCompleter(characterCompletion) self.lnCharacters.setToolTip(i18n("The names of the characters that this comic revolves around. Comma-separated.")) self.lnFormat = QLineEdit() formatCompletion = multi_entry_completer() formatCompletion.setModel(QStringListModel(self.formatKeysList)) formatCompletion.setCaseSensitivity(False) self.lnFormat.setCompleter(formatCompletion) ratingLayout = QHBoxLayout() self.cmbRatingSystem = QComboBox() self.cmbRatingSystem.addItems(self.ratingKeysList.keys()) self.cmbRatingSystem.setEditable(True) self.cmbRating = QComboBox() self.cmbRating.setEditable(True) self.cmbRatingSystem.currentIndexChanged.connect(self.slot_refill_ratings) ratingLayout.addWidget(self.cmbRatingSystem) ratingLayout.addWidget(self.cmbRating) self.lnSeriesName = QLineEdit() self.lnSeriesName.setToolTip(i18n("If this is part of a series, enter the name of the series and the number.")) self.spnSeriesNumber = QSpinBox() self.spnSeriesNumber.setPrefix(i18n("No. ")) self.spnSeriesVol = QSpinBox() self.spnSeriesVol.setPrefix(i18n("Vol. ")) seriesLayout = QHBoxLayout() seriesLayout.addWidget(self.lnSeriesName) seriesLayout.addWidget(self.spnSeriesVol) seriesLayout.addWidget(self.spnSeriesNumber) otherCompletion = multi_entry_completer() otherCompletion.setModel(QStringListModel(self.otherKeysList)) otherCompletion.setCaseSensitivity(False) otherCompletion.setFilterMode(Qt.MatchContains) self.lnOtherKeywords = QLineEdit() self.lnOtherKeywords.setCompleter(otherCompletion) self.lnOtherKeywords.setToolTip(i18n("Other keywords that do not fit in the previously mentioned sets. As always, comma-separated.")) self.cmbLanguage = language_combo_box() self.cmbCountry = country_combo_box() self.cmbLanguage.currentIndexChanged.connect(self.slot_update_countries) self.cmbReadingMode = QComboBox() self.cmbReadingMode.addItem(i18n("Left to Right")) self.cmbReadingMode.addItem(i18n("Right to Left")) self.cmbCoverPage = QComboBox() self.cmbCoverPage.setToolTip(i18n("Which page is the cover page? This will be empty if there are no pages.")) mformLayout.addRow(i18n("Title:"), self.lnTitle) mformLayout.addRow(i18n("Cover page:"), self.cmbCoverPage) mformLayout.addRow(i18n("Summary:"), self.teSummary) mformLayout.addRow(i18n("Language:"), self.cmbLanguage) mformLayout.addRow("", self.cmbCountry) mformLayout.addRow(i18n("Reading direction:"), self.cmbReadingMode) mformLayout.addRow(i18n("Genre:"), self.lnGenre) mformLayout.addRow(i18n("Characters:"), self.lnCharacters) mformLayout.addRow(i18n("Format:"), self.lnFormat) mformLayout.addRow(i18n("Rating:"), ratingLayout) mformLayout.addRow(i18n("Series:"), seriesLayout) mformLayout.addRow(i18n("Other:"), self.lnOtherKeywords) mainWidget.addTab(metadataPage, i18n("Work")) # The page for the authors. authorPage = QWidget() authorPage.setLayout(QVBoxLayout()) explanation = QLabel(i18n("The following is a table of the authors that contributed to this comic. You can set their nickname, proper names (first, middle, last), role (penciller, inker, etc), email and homepage.")) explanation.setWordWrap(True) self.authorModel = QStandardItemModel(0, 8) labels = [i18n("Nick Name"), i18n("Given Name"), i18n("Middle Name"), i18n("Family Name"), i18n("Role"), i18n("Email"), i18n("Homepage"), i18n("Language")] self.authorModel.setHorizontalHeaderLabels(labels) self.authorTable = QTableView() self.authorTable.setModel(self.authorModel) self.authorTable.verticalHeader().setDragEnabled(True) self.authorTable.verticalHeader().setDropIndicatorShown(True) self.authorTable.verticalHeader().setSectionsMovable(True) self.authorTable.verticalHeader().sectionMoved.connect(self.slot_reset_author_row_visual) delegate = author_delegate() delegate.setCompleterData(self.authorRoleList, 4) delegate.setLanguageData(len(labels) - 1) self.authorTable.setItemDelegate(delegate) author_button_layout = QWidget() author_button_layout.setLayout(QHBoxLayout()) btn_add_author = QPushButton(i18n("Add Author")) btn_add_author.clicked.connect(self.slot_add_author) btn_remove_author = QPushButton(i18n("Remove Author")) btn_remove_author.clicked.connect(self.slot_remove_author) author_button_layout.layout().addWidget(btn_add_author) author_button_layout.layout().addWidget(btn_remove_author) authorPage.layout().addWidget(explanation) authorPage.layout().addWidget(self.authorTable) authorPage.layout().addWidget(author_button_layout) mainWidget.addTab(authorPage, i18n("Authors")) # The page with publisher information. publisherPage = QWidget() publisherLayout = QFormLayout() publisherPage.setLayout(publisherLayout) self.publisherName = QLineEdit() self.publisherName.setToolTip(i18n("The name of the company, group or person who is responsible for the final version the reader gets.")) publishDateLayout = QHBoxLayout() self.publishDate = QDateEdit() self.publishDate.setDisplayFormat(QLocale().system().dateFormat()) currentDate = QPushButton(i18n("Set Today")) currentDate.setToolTip(i18n("Sets the publish date to the current date.")) currentDate.clicked.connect(self.slot_set_date) publishDateLayout.addWidget(self.publishDate) publishDateLayout.addWidget(currentDate) self.publishCity = QLineEdit() self.publishCity.setToolTip(i18n("Traditional publishers are always mentioned in source with the city they are located.")) self.isbn = QLineEdit() self.license = license_combo_box() # Maybe ought to make this a QLineEdit... self.license.setEditable(True) self.license.completer().setCompletionMode(QCompleter.PopupCompletion) dataBaseReference = QVBoxLayout() self.ln_database_name = QLineEdit() self.ln_database_name.setToolTip(i18n("If there is an entry in a comics data base, that should be added here. It is unlikely to be a factor for comics from scratch, but useful when doing a conversion.")) self.cmb_entry_type = QComboBox() self.cmb_entry_type.addItems(["IssueID", "SeriesID", "URL"]) self.cmb_entry_type.setEditable(True) self.ln_source = QLineEdit() - self.ln_source.setToolTip(i18n("Whether the comic is an adaption of an existing source, and if so, how to find information about that source. So for example, for an adapted webcomic, the official website url should go here.")) + self.ln_source.setToolTip(i18n("Whether the comic is an adaptation of an existing source, and if so, how to find information about that source. So for example, for an adapted webcomic, the official website url should go here.")) self.label_uuid = QLabel() self.label_uuid.setToolTip(i18n("By default this will be filled with a generated universal unique identifier. The ID by itself is merely so that comic book library management programs can figure out if this particular comic is already in their database and whether it has been rated. Of course, the UUID can be changed into something else by manually changing the JSON, but this is advanced usage.")) self.ln_database_entry = QLineEdit() dbHorizontal = QHBoxLayout() dbHorizontal.addWidget(self.ln_database_name) dbHorizontal.addWidget(self.cmb_entry_type) dataBaseReference.addLayout(dbHorizontal) dataBaseReference.addWidget(self.ln_database_entry) publisherLayout.addRow(i18n("Name:"), self.publisherName) publisherLayout.addRow(i18n("City:"), self.publishCity) publisherLayout.addRow(i18n("Date:"), publishDateLayout) publisherLayout.addRow(i18n("ISBN:"), self.isbn) publisherLayout.addRow(i18n("Source:"), self.ln_source) publisherLayout.addRow(i18n("UUID:"), self.label_uuid) publisherLayout.addRow(i18n("License:"), self.license) publisherLayout.addRow(i18n("Database:"), dataBaseReference) mainWidget.addTab(publisherPage, i18n("Publisher")) """ Ensure that the drag and drop of authors doesn't mess up the labels. """ def slot_reset_author_row_visual(self): headerLabelList = [] for i in range(self.authorTable.verticalHeader().count()): headerLabelList.append(str(i)) for i in range(self.authorTable.verticalHeader().count()): logicalI = self.authorTable.verticalHeader().logicalIndex(i) headerLabelList[logicalI] = str(i + 1) self.authorModel.setVerticalHeaderLabels(headerLabelList) """ Set the publish date to the current date. """ def slot_set_date(self): self.publishDate.setDate(QDate().currentDate()) def slot_update_countries(self): code = self.cmbLanguage.codeForCurrentEntry() self.cmbCountry.set_country_for_locale(code) """ Append keys to autocompletion lists from the directory mainP. """ def get_auto_completion_keys(self, mainP=Path()): genre = Path(mainP / "key_genre") characters = Path(mainP / "key_characters") rating = Path(mainP / "key_rating") format = Path(mainP / "key_format") keywords = Path(mainP / "key_other") authorRole = Path(mainP / "key_author_roles") if genre.exists(): for t in list(genre.glob('**/*.txt')): file = open(str(t), "r", errors="replace") for l in file: if str(l).strip("\n") not in self.genreKeysList: self.genreKeysList.append(str(l).strip("\n")) file.close() if characters.exists(): for t in list(characters.glob('**/*.txt')): file = open(str(t), "r", errors="replace") for l in file: if str(l).strip("\n") not in self.characterKeysList: self.characterKeysList.append(str(l).strip("\n")) file.close() if format.exists(): for t in list(format.glob('**/*.txt')): file = open(str(t), "r", errors="replace") for l in file: if str(l).strip("\n") not in self.formatKeysList: self.formatKeysList.append(str(l).strip("\n")) file.close() if rating.exists(): for t in list(rating.glob('**/*.csv')): file = open(str(t), "r", newline="", encoding="utf-8") ratings = csv.reader(file) title = os.path.basename(str(t)) r = 0 for row in ratings: listItem = [] if r is 0: title = row[1] else: listItem = self.ratingKeysList[title] item = [] item.append(row[0]) item.append(row[1]) listItem.append(item) self.ratingKeysList[title] = listItem r += 1 file.close() if keywords.exists(): for t in list(keywords.glob('**/*.txt')): file = open(str(t), "r", errors="replace") for l in file: if str(l).strip("\n") not in self.otherKeysList: self.otherKeysList.append(str(l).strip("\n")) file.close() if authorRole.exists(): for t in list(authorRole.glob('**/*.txt')): file = open(str(t), "r", errors="replace") for l in file: if str(l).strip("\n") not in self.authorRoleList: self.authorRoleList.append(str(l).strip("\n")) file.close() """ Refill the ratings box. This is called whenever the rating system changes. """ def slot_refill_ratings(self): if self.cmbRatingSystem.currentText() in self.ratingKeysList.keys(): self.cmbRating.clear() model = QStandardItemModel() for i in self.ratingKeysList[self.cmbRatingSystem.currentText()]: item = QStandardItem() item.setText(i[0]) item.setToolTip(i[1]) model.appendRow(item) self.cmbRating.setModel(model) """ Add an author with default values initialised. """ def slot_add_author(self): listItems = [] listItems.append(QStandardItem(i18n("Anon"))) # Nick name listItems.append(QStandardItem(i18n("John"))) # First name listItems.append(QStandardItem()) # Middle name listItems.append(QStandardItem(i18n("Doe"))) # Last name listItems.append(QStandardItem()) # role listItems.append(QStandardItem()) # email listItems.append(QStandardItem()) # homepage language = QLocale.system().name().split("_")[0] if language == "C": language = "en" listItems.append(QStandardItem(language)) # Language self.authorModel.appendRow(listItems) """ Remove the selected author from the author list. """ def slot_remove_author(self): self.authorModel.removeRow(self.authorTable.currentIndex().row()) """ Load the UI values from the config dictionary given. """ def setConfig(self, config): if "title" in config.keys(): self.lnTitle.setText(config["title"]) self.teSummary.clear() if "pages" in config.keys(): self.cmbCoverPage.clear() for page in config["pages"]: self.cmbCoverPage.addItem(page) if "cover" in config.keys(): if config["cover"] in config["pages"]: self.cmbCoverPage.setCurrentText(config["cover"]) if "summary" in config.keys(): self.teSummary.appendPlainText(config["summary"]) if "genre" in config.keys(): genreList = [] genreListConf = config["genre"] totalMatch = 100 if isinstance(config["genre"], dict): genreListConf = config["genre"].keys() totalMatch = 0 for genre in genreListConf: genreKey = genre if genre in self.acbfGenreList: genreKey = self.acbfGenreList[genre] if isinstance(config["genre"], dict): genreValue = config["genre"][genre] if genreValue > 0: genreKey = str(genreKey + "(" + str(genreValue) + ")") genreList.append(genreKey) self.lnGenre.setText(", ".join(genreList)) if "characters" in config.keys(): self.lnCharacters.setText(", ".join(config["characters"])) if "format" in config.keys(): self.lnFormat.setText(", ".join(config["format"])) if "rating" in config.keys(): self.cmbRating.setCurrentText(config["rating"]) else: self.cmbRating.setCurrentText("") if "ratingSystem" in config.keys(): self.cmbRatingSystem.setCurrentText(config["ratingSystem"]) else: self.cmbRatingSystem.setCurrentText("") if "otherKeywords" in config.keys(): self.lnOtherKeywords.setText(", ".join(config["otherKeywords"])) if "seriesName" in config.keys(): self.lnSeriesName.setText(config["seriesName"]) if "seriesVolume" in config.keys(): self.spnSeriesVol.setValue(config["seriesVolume"]) if "seriesNumber" in config.keys(): self.spnSeriesNumber.setValue(config["seriesNumber"]) if "language" in config.keys(): code = config["language"] if "_" in code: self.cmbLanguage.setEntryToCode(code.split("_")[0]) self.cmbCountry.setEntryToCode(code.split("_")[-1]) elif "-" in code: self.cmbLanguage.setEntryToCode(code.split("-")[0]) self.cmbCountry.setEntryToCode(code.split("-")[-1]) else: self.cmbLanguage.setEntryToCode(code) if "readingDirection" in config.keys(): if config["readingDirection"] is "leftToRight": self.cmbReadingMode.setCurrentIndex(int(Qt.LeftToRight)) else: self.cmbReadingMode.setCurrentIndex(int(Qt.RightToLeft)) else: self.cmbReadingMode.setCurrentIndex(QLocale(self.cmbLanguage.codeForCurrentEntry()).textDirection()) if "publisherName" in config.keys(): self.publisherName.setText(config["publisherName"]) if "publisherCity" in config.keys(): self.publishCity.setText(config["publisherCity"]) if "publishingDate" in config.keys(): self.publishDate.setDate(QDate.fromString(config["publishingDate"], Qt.ISODate)) if "isbn-number" in config.keys(): self.isbn.setText(config["isbn-number"]) if "source" in config.keys(): self.ln_source.setText(config["source"]) elif "acbfSource" in config.keys(): self.ln_source.setText(config["acbfSource"]) if "uuid" in config.keys(): self.label_uuid.setText(config["uuid"]) else: uuid = str() if "acbfID" in config.keys(): uuid = config["acbfID"] uuid = uuid.strip("{") uuid = uuid.strip("}") uuidVerify = uuid.split("-") if len(uuidVerify[0])!=8 or len(uuidVerify[1])!=4 or len(uuidVerify[2])!=4 or len(uuidVerify[3])!=4 or len(uuidVerify[4])!=12: uuid = QUuid.createUuid().toString() self.label_uuid.setText(uuid) config["uuid"] = uuid if "license" in config.keys(): self.license.setCurrentText(config["license"]) else: self.license.setCurrentText("") # I would like to keep it ambiguous whether the artist has thought about the license or not. if "authorList" in config.keys(): authorList = config["authorList"] for i in range(len(authorList)): author = authorList[i] if len(author.keys()) > 0: listItems = [] listItems = [] listItems.append(QStandardItem(author.get("nickname", ""))) listItems.append(QStandardItem(author.get("first-name", ""))) listItems.append(QStandardItem(author.get("initials", ""))) listItems.append(QStandardItem(author.get("last-name", ""))) role = author.get("role", "") if role in self.acbfAuthorRolesList.keys(): role = self.acbfAuthorRolesList[role] listItems.append(QStandardItem(role)) listItems.append(QStandardItem(author.get("email", ""))) listItems.append(QStandardItem(author.get("homepage", ""))) listItems.append(QStandardItem(author.get("language", ""))) self.authorModel.appendRow(listItems) else: self.slot_add_author() dbRef = config.get("databaseReference", {}) self.ln_database_name.setText(dbRef.get("name", "")) self.ln_database_entry.setText(dbRef.get("entry", "")) stringCmbEntryType = self.cmb_entry_type.itemText(0) self.cmb_entry_type.setCurrentText(dbRef.get("type", stringCmbEntryType)) """ Store the GUI values into the config dictionary given. @return the config diactionary filled with new values. """ def getConfig(self, config): text = self.lnTitle.text() if len(text) > 0 and text.isspace() is False: config["title"] = text elif "title" in config.keys(): config.pop("title") config["cover"] = self.cmbCoverPage.currentText() listkeys = self.lnGenre.text() if len(listkeys) > 0 and listkeys.isspace() is False: preSplit = self.lnGenre.text().split(",") genreMatcher = re.compile(r'\((\d+)\)') genreList = {} totalValue = 0 for key in preSplit: m = genreMatcher.search(key) if m: genre = str(genreMatcher.sub("", key)).strip() match = int(m.group()[:-1][1:]) else: genre = key.strip() match = 0 if genre in self.acbfGenreList.values(): i = list(self.acbfGenreList.values()).index(genre) genreList[list(self.acbfGenreList.keys())[i]] = match else: genreList[genre] = match totalValue += match # Normalize the values: for key in genreList.keys(): if genreList[key] > 0: genreList[key] = round(genreList[key] / totalValue * 100) config["genre"] = genreList elif "genre" in config.keys(): config.pop("genre") listkeys = self.lnCharacters.text() if len(listkeys) > 0 and listkeys.isspace() is False: config["characters"] = self.lnCharacters.text().split(", ") elif "characters" in config.keys(): config.pop("characters") listkeys = self.lnFormat.text() if len(listkeys) > 0 and listkeys.isspace() is False: config["format"] = self.lnFormat.text().split(", ") elif "format" in config.keys(): config.pop("format") config["ratingSystem"] = self.cmbRatingSystem.currentText() config["rating"] = self.cmbRating.currentText() listkeys = self.lnOtherKeywords.text() if len(listkeys) > 0 and listkeys.isspace() is False: config["otherKeywords"] = self.lnOtherKeywords.text().split(", ") elif "otherKeywords" in config.keys(): config.pop("otherKeywords") text = self.teSummary.toPlainText() if len(text) > 0 and text.isspace() is False: config["summary"] = text elif "summary" in config.keys(): config.pop("summary") if len(self.lnSeriesName.text()) > 0: config["seriesName"] = self.lnSeriesName.text() config["seriesNumber"] = self.spnSeriesNumber.value() if self.spnSeriesVol.value() > 0: config["seriesVolume"] = self.spnSeriesVol.value() config["language"] = str(self.cmbLanguage.codeForCurrentEntry()+"-"+self.cmbCountry.codeForCurrentEntry()) if self.cmbReadingMode.currentIndex() is int(Qt.LeftToRight): config["readingDirection"] = "leftToRight" else: config["readingDirection"] = "rightToLeft" authorList = [] for row in range(self.authorTable.verticalHeader().count()): logicalIndex = self.authorTable.verticalHeader().logicalIndex(row) listEntries = ["nickname", "first-name", "initials", "last-name", "role", "email", "homepage", "language"] author = {} for i in range(len(listEntries)): entry = self.authorModel.data(self.authorModel.index(logicalIndex, i)) if entry is None: entry = " " if entry.isspace() is False and len(entry) > 0: if listEntries[i] == "role": if entry in self.acbfAuthorRolesList.values(): entryI = list(self.acbfAuthorRolesList.values()).index(entry) entry = list(self.acbfAuthorRolesList.keys())[entryI] author[listEntries[i]] = entry elif listEntries[i] in author.keys(): author.pop(listEntries[i]) authorList.append(author) config["authorList"] = authorList config["publisherName"] = self.publisherName.text() config["publisherCity"] = self.publishCity.text() config["publishingDate"] = self.publishDate.date().toString(Qt.ISODate) config["isbn-number"] = self.isbn.text() config["source"] = self.ln_source.text() config["license"] = self.license.currentText() if self.ln_database_name.text().isalnum() and self.ln_database_entry.text().isalnum(): dbRef = {} dbRef["name"] = self.ln_database_name.text() dbRef["entry"] = self.ln_database_entry.text() dbRef["type"] = self.cmb_entry_type.currentText() config["databaseReference"] = dbRef return config diff --git a/plugins/python/plugin_importer/tests/test_plugin_importer.py b/plugins/python/plugin_importer/tests/test_plugin_importer.py index f402cba1e3..5330a6b7d5 100644 --- a/plugins/python/plugin_importer/tests/test_plugin_importer.py +++ b/plugins/python/plugin_importer/tests/test_plugin_importer.py @@ -1,300 +1,300 @@ """Unit tests for the plugin_importer module. Unit tests can either be toplevel functions whose names start with `test_`, or they can be class methods on classes derived from `unittest.TestCase`; again the method names need to start with `test_`. """ import os import pytest from tempfile import TemporaryDirectory from unittest import mock, TestCase from zipfile import ZipFile from .. plugin_importer import ( NoPluginsFoundException, PluginImporter, PluginReadError, ) class PluginImporterTestCase(TestCase): """Collection of unit tests for the plugin_importer module. Since the tests need a bit of setup for file system operations, we gather them together in a TestCase class. """ def setUp(self): """This method will be run before each test in this class. We create temporary directories for creating and extracting zip files: `resources_dir` will be the destination (a stand-in for Krita's resources dir), `plugin_dir` the source where our plugin zip is located. """ self.resources_dir = TemporaryDirectory() self.plugin_dir = TemporaryDirectory() def tearDown(self): """This method will be run after each test in this class. We remove the temporary directories created in `setUp`. """ self.resources_dir.cleanup() self.plugin_dir.cleanup() @property def zip_filename(self): """Helper method for easy access to the full path of the zipped plugin. """ return os.path.join(self.plugin_dir.name, 'plugin.zip') def zip_plugin(self, dirname): """Helper method to zip a plugin from the subdirectory `dirname` in the `fixtures` folder and store it in the temporary directory `self.plugin_dir`. The `fixtures` directory contains the non-zipped plugins for easier maintenance. """ # Get the full name of the folder this file resides in: testroot = os.path.dirname(os.path.realpath(__file__)) # From that, we can get the full name of the plugin fixture folder: src = os.path.join(testroot, 'fixtures', dirname) # Zip it: with ZipFile(self.zip_filename, 'w') as plugin_zip: for root, dirs, files in os.walk(src): dirname = root.replace(src, '') for filename in files + dirs: plugin_zip.write( filename=os.path.join(root, filename), arcname=os.path.join(dirname, filename)) def assert_in_resources_dir(self, *path): """Helper method to check whether a directory or file exists inside `self.resources_dir`. """ assert os.path.exists(os.path.join(self.resources_dir.name, *path)) def assert_not_in_resources_dir(self, *path): """Helper method to check whether a directory or file doesn't exist inside `self.resources_dir`. """ assert not os.path.exists(os.path.join(self.resources_dir.name, *path)) ############################################################ # The actual tests start below def test_zipfile_doesnt_exist(self): """Test: Import a filename that doesn't exist.""" # Create nothing here... with pytest.raises(PluginReadError): # We expect an exception PluginImporter(self.zip_filename, self.resources_dir.name, lambda x: True) def test_zipfile_not_a_zip(self): """Test: Import a file that isn't a zip file.""" # Create a text file: with open(self.zip_filename, 'w') as f: f.write('foo') with pytest.raises(PluginReadError): # We expect an exception PluginImporter(self.zip_filename, self.resources_dir.name, lambda x: True) def test_simple_plugin_success(self): """Test: Import a basic plugin.""" self.zip_plugin('success_simple') importer = PluginImporter(self.zip_filename, self.resources_dir.name, lambda x: True) imported = importer.import_all() assert len(imported) == 1 self.assert_in_resources_dir('pykrita', 'foo') self.assert_in_resources_dir('pykrita', 'foo', '__init__.py') self.assert_in_resources_dir('pykrita', 'foo', 'foo.py') self.assert_in_resources_dir('pykrita', 'foo.desktop') self.assert_in_resources_dir('actions', 'foo.action') def test_toplevel_plugin_success(self): """Test: Import a plugin with everything at toplevel.""" self.zip_plugin('success_toplevel') importer = PluginImporter(self.zip_filename, self.resources_dir.name, lambda x: True) imported = importer.import_all() assert len(imported) == 1 self.assert_in_resources_dir('pykrita', 'foo') self.assert_in_resources_dir('pykrita', 'foo', '__init__.py') self.assert_in_resources_dir('pykrita', 'foo', 'foo.py') self.assert_in_resources_dir('pykrita', 'foo.desktop') self.assert_in_resources_dir('actions', 'foo.action') def test_nested_plugin_success(self): """Test: Import a plugin with nested directories.""" self.zip_plugin('success_nested') importer = PluginImporter(self.zip_filename, self.resources_dir.name, lambda x: True) imported = importer.import_all() assert len(imported) == 1 self.assert_in_resources_dir('pykrita', 'foo') self.assert_in_resources_dir('pykrita', 'foo', '__init__.py') self.assert_in_resources_dir('pykrita', 'foo', 'foo.py') self.assert_in_resources_dir('pykrita', 'foo.desktop') self.assert_in_resources_dir('actions', 'foo.action') def test_no_action_success(self): """Test: Import a plugin without action file. Should import fine without creating an action file.""" self.zip_plugin('success_no_action') importer = PluginImporter(self.zip_filename, self.resources_dir.name, lambda x: True) imported = importer.import_all() assert len(imported) == 1 self.assert_in_resources_dir('pykrita', 'foo') self.assert_in_resources_dir('pykrita', 'foo', '__init__.py') self.assert_in_resources_dir('pykrita', 'foo', 'foo.py') self.assert_in_resources_dir('pykrita', 'foo.desktop') self.assert_not_in_resources_dir('actions', 'foo.action') def test_overwrite_existing(self): """Test: Overwrite an existing plugin when overwrite confirmed.""" self.zip_plugin('success_simple') - # Create an existing python module in the the resources directory: + # Create an existing python module in the resources directory: plugin_dir = os.path.join(self.resources_dir.name, 'pykrita', 'foo') os.makedirs(plugin_dir) init_file = os.path.join(plugin_dir, '__init__.py') with open(init_file, 'w') as f: f.write('# existing module') # Create a mock callback on which we can test that it has been called: confirm_callback = mock.MagicMock(return_value=True) importer = PluginImporter(self.zip_filename, self.resources_dir.name, confirm_callback) imported = importer.import_all() assert len(imported) == 1 assert confirm_callback.called # Existing plugin should be overwritten: with open(init_file, 'r') as f: assert f.read().strip() == "print('hello')" def test_dont_overwrite_existing(self): """Test: Don't overwrite an existing plugin when overwrite not confirmed.""" self.zip_plugin('success_simple') - # Create an existing python module in the the resources directory: + # Create an existing python module in the resources directory: plugin_dir = os.path.join(self.resources_dir.name, 'pykrita', 'foo') os.makedirs(plugin_dir) init_file = os.path.join(plugin_dir, '__init__.py') with open(init_file, 'w') as f: f.write('# existing module') # Create a mock callback on which we can test that it has been called: confirm_callback = mock.MagicMock(return_value=False) importer = PluginImporter(self.zip_filename, self.resources_dir.name, confirm_callback) imported = importer.import_all() assert len(imported) == 0 assert confirm_callback.called # Existing plugin should not be overwritten: with open(init_file, 'r') as f: assert f.read().strip() == '# existing module' def test_missing_desktop_file(self): """Test: Import plugin without a desktop file.""" self.zip_plugin('fail_no_desktop_file') importer = PluginImporter(self.zip_filename, self.resources_dir.name, lambda x: True) with pytest.raises(NoPluginsFoundException): # We expect an exception importer.import_all() def test_unparsable_desktop_file(self): """Test: Import plugin whose desktop file is not parsable.""" self.zip_plugin('fail_unparsable_desktop_file') importer = PluginImporter(self.zip_filename, self.resources_dir.name, lambda x: True) with pytest.raises(PluginReadError): # We expect an exception importer.import_all() def test_missing_keys_in_desktop_file(self): """Test: Import plugin whose destkop file is missing needed keys.""" self.zip_plugin('fail_missing_keys_desktop_file') importer = PluginImporter(self.zip_filename, self.resources_dir.name, lambda x: True) with pytest.raises(PluginReadError): # We expect an exception importer.import_all() def test_no_matching_plugindir(self): """Test: Import plugin whose destkop file is missing needed keys.""" self.zip_plugin('fail_no_matching_plugindir') importer = PluginImporter(self.zip_filename, self.resources_dir.name, lambda x: True) with pytest.raises(NoPluginsFoundException): # We expect an exception importer.import_all() def test_no_init_file(self): """Test: Import plugin whose python module is missing the __init__.py file.""" self.zip_plugin('fail_no_init_file') importer = PluginImporter(self.zip_filename, self.resources_dir.name, lambda x: True) with pytest.raises(NoPluginsFoundException): # We expect an exception importer.import_all() def test_unparsable_action_file(self): """Test: Import plugin whose action file isn't parsable.""" self.zip_plugin('fail_unparsable_action_file') importer = PluginImporter(self.zip_filename, self.resources_dir.name, lambda x: True) with pytest.raises(PluginReadError): # We expect an exception importer.import_all() diff --git a/plugins/tools/defaulttool/defaulttool/ShapeResizeStrategy.cpp b/plugins/tools/defaulttool/defaulttool/ShapeResizeStrategy.cpp index f247c6213c..29d4b542f2 100644 --- a/plugins/tools/defaulttool/defaulttool/ShapeResizeStrategy.cpp +++ b/plugins/tools/defaulttool/defaulttool/ShapeResizeStrategy.cpp @@ -1,254 +1,253 @@ /* This file is part of the KDE project * Copyright (C) 2006-2007 Thomas Zander * Copyright (C) 2007,2011 Jan Hambrecht * Copyright (C) 2017 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 "ShapeResizeStrategy.h" #include "SelectionDecorator.h" #include #include #include #include #include #include #include #include #include #include #include #include ShapeResizeStrategy::ShapeResizeStrategy(KoToolBase *tool, KoSelection *selection, const QPointF &clicked, KoFlake::SelectionHandle direction, bool forceUniformScalingMode) : KoInteractionStrategy(tool), m_forceUniformScalingMode(forceUniformScalingMode) { KIS_SAFE_ASSERT_RECOVER_RETURN(selection && selection->count() > 0); m_selectedShapes = selection->selectedEditableShapes(); m_start = clicked; KoShape *shape = 0; if (m_selectedShapes.size() > 1) { shape = selection; } else if (m_selectedShapes.size() == 1) { shape = m_selectedShapes.first(); } if (shape) { const qreal w = shape->size().width(); const qreal h = shape->size().height(); switch (direction) { case KoFlake::TopMiddleHandle: m_start = 0.5 * (shape->absolutePosition(KoFlake::TopLeft) + shape->absolutePosition(KoFlake::TopRight)); m_top = true; m_bottom = false; m_left = false; m_right = false; m_globalStillPoint = QPointF(0.5 * w, h); break; case KoFlake::TopRightHandle: m_start = shape->absolutePosition(KoFlake::TopRight); m_top = true; m_bottom = false; m_left = false; m_right = true; m_globalStillPoint = QPointF(0, h); break; case KoFlake::RightMiddleHandle: m_start = 0.5 * (shape->absolutePosition(KoFlake::TopRight) + shape->absolutePosition(KoFlake::BottomRight)); m_top = false; m_bottom = false; m_left = false; m_right = true; m_globalStillPoint = QPointF(0, 0.5 * h); break; case KoFlake::BottomRightHandle: m_start = shape->absolutePosition(KoFlake::BottomRight); m_top = false; m_bottom = true; m_left = false; m_right = true; m_globalStillPoint = QPointF(0, 0); break; case KoFlake::BottomMiddleHandle: m_start = 0.5 * (shape->absolutePosition(KoFlake::BottomRight) + shape->absolutePosition(KoFlake::BottomLeft)); m_top = false; m_bottom = true; m_left = false; m_right = false; m_globalStillPoint = QPointF(0.5 * w, 0); break; case KoFlake::BottomLeftHandle: m_start = shape->absolutePosition(KoFlake::BottomLeft); m_top = false; m_bottom = true; m_left = true; m_right = false; m_globalStillPoint = QPointF(w, 0); break; case KoFlake::LeftMiddleHandle: m_start = 0.5 * (shape->absolutePosition(KoFlake::BottomLeft) + shape->absolutePosition(KoFlake::TopLeft)); m_top = false; m_bottom = false; m_left = true; m_right = false; m_globalStillPoint = QPointF(w, 0.5 * h); break; case KoFlake::TopLeftHandle: m_start = shape->absolutePosition(KoFlake::TopLeft); m_top = true; m_bottom = false; m_left = true; m_right = false; m_globalStillPoint = QPointF(w, h); break; default: Q_ASSERT(0); // illegal 'corner' } const QPointF p0 = shape->outlineRect().topLeft(); m_globalStillPoint = shape->absoluteTransformation(0).map(p0 + m_globalStillPoint); m_globalCenterPoint = shape->absolutePosition(KoFlake::Center); m_unwindMatrix = shape->absoluteTransformation(0).inverted(); m_initialSelectionSize = shape->size(); m_postScalingCoveringTransform = shape->transformation(); } tool->setStatusText(i18n("Press CTRL to resize from center.")); tool->canvas()->snapGuide()->setIgnoredShapes(KoShape::linearizeSubtree(m_selectedShapes)); } ShapeResizeStrategy::~ShapeResizeStrategy() { } void ShapeResizeStrategy::handleMouseMove(const QPointF &point, Qt::KeyboardModifiers modifiers) { tool()->canvas()->updateCanvas(tool()->canvas()->snapGuide()->boundingRect()); QPointF newPos = tool()->canvas()->snapGuide()->snap(point, modifiers); tool()->canvas()->updateCanvas(tool()->canvas()->snapGuide()->boundingRect()); bool keepAspect = modifiers & Qt::ShiftModifier; Q_FOREACH (KoShape *shape, m_selectedShapes) { keepAspect = keepAspect || shape->keepAspectRatio(); } qreal startWidth = m_initialSelectionSize.width(); if (startWidth < std::numeric_limits::epsilon()) { startWidth = std::numeric_limits::epsilon(); } qreal startHeight = m_initialSelectionSize.height(); if (startHeight < std::numeric_limits::epsilon()) { startHeight = std::numeric_limits::epsilon(); } QPointF distance = m_unwindMatrix.map(newPos) - m_unwindMatrix.map(m_start); // guard against resizing zero width shapes, which would result in huge zoom factors if (m_initialSelectionSize.width() < std::numeric_limits::epsilon()) { distance.rx() = 0.0; } // guard against resizing zero height shapes, which would result in huge zoom factors if (m_initialSelectionSize.height() < std::numeric_limits::epsilon()) { distance.ry() = 0.0; } const bool scaleFromCenter = modifiers & Qt::ControlModifier; if (scaleFromCenter) { distance *= 2.0; } qreal newWidth = startWidth; qreal newHeight = startHeight; if (m_left) { newWidth = startWidth - distance.x(); } else if (m_right) { newWidth = startWidth + distance.x(); } if (m_top) { newHeight = startHeight - distance.y(); } else if (m_bottom) { newHeight = startHeight + distance.y(); } /** * Do not let a shape be less than 1px in size in current view * coordinates. If the user wants it to be smaller, he can just * zoom-in a bit. */ QSizeF minViewSize(1.0, 1.0); QSizeF minDocSize = tool()->canvas()->viewConverter()->viewToDocument(minViewSize); if (qAbs(newWidth) < minDocSize.width()) { int sign = newWidth >= 0.0 ? 1 : -1; // zero -> '1' newWidth = sign * minDocSize.width(); } if (qAbs(newHeight) < minDocSize.height()) { int sign = newHeight >= 0.0 ? 1 : -1; // zero -> '1' newHeight = sign * minDocSize.height(); } qreal zoomX = newWidth / startWidth; qreal zoomY = newHeight / startHeight; if (keepAspect) { const bool cornerUsed = ((m_bottom ? 1 : 0) + (m_top ? 1 : 0) + (m_left ? 1 : 0) + (m_right ? 1 : 0)) == 2; if (cornerUsed) { if (startWidth < startHeight) { zoomY = zoomX; } else { zoomX = zoomY; } } else { if (m_left || m_right) { zoomY = qAbs(zoomX); } else { zoomX = qAbs(zoomY); } } } resizeBy(scaleFromCenter ? m_globalCenterPoint : m_globalStillPoint, zoomX, zoomY); } void ShapeResizeStrategy::resizeBy(const QPointF &stillPoint, qreal zoomX, qreal zoomY) { - if (m_executedCommand) { - m_executedCommand->undo(); - m_executedCommand.reset(); + if (!m_executedCommand) { + const bool usePostScaling = m_selectedShapes.size() > 1 || m_forceUniformScalingMode; + + m_executedCommand.reset( + new KoShapeResizeCommand( + m_selectedShapes, + zoomX, zoomY, + stillPoint, + false, usePostScaling, m_postScalingCoveringTransform)); + m_executedCommand->redo(); + } else { + m_executedCommand->replaceResizeAction(zoomX, zoomY, stillPoint); } - - const bool usePostScaling = m_selectedShapes.size() > 1 || m_forceUniformScalingMode; - - m_executedCommand.reset( - new KoShapeResizeCommand( - m_selectedShapes, - zoomX, zoomY, - stillPoint, - false, usePostScaling, m_postScalingCoveringTransform)); - m_executedCommand->redo(); } KUndo2Command *ShapeResizeStrategy::createCommand() { tool()->canvas()->snapGuide()->reset(); if (m_executedCommand) { m_executedCommand->setSkipOneRedo(true); } return m_executedCommand.take(); } void ShapeResizeStrategy::finishInteraction(Qt::KeyboardModifiers modifiers) { Q_UNUSED(modifiers); tool()->canvas()->updateCanvas(tool()->canvas()->snapGuide()->boundingRect()); } void ShapeResizeStrategy::paint(QPainter &painter, const KoViewConverter &converter) { Q_UNUSED(painter); Q_UNUSED(converter); } diff --git a/plugins/tools/karbonplugins/tools/CalligraphyTool/tutorial/TUTORIAL b/plugins/tools/karbonplugins/tools/CalligraphyTool/tutorial/TUTORIAL index 98585475e1..8138f3e076 100644 --- a/plugins/tools/karbonplugins/tools/CalligraphyTool/tutorial/TUTORIAL +++ b/plugins/tools/karbonplugins/tools/CalligraphyTool/tutorial/TUTORIAL @@ -1,108 +1,108 @@ Adapted from the Inkscape tutorial by: Copyright (C) 2004-2006 Bulia Byak Copyright (C) 2004-2006 Josh Andler Adaptation to Karbon by: Copyright (C) 2008 Fela Winkelmolen This tutorial 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. One of the many great tools available in Karbon is the Calligraphy tool. This tutorial will help you become acquainted with how that tool works, as well as demonstrate some basic techniques of the art of Calligraphy. History and Styles ================== Going by the dictionary definition, calligraphy means "beautiful writing" or "fair or elegant penmanship". Essentially, calligraphy is the art of making beautiful or elegant handwriting. It may sound intimidating, but with a little practice, anyone can master the basics of this art. The earliest forms of calligraphy date back to cave-man paintings. Up until roughly 1440 AD, before the printing press was around, calligraphy was the way books and other publications were made. A scribe had to handwrite every individual copy of every book or publication. The handwriting was done with a quill and ink onto materials such as parchment or vellum. The lettering styles used throughout the ages include Rustic, Carolingian, Blackletter, etc. Perhaps the most common place where the average person will run across calligraphy today is on wedding invitations. There are three main styles of calligraphy: * Western or Roman * Arabic * Chinese or Oriental This tutorial focuses mainly on Western calligraphy, as the other two styles tend to use a brush (instead of a pen with nib), which is not how our Calligraphy tool currently functions. One great advantage that we have over the scribes of the past is the Undo command: If you make a mistake, the entire page is not ruined. Hardware ======== You'll get the best results if you use a tablet and pen (e.g. Wacom). However, even with mouse you can do some beginning calligraphy, though you will have difficulty producing fast sweeping strokes. Calligraphy Tool Options ======================== Switch to the Calligraphy tool by clicking on its button in the tools docker. In the "Tool options" docker you can set several parameters, most of those are shown only after pressing "show details >>". The following sections will explain their use. Following an Existing Path ========================== When checking the "Follow selected path" option the stroke will follow the outline of the currently selected path, instead of following the mouse or tablet position. This way you can use the calligraphy tool to draw poligons, stars, etc. Or you can fist use the path tool to create a precise guide path, and than use the calligraphy tool on the created path. Width & Thinning ================ This pair of options control the width of your pen. Since pen width is changed often, you can adjust it without going to the docker, using the left and right arrow keys. The best thing about these keys is that they work while you're drawing, so you can change the width of your pen gradually in the middle of the stroke. - + Pen width may also depend on the velocity, as controlled by the thinning parameter. This parameter can take values from -1 to 1; zero means the width is independent of velocity, positive values make faster strokes thinner, negative values make faster strokes broader. Here are a few examples, the two left strokes having positive thinning, and the right two negative thinning, all strokes are drawn with the same width. For fun, set the thinning to 1 (maximum) and draw with jerky movements to get strangely naturalistic, neuron-like shapes: Angle & Fixation ================ After width, angle is the most important calligraphy parameter. It is the angle of your pen in degrees, changing from 0 (horizontal) to 179 passing through 90 (vertical): Each traditional calligraphy style has its own prevalent pen angle. For example, the Unicial hand uses the angle of 25 degrees. More complex hands and more experienced calligraphers will often vary the angle while drawing, and Karbon makes this possible by pressing up and down arrow keys. For beginning calligraphy lessons, however, keeping the angle constant will work best. Here are examples of strokes drawn at different angles (fixation = 1): As you can see, the stroke is at its thinnest when it is drawn parallel to its angle, and at its broadest when drawn perpendicular. Positive angles are the most natural and traditional for right-handed calligraphy. The level of contrast between the thinnest and the thickest is controlled by the fixation parameter. The value of 1 means that the angle is always constant, as set in the Angle field. Decreasing fixation lets the pen turn a little against the direction of the stroke. With fixation=0, pen rotates freely to be always perpendicular to the stroke, and Angle has no effect anymore: Typographically speaking, maximum fixation and therefore maximum stroke width contrast (above left) are the features of antique serif typefaces, such as Times or Bodoni (because these typefaces were historically an imitation of fixed-pen calligraphy). Zero fixation and zero width contrast (above right), on the other hand, suggest modern sans serif typefaces such as Helvetica. Mass & Drag =========== Unlike width and angle, these two last parameters define how the tool "feels" rather than affect its visual output. So there won't be any illustrations in this section; instead just try them yourself to get a better idea. In physics, mass is what causes inertia; the higher the mass of the Karbon calligraphy tool, the more it lags behind your mouse pointer and the more it smoothes out sharp turns and quick jerks in your stroke. When this value is small the tool is fast and responsive, but you increase the mass to get slower and smoother pen. Drag is the resistance of the paper to the movement of the pen. Lowering this parameter makes paper "slippery": if the mass is big, the pen tends to run away on sharp turns; if the mass is zero, low drag makes the pen to wiggle wildly. Calligraphy examples ==================== Now that you know the basic capabilities of the tool, you can try to produce some real calligraphy. If you are new to this art, get yourself a good calligraphy book and study it with Inkscape. This section will show you just a few simple examples. First of all, to do letters, you need to create a pair of rulers to guide you. If you're going to write in a slanted or cursive hand, add some slanted guides across the two rulers as well, for example: Then zoom in so that the height between the rulers corresponds to your most natural hand movement range, adjust width and angle, and off you go! Probably the first thing you would do as a beginner calligrapher is practice the basic elements of letters -- vertical and horizontal stems, round strokes, slanted stems. Here are some letter elements for the Unicial hand: Some useful tips: * Double clicking on a calligraphic shape will allow you to edit the control points of the guide path. For further finetuning you can convert the calligraphic shape into a simple path clicking on the "Convert To Path" button. * If your last stroke is bad, just undo it (Ctrl+Z). However, if its shape is good but the position or size are slightly off, it's better to switch to the default tool temporarily and nudge/scale/rotate it as needed, then return to the calligraphy tool. * Having done a word, switch to the default tool again to adjust stem uniformity and letterspacing. Don't overdo this, however; good calligraphy must retain somewhat irregular handwritten look. Resist the temptation to copy over letters and letter elements; each stroke must be original. And here are some complete lettering examples (taken from the original Inkscape tutorial as a lot of the other images and text): Profiles ======== Karbon allows you to easily load and save predefined and custom sets of options, called profiles. The combo box in the top of the "Tool options" docker allows you to choose the profile. To save a profile just click on the "Save profile as..." button (if the details are hidden first press "Show details >>") after which you will be asked to enter a name for the new profile. Conclusion ========== Calligraphy is not only fun; it's a deeply spiritual art that may transform your outlook on everything you do and see. Inkscape's calligraphy tool can only serve as a modest introduction. And yet it is very nice to play with and may be useful in real design. Enjoy! \ No newline at end of file diff --git a/plugins/tools/selectiontools/CMakeLists.txt b/plugins/tools/selectiontools/CMakeLists.txt index c81049076c..756cab5959 100644 --- a/plugins/tools/selectiontools/CMakeLists.txt +++ b/plugins/tools/selectiontools/CMakeLists.txt @@ -1,30 +1,37 @@ +add_subdirectory(tests) + set(kritaselectiontools_SOURCES selection_tools.cc kis_tool_select_rectangular.cc kis_tool_select_polygonal.cc kis_tool_select_elliptical.cc kis_tool_select_contiguous.cc kis_tool_select_outline.cc kis_tool_select_path.cc kis_tool_select_similar.cc kis_selection_modifier_mapper.cc + KisMagneticWorker.cc + KisToolSelectMagnetic.cc ) qt5_add_resources(kritaselectiontools_SOURCES selectiontools.qrc) add_library(kritaselectiontools MODULE ${kritaselectiontools_SOURCES}) -target_link_libraries(kritaselectiontools kritaui kritabasicflakes) +generate_export_header(kritaselectiontools BASE_NAME kritaselectiontools) + +target_link_libraries(kritaselectiontools kritaui kritabasicflakes kritaimage) install(TARGETS kritaselectiontools DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) install( FILES KisToolSelectPolygonal.action KisToolSelectElliptical.action KisToolSelectSimilar.action KisToolSelectContiguous.action KisToolSelectRectangular.action KisToolSelectOutline.action KisToolSelectPath.action + KisToolSelectMagnetic.action DESTINATION ${DATA_INSTALL_DIR}/krita/actions ) diff --git a/plugins/tools/selectiontools/KisMagneticGraph.h b/plugins/tools/selectiontools/KisMagneticGraph.h new file mode 100644 index 0000000000..a49b456ae6 --- /dev/null +++ b/plugins/tools/selectiontools/KisMagneticGraph.h @@ -0,0 +1,289 @@ +/* + * Copyright (c) 2019 Kuntal Majumder + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef KISMAGNETICGRAPH_H +#define KISMAGNETICGRAPH_H + +#include +#include +#include +#include + +#include +#include +#include + +struct VertexDescriptor { + long x, y; + + enum Direction { + MIN = 0, + N = MIN, S, E, W, NW, NE, SW, SE, NONE + }; + + VertexDescriptor(long _x, long _y) : + x(_x), y(_y) + { } + + VertexDescriptor(QPoint pt) : + x(pt.x()), y(pt.y()) + { } + + VertexDescriptor() : + x(0), y(0) + { } + + bool operator == (VertexDescriptor const &rhs) const + { + return rhs.x == x && rhs.y == y; + } + + bool operator == (QPoint const &rhs) const + { + return rhs.x() == x && rhs.y() == y; + } + + bool operator != (VertexDescriptor const &rhs) const + { + return rhs.x != x || rhs.y != y; + } + + bool operator < (VertexDescriptor const &rhs) const + { + return x < rhs.x || (x == rhs.x && y < rhs.y); + } + + // returns one of the 8 neighboring pixel based on the direction + // it gives out multiple warnings, but I am lazy, sorry + VertexDescriptor neighbor(Direction direction) const + { + int dx = 0, dy = 0; + + switch (direction) { + case W: + Q_FALLTHROUGH(); + case SW: + Q_FALLTHROUGH(); + case NW: + dx = -1; + break; + case E: + Q_FALLTHROUGH(); + case SE: + Q_FALLTHROUGH(); + case NE: + dx = 1; + default: + ; + } + + switch (direction) { + case N: + Q_FALLTHROUGH(); + case NW: + Q_FALLTHROUGH(); + case NE: + dy = -1; + break; + case S: + Q_FALLTHROUGH(); + case SW: + Q_FALLTHROUGH(); + case SE: + dy = 1; + default: + ; + } + + VertexDescriptor const neighbor(x + dx, y + dy); + return neighbor; + } // neighbor +}; + +QDebug operator << (QDebug dbg, const VertexDescriptor &v) +{ + dbg.nospace() << "(" << v.x << ", " << v.y << ")"; + return dbg.space(); +} + +struct neighbour_iterator; + +struct KisMagneticGraph { + typedef KisMagneticGraph type; + + KisMagneticGraph(){ } + + KisMagneticGraph(KisPaintDeviceSP dev) : + m_dev(dev) + { + m_randAccess = m_dev->createRandomAccessorNG(m_dev->exactBounds().x(), m_dev->exactBounds().y()); + } + + KisMagneticGraph(KisPaintDeviceSP dev, QRect graphRect) : + m_rect(graphRect), m_dev(dev) + { + m_randAccess = m_dev->createRandomAccessorNG(m_dev->exactBounds().x(), m_dev->exactBounds().y()); + } + + typedef VertexDescriptor vertex_descriptor; + typedef std::pair edge_descriptor; + typedef boost::undirected_tag directed_category; + typedef boost::disallow_parallel_edge_tag edge_parallel_category; + typedef boost::incidence_graph_tag traversal_category; + typedef neighbour_iterator out_edge_iterator; + typedef unsigned degree_size_type; + + + quint8 getIntensity(VertexDescriptor pt) + { + m_randAccess->moveTo(pt.x, pt.y); + quint8 val = *(m_randAccess->rawData()); + return val; + } + + unsigned outDegree(VertexDescriptor pt) + { + // corners + if (pt == m_rect.topLeft() || pt == m_rect.topRight() || + pt == m_rect.bottomLeft() || pt == m_rect.bottomRight()) + { + if (m_rect.width() == 1 || m_rect.height() == 1) + return 1; + + return 3; + } + + // edges + if (pt.x == m_rect.topLeft().x() || pt.y == m_rect.topLeft().y() || + pt.x == m_rect.bottomRight().x() || pt.y == m_rect.bottomRight().y()) + { + if (m_rect.width() == 1 || m_rect.height() == 1) + return 2; + + return 5; + } + return 8; + } + + QRect m_rect; + +private: + KisPaintDeviceSP m_dev; + KisRandomAccessorSP m_randAccess; +}; + +struct neighbour_iterator : public boost::iterator_facade + , boost::forward_traversal_tag + , std::pair > { + neighbour_iterator(VertexDescriptor v, KisMagneticGraph g, VertexDescriptor::Direction d) : + m_point(v), m_direction(d), m_graph(g) + { } + + neighbour_iterator() + { } + + std::pair operator * () const + { + std::pair const result = std::make_pair(m_point, m_point.neighbor(m_direction)); + return result; + } + + void operator ++ () + { + m_direction = static_cast(int(m_direction) + 1); + VertexDescriptor next = m_point.neighbor(m_direction); + if (m_direction == VertexDescriptor::NONE) { + return; + } + if (!m_graph.m_rect.contains(next.x, next.y)) { + operator ++ (); + } + } + + bool operator == (neighbour_iterator const& that) const + { + return m_point == that.m_point && m_direction == that.m_direction; + } + + bool equal(neighbour_iterator const& that) const + { + return operator == (that); + } + + void increment() + { + operator ++ (); + } + +private: + VertexDescriptor m_point; + VertexDescriptor::Direction m_direction; + KisMagneticGraph m_graph; +}; + +// Requirements for an Incidence Graph, +// https://www.boost.org/doc/libs/1_70_0/libs/graph/doc/IncidenceGraph.html + +namespace boost { +template <> +struct graph_traits { + typedef typename KisMagneticGraph::vertex_descriptor vertex_descriptor; + typedef typename KisMagneticGraph::edge_descriptor edge_descriptor; + typedef typename KisMagneticGraph::out_edge_iterator out_edge_iterator; + typedef typename KisMagneticGraph::directed_category directed_category; + typedef typename KisMagneticGraph::edge_parallel_category edge_parallel_category; + typedef typename KisMagneticGraph::traversal_category traversal_category; + typedef typename KisMagneticGraph::degree_size_type degree_size_type; + + typedef void in_edge_iterator; + typedef void vertex_iterator; + typedef void vertices_size_type; + typedef void edge_iterator; + typedef void edges_size_type; +}; +} + +typename KisMagneticGraph::vertex_descriptor source(typename KisMagneticGraph::edge_descriptor e, KisMagneticGraph g) +{ + Q_UNUSED(g) + return e.first; +} + +typename KisMagneticGraph::vertex_descriptor target(typename KisMagneticGraph::edge_descriptor e, KisMagneticGraph g) +{ + Q_UNUSED(g) + return e.second; +} + +std::pair out_edges( + typename KisMagneticGraph::vertex_descriptor v, KisMagneticGraph g) +{ + return std::make_pair( + KisMagneticGraph::out_edge_iterator(v, g, VertexDescriptor::Direction::MIN), + KisMagneticGraph::out_edge_iterator(v, g, VertexDescriptor::Direction::NONE) + ); +} + +typename KisMagneticGraph::degree_size_type out_degree(typename KisMagneticGraph::vertex_descriptor v, + KisMagneticGraph g) +{ + return g.outDegree(v); +} + +#endif // ifndef KISMAGNETICGRAPH_H diff --git a/plugins/tools/selectiontools/KisMagneticWorker.cc b/plugins/tools/selectiontools/KisMagneticWorker.cc new file mode 100644 index 0000000000..8a21ac8069 --- /dev/null +++ b/plugins/tools/selectiontools/KisMagneticWorker.cc @@ -0,0 +1,282 @@ +/* + * Copyright (c) 2019 Kuntal Majumder + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "KisMagneticWorker.h" + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include "KisMagneticGraph.h" + +struct DistanceMap { + typedef VertexDescriptor key_type; + typedef double data_type; + typedef std::pair value_type; + + explicit DistanceMap(double const &dval) + : m_default(dval) + { } + + data_type &operator [] (key_type const &k) + { + if (m.find(k) == m.end()) + m[k] = m_default; + return m[k]; + } + +private: + std::map m; + data_type const m_default; +}; + +struct PredecessorMap { + PredecessorMap() = default; + + PredecessorMap(PredecessorMap const &that) = default; + + typedef VertexDescriptor key_type; + typedef VertexDescriptor value_type; + typedef boost::read_write_property_map_tag category; + + VertexDescriptor &operator [] (VertexDescriptor v) + { + return m_map[v]; + } + + std::map m_map; +}; + +VertexDescriptor get(PredecessorMap const &m, VertexDescriptor v) +{ + auto found = m.m_map.find(v); + return found != m.m_map.end() ? found->second : v; +} + +void put(PredecessorMap &m, VertexDescriptor key, VertexDescriptor value) +{ + m.m_map[key] = value; +} + +double EuclideanDistance(VertexDescriptor p1, VertexDescriptor p2) +{ + return std::sqrt(std::pow(p1.y - p2.y, 2) + std::pow(p1.x - p2.x, 2)); +} + +class AStarHeuristic : public boost::astar_heuristic +{ +private: + VertexDescriptor m_goal; + +public: + explicit AStarHeuristic(VertexDescriptor goal) : + m_goal(goal) + { } + + double operator () (VertexDescriptor v) + { + return EuclideanDistance(v, m_goal); + } +}; + +struct GoalFound { }; + +class AStarGoalVisitor : public boost::default_astar_visitor +{ +public: + explicit AStarGoalVisitor(VertexDescriptor goal) : m_goal(goal){ } + + void examine_vertex(VertexDescriptor u, KisMagneticGraph const &g) + { + Q_UNUSED(g) + if (u == m_goal) { + throw GoalFound(); + } + } + +private: + VertexDescriptor m_goal; +}; + +struct WeightMap { + typedef std::pair key_type; + typedef double data_type; + typedef std::pair value_type; + + WeightMap() = default; + + explicit WeightMap(const KisMagneticGraph &g) : + m_graph(g) + { } + + data_type &operator [] (key_type const &k) + { + if (m_map.find(k) == m_map.end()) { + double edge_gradient = (m_graph.getIntensity(k.first) + m_graph.getIntensity(k.second)) / 2; + m_map[k] = EuclideanDistance(k.first, k.second) + 255.0 - edge_gradient; + } + return m_map[k]; + } + +private: + std::map m_map; + KisMagneticGraph m_graph; +}; + +KisMagneticLazyTiles::KisMagneticLazyTiles(KisPaintDeviceSP dev) +{ + m_dev = KisPainter::convertToAlphaAsGray(dev); + QSize s = dev->defaultBounds()->bounds().size(); + m_tileSize = KritaUtils::optimalPatchSize(); + m_tilesPerRow = (int) std::ceil((double) s.width() / (double) m_tileSize.width()); + int tilesPerColumn = (int) std::ceil((double) s.height() / (double) m_tileSize.height()); + m_dev->setDefaultBounds(dev->defaultBounds()); + + for (int i = 0; i < tilesPerColumn; i++) { + for (int j = 0; j < m_tilesPerRow; j++) { + int width = std::min(s.width() - j * m_tileSize.width(), m_tileSize.width()); + int height = std::min(s.height() - i * m_tileSize.height(), m_tileSize.height()); + QRect temp(j *m_tileSize.width(), i *m_tileSize.height(), width, height); + m_tiles.push_back(temp); + } + } + m_radiusRecord = QVector(m_tiles.size(), -1); +} + +void KisMagneticLazyTiles::filter(qreal radius, QRect &rect) +{ + auto divide = [](QPoint p, QSize s){ + return QPoint(p.x() / s.width(), p.y() / s.height()); + }; + + QPoint firstTile = divide(rect.topLeft(), m_tileSize); + QPoint lastTile = divide(rect.bottomRight(), m_tileSize); + for (int i = firstTile.y(); i <= lastTile.y(); i++) { + for (int j = firstTile.x(); j <= lastTile.x(); j++) { + int currentTile = i * m_tilesPerRow + j; + if (radius != m_radiusRecord[currentTile]) { + QRect bounds = m_tiles[currentTile]; + KisGaussianKernel::applyTightLoG(m_dev, bounds, radius, -1.0, QBitArray(), nullptr); + KisLazyFillTools::normalizeAlpha8Device(m_dev, bounds); + m_radiusRecord[currentTile] = radius; + } + } + } +} + +KisMagneticWorker::KisMagneticWorker(const KisPaintDeviceSP &dev) : + m_lazyTileFilter(dev) +{ } + +QVector KisMagneticWorker::computeEdge(int bounds, QPoint begin, QPoint end, qreal radius) +{ + QRect rect; + KisAlgebra2D::accumulateBounds(QVector { begin, end }, &rect); + rect = kisGrowRect(rect, bounds); + m_lazyTileFilter.filter(radius, rect); + + VertexDescriptor goal(end); + VertexDescriptor start(begin); + + m_graph = new KisMagneticGraph(m_lazyTileFilter.device(), rect); + + // How many maps does it require? + // Take a look here, if it doesn't make sense, https://www.boost.org/doc/libs/1_70_0/libs/graph/doc/astar_search.html + PredecessorMap pmap; + DistanceMap dmap(std::numeric_limits::max()); + dmap[start] = 0; + std::map rmap; + std::map cmap; + std::map imap; + WeightMap wmap(*m_graph); + AStarHeuristic heuristic(goal); + QVector result; + + try { + boost::astar_search_no_init( + *m_graph, start, heuristic, + boost::visitor(AStarGoalVisitor(goal)) + .distance_map(boost::associative_property_map(dmap)) + .predecessor_map(boost::ref(pmap)) + .weight_map(boost::associative_property_map(wmap)) + .vertex_index_map(boost::associative_property_map >(imap)) + .rank_map(boost::associative_property_map >(rmap)) + .color_map(boost::associative_property_map > + (cmap)) + .distance_combine(std::plus()) + .distance_compare(std::less()) + ); + } catch (GoalFound const &) { + for (VertexDescriptor u = goal; u != start; u = pmap[u]) { + result.push_front(QPointF(u.x, u.y)); + } + } + + result.push_front(QPoint(start.x, start.y)); + + return result; +} // KisMagneticWorker::computeEdge + +qreal KisMagneticWorker::intensity(QPoint pt) +{ + return m_graph->getIntensity(VertexDescriptor(pt)); +} + +void KisMagneticWorker::saveTheImage(vQPointF points) +{ + QImage img = m_lazyTileFilter.device()->convertToQImage(nullptr, m_lazyTileFilter.device()->exactBounds()); + + const QPointF offset = m_lazyTileFilter.device()->exactBounds().topLeft(); + for (QPointF &pt : points) { + pt -= offset; + } + + img = img.convertToFormat(QImage::Format_ARGB32); + QPainter gc(&img); + + QPainterPath path; + + for (int i = 0; i < points.size(); i++) { + if (i == 0) { + path.moveTo(points[i]); + } else { + path.lineTo(points[i]); + } + } + + gc.setPen(Qt::blue); + gc.drawPath(path); + + gc.setPen(Qt::green); + gc.drawEllipse(points[0], 3, 3); + gc.setPen(Qt::red); + gc.drawEllipse(points[points.count() - 1], 2, 2); + + for (QRect &r : m_lazyTileFilter.tiles() ) { + gc.drawRect(r); + } + + img.save("result.png"); +} // KisMagneticWorker::saveTheImage diff --git a/plugins/tools/selectiontools/KisMagneticWorker.h b/plugins/tools/selectiontools/KisMagneticWorker.h new file mode 100644 index 0000000000..3ceed4fdf6 --- /dev/null +++ b/plugins/tools/selectiontools/KisMagneticWorker.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2019 Kuntal Majumder + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef KISMAGNETICWORKER_H +#define KISMAGNETICWORKER_H + +#include +#include + +class KisMagneticGraph; + +class KisMagneticLazyTiles { +private: + QVector m_tiles; + QVector m_radiusRecord; + KisPaintDeviceSP m_dev; + QSize m_tileSize; + int m_tilesPerRow; + +public: + KisMagneticLazyTiles(KisPaintDeviceSP dev); + void filter(qreal radius, QRect &rect); + inline KisPaintDeviceSP device(){ return m_dev; } + inline QVector tiles(){ return m_tiles; } +}; + +class KRITASELECTIONTOOLS_EXPORT KisMagneticWorker { +public: + KisMagneticWorker(const KisPaintDeviceSP &dev); + + QVector computeEdge(int bounds, QPoint start, QPoint end, qreal radius); + void saveTheImage(vQPointF points); + qreal intensity(QPoint pt); + +private: + KisMagneticLazyTiles m_lazyTileFilter; + KisMagneticGraph *m_graph; +}; + +#endif // ifndef KISMAGNETICWORKER_H diff --git a/plugins/tools/selectiontools/KisToolSelectMagnetic.action b/plugins/tools/selectiontools/KisToolSelectMagnetic.action new file mode 100644 index 0000000000..fe0fbb9f7c --- /dev/null +++ b/plugins/tools/selectiontools/KisToolSelectMagnetic.action @@ -0,0 +1,6 @@ + + + + Magnetic Selection Tool + + diff --git a/plugins/tools/selectiontools/KisToolSelectMagnetic.cc b/plugins/tools/selectiontools/KisToolSelectMagnetic.cc new file mode 100644 index 0000000000..44f3bc1093 --- /dev/null +++ b/plugins/tools/selectiontools/KisToolSelectMagnetic.cc @@ -0,0 +1,739 @@ +/* + * Copyright (c) 2019 Kuntal Majumder + * + * 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 "KisToolSelectMagnetic.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "kis_painter.h" +#include +#include "canvas/kis_canvas2.h" +#include "kis_pixel_selection.h" +#include "kis_selection_tool_helper.h" + +#include "kis_algebra_2d.h" + +#include "KisHandlePainterHelper.h" + +#include + +#define FEEDBACK_LINE_WIDTH 2 + +KisToolSelectMagnetic::KisToolSelectMagnetic(KoCanvasBase *canvas) + : KisToolSelect(canvas, + KisCursor::load("tool_magnetic_selection_cursor.png", 5, 5), + i18n("Magnetic Selection")), + m_continuedMode(false), m_complete(false), m_selected(false), m_finished(false), + m_worker(image()->projection()), m_threshold(70), m_searchRadius(30), m_anchorGap(30), + m_filterRadius(3.0), m_mouseHoverCompressor(100, KisSignalCompressor::FIRST_ACTIVE) + +{ } + +void KisToolSelectMagnetic::keyPressEvent(QKeyEvent *event) +{ + if (event->key() == Qt::Key_Control) { + m_continuedMode = true; + } + + KisToolSelect::keyPressEvent(event); +} + +/* + * Calculates the checkpoints responsible to determining the last point from where + * the edge is calculated. + * Takes 3 point, min, median and max, searches for an edge point from median to max, if fails, + * searches for the same from median to min, if fails, median becomes that edge point. + */ +void KisToolSelectMagnetic::calculateCheckPoints(vQPointF points) +{ + qreal totalDistance = 0.0; + int checkPoint = 0; + int finalPoint = 2; + int midPoint = 1; + int minPoint = 0; + qreal maxFactor = 2; + + for (; finalPoint < points.count(); finalPoint++) { + totalDistance += kisDistance(points[finalPoint], points[finalPoint - 1]); + + if (totalDistance <= m_anchorGap / 3) { + minPoint = finalPoint; + } + + if (totalDistance <= m_anchorGap) { + midPoint = finalPoint; + } + + if (totalDistance > maxFactor * m_anchorGap) { + break; + } + } + + if (totalDistance > maxFactor * m_anchorGap) { + bool foundSomething = false; + + for (int i = midPoint; i < finalPoint; i++) { + if (m_worker.intensity(points.at(i).toPoint()) >= m_threshold) { + m_lastAnchor = points.at(i).toPoint(); + m_anchorPoints.push_back(m_lastAnchor); + + vQPointF temp; + for (int j = 0; j <= i; j++) { + temp.push_back(points[j]); + } + + m_pointCollection.push_back(temp); + foundSomething = true; + checkPoint = i; + break; + } + } + + if (!foundSomething) { + for (int i = midPoint - 1; i >= minPoint; i--) { + if (m_worker.intensity(points.at(i).toPoint()) >= m_threshold) { + m_lastAnchor = points.at(i).toPoint(); + m_anchorPoints.push_back(m_lastAnchor); + vQPointF temp; + for (int j = midPoint - 1; j >= i; j--) { + temp.push_front(points[j]); + } + + m_pointCollection.push_back(temp); + foundSomething = true; + checkPoint = i; + break; + } + } + } + + if (!foundSomething) { + m_lastAnchor = points[midPoint].toPoint(); + m_anchorPoints.push_back(m_lastAnchor); + vQPointF temp; + + for (int j = 0; j <= midPoint; j++) { + temp.push_back(points[j]); + } + + m_pointCollection.push_back(temp); + checkPoint = midPoint; + foundSomething = true; + } + } + + totalDistance = 0.0; + reEvaluatePoints(); + + for (; finalPoint < points.count(); finalPoint++) { + totalDistance += kisDistance(points[finalPoint], points[checkPoint]); + if (totalDistance > maxFactor * m_anchorGap) { + points.remove(0, checkPoint + 1); + calculateCheckPoints(points); + break; + } + } +} // KisToolSelectMagnetic::calculateCheckPoints + +void KisToolSelectMagnetic::keyReleaseEvent(QKeyEvent *event) +{ + if (event->key() == Qt::Key_Control || + !(event->modifiers() & Qt::ControlModifier)) + { + m_continuedMode = false; + if (mode() != PAINT_MODE && !m_points.isEmpty()) { + finishSelectionAction(); + } + } + + KisToolSelect::keyReleaseEvent(event); +} + +vQPointF KisToolSelectMagnetic::computeEdgeWrapper(QPoint a, QPoint b) +{ + return m_worker.computeEdge(m_searchRadius, a, b, m_filterRadius); +} + +// the cursor is still tracked even when no mousebutton is pressed +void KisToolSelectMagnetic::mouseMoveEvent(KoPointerEvent *event) +{ + m_lastCursorPos = convertToPixelCoord(event); + KisToolSelect::mouseMoveEvent(event); + updatePaintPath(); +} // KisToolSelectMagnetic::mouseMoveEvent + +// press primary mouse button +void KisToolSelectMagnetic::beginPrimaryAction(KoPointerEvent *event) +{ + setMode(KisTool::PAINT_MODE); + QPointF temp(convertToPixelCoord(event)); + + if (!image()->bounds().contains(temp.toPoint())) { + return; + } + + m_cursorOnPress = temp; + + checkIfAnchorIsSelected(temp); + + if (m_complete || m_selected) { + return; + } + + if (m_anchorPoints.count() != 0) { + vQPointF edge = computeEdgeWrapper(m_anchorPoints.last(), temp.toPoint()); + m_points.append(edge); + m_pointCollection.push_back(edge); + } else { + updateInitialAnchorBounds(temp.toPoint()); + } + + m_lastAnchor = temp.toPoint(); + m_anchorPoints.push_back(m_lastAnchor); + m_lastCursorPos = temp; + reEvaluatePoints(); + updateCanvasPixelRect(image()->bounds()); +} // KisToolSelectMagnetic::beginPrimaryAction + +void KisToolSelectMagnetic::checkIfAnchorIsSelected(QPointF temp) +{ + Q_FOREACH (const QPoint pt, m_anchorPoints) { + qreal zoomLevel = canvas()->viewConverter()->zoom(); + int sides = (int) std::ceil(10.0 / zoomLevel); + QRect r = QRect(QPoint(0, 0), QSize(sides, sides)); + r.moveCenter(pt); + if (r.contains(temp.toPoint())) { + m_selected = true; + m_selectedAnchor = m_anchorPoints.lastIndexOf(pt); + return; + } + } +} + +void KisToolSelectMagnetic::beginPrimaryDoubleClickAction(KoPointerEvent *event) +{ + QPointF temp = convertToPixelCoord(event); + + if (!image()->bounds().contains(temp.toPoint())) { + return; + } + + checkIfAnchorIsSelected(temp); + + if (m_selected) { + deleteSelectedAnchor(); + return; + } + + if (m_complete) { + int pointA = 0, pointB = 1; + double dist = std::numeric_limits::max(); + int total = m_anchorPoints.count(); + for (int i = 0; i < total; i++) { + double distToCompare = kisDistance(m_anchorPoints[i], temp) + + kisDistance(temp, m_anchorPoints[(i + 1) % total]); + if (distToCompare < dist) { + pointA = i; + pointB = (i + 1) % total; + dist = distToCompare; + } + } + + vQPointF path1 = computeEdgeWrapper(m_anchorPoints[pointA], temp.toPoint()); + vQPointF path2 = computeEdgeWrapper(temp.toPoint(), m_anchorPoints[pointB]); + + m_pointCollection[pointA] = path1; + m_pointCollection.insert(pointB, path2); + m_anchorPoints.insert(pointB, temp.toPoint()); + + reEvaluatePoints(); + } +} // KisToolSelectMagnetic::beginPrimaryDoubleClickAction + +// drag while primary mouse button is pressed +void KisToolSelectMagnetic::continuePrimaryAction(KoPointerEvent *event) +{ + if (m_selected) { + m_anchorPoints[m_selectedAnchor] = convertToPixelCoord(event).toPoint(); + } else if (!m_complete) { + m_lastCursorPos = convertToPixelCoord(event); + if(kisDistance(m_lastCursorPos, m_cursorOnPress) >= m_anchorGap) + m_mouseHoverCompressor.start(); + } + KisToolSelectBase::continuePrimaryAction(event); +} + +void KisToolSelectMagnetic::slotCalculateEdge() +{ + QPoint current = m_lastCursorPos.toPoint(); + if (!image()->bounds().contains(current)) + return; + + if(kisDistance(m_lastAnchor, current) < m_anchorGap) + return; + + vQPointF pointSet = computeEdgeWrapper(m_lastAnchor, current); + calculateCheckPoints(pointSet); +} + +// release primary mouse button +void KisToolSelectMagnetic::endPrimaryAction(KoPointerEvent *event) +{ + if (m_selected && convertToPixelCoord(event) != m_cursorOnPress) { + if (!image()->bounds().contains(m_anchorPoints[m_selectedAnchor])) { + deleteSelectedAnchor(); + } else { + updateSelectedAnchor(); + } + } else if (m_selected) { + QPointF temp(convertToPixelCoord(event)); + + if (!image()->bounds().contains(temp.toPoint())) { + return; + } + + if (m_snapBound.contains(temp) && m_anchorPoints.count() > 1) { + if(m_complete){ + finishSelectionAction(); + return; + } + vQPointF edge = computeEdgeWrapper(m_anchorPoints.last(), temp.toPoint()); + m_points.append(edge); + m_pointCollection.push_back(edge); + m_complete = true; + } + } + if (m_mouseHoverCompressor.isActive()) { + m_mouseHoverCompressor.stop(); + slotCalculateEdge(); + } + m_selected = false; + KisToolSelectBase::endPrimaryAction(event); +} // KisToolSelectMagnetic::endPrimaryAction + +void KisToolSelectMagnetic::deleteSelectedAnchor() +{ + if (m_anchorPoints.isEmpty()) + return; + + // if it is the initial anchor + if (m_selectedAnchor == 0) { + m_anchorPoints.pop_front(); + if (m_anchorPoints.isEmpty()) { + // it was the only point lol + resetVariables(); + reEvaluatePoints(); + return; + } + m_pointCollection.pop_front(); + if (m_complete) { + m_pointCollection[0] = computeEdgeWrapper(m_anchorPoints.first(), m_anchorPoints.last()); + } + reEvaluatePoints(); + return; + } + + // if it is the last anchor + if (m_selectedAnchor == m_anchorPoints.count() - 1) { + m_anchorPoints.pop_back(); + m_pointCollection.pop_back(); + if (m_complete) { + m_pointCollection[m_selectedAnchor] = computeEdgeWrapper(m_anchorPoints.last(), m_anchorPoints.first()); + } + reEvaluatePoints(); + return; + } + + // it is in the middle + m_anchorPoints.remove(m_selectedAnchor); + m_pointCollection.remove(m_selectedAnchor); + m_pointCollection[m_selectedAnchor - 1] = computeEdgeWrapper(m_anchorPoints[m_selectedAnchor - 1], + m_anchorPoints[m_selectedAnchor]); + reEvaluatePoints(); +} // KisToolSelectMagnetic::deleteSelectedAnchor + +void KisToolSelectMagnetic::updateSelectedAnchor() +{ + // initial + if (m_selectedAnchor == 0 && m_anchorPoints.count() > 1) { + m_pointCollection[m_selectedAnchor] = computeEdgeWrapper(m_anchorPoints[0], m_anchorPoints[1]); + if (m_complete) { + m_pointCollection[m_anchorPoints.count() - 1] = computeEdgeWrapper(m_anchorPoints.last(), + m_anchorPoints.first()); + } + reEvaluatePoints(); + return; + } + + // last + if (m_selectedAnchor == m_anchorPoints.count() - 1) { + m_pointCollection[m_selectedAnchor - 1] = computeEdgeWrapper(m_anchorPoints[m_selectedAnchor - 1], + m_anchorPoints.last()); + if (m_complete) { + m_pointCollection[m_selectedAnchor] = computeEdgeWrapper(m_anchorPoints.last(), m_anchorPoints.first()); + } + reEvaluatePoints(); + return; + } + + // middle + m_pointCollection[m_selectedAnchor - 1] = computeEdgeWrapper(m_anchorPoints[m_selectedAnchor - 1], + m_anchorPoints[m_selectedAnchor]); + m_pointCollection[m_selectedAnchor] = computeEdgeWrapper(m_anchorPoints[m_selectedAnchor], + m_anchorPoints[m_selectedAnchor + 1]); + reEvaluatePoints(); +} + +int KisToolSelectMagnetic::updateInitialAnchorBounds(QPoint pt) +{ + qreal zoomLevel = canvas()->viewConverter()->zoom(); + int sides = (int) std::ceil(10.0 / zoomLevel); + m_snapBound = QRectF(QPoint(0, 0), QSize(sides, sides)); + m_snapBound.moveCenter(pt); + return sides; +} + +void KisToolSelectMagnetic::reEvaluatePoints() +{ + m_points.clear(); + Q_FOREACH (const vQPointF vec, m_pointCollection) { + m_points.append(vec); + } + + updatePaintPath(); +} + +void KisToolSelectMagnetic::finishSelectionAction() +{ + KisCanvas2 *kisCanvas = dynamic_cast(canvas()); + KIS_ASSERT_RECOVER_RETURN(kisCanvas) + kisCanvas->updateCanvas(); + setMode(KisTool::HOVER_MODE); + m_complete = false; + m_finished = true; + + // just for testing out + // m_worker.saveTheImage(m_points); + + QRectF boundingViewRect = + pixelToView(KisAlgebra2D::accumulateBounds(m_points)); + + KisSelectionToolHelper helper(kisCanvas, kundo2_i18n("Magnetic Selection")); + + if (m_points.count() > 2 && + !helper.tryDeselectCurrentSelection(boundingViewRect, selectionAction())) + { + QApplication::setOverrideCursor(KisCursor::waitCursor()); + + const SelectionMode mode = + helper.tryOverrideSelectionMode(kisCanvas->viewManager()->selection(), + selectionMode(), + selectionAction()); + if (mode == PIXEL_SELECTION) { + KisPixelSelectionSP tmpSel = KisPixelSelectionSP(new KisPixelSelection()); + + KisPainter painter(tmpSel); + painter.setPaintColor(KoColor(Qt::black, tmpSel->colorSpace())); + painter.setAntiAliasPolygonFill(antiAliasSelection()); + painter.setFillStyle(KisPainter::FillStyleForegroundColor); + painter.setStrokeStyle(KisPainter::StrokeStyleNone); + + painter.paintPolygon(m_points); + + QPainterPath cache; + cache.addPolygon(m_points); + cache.closeSubpath(); + tmpSel->setOutlineCache(cache); + + helper.selectPixelSelection(tmpSel, selectionAction()); + } else { + KoPathShape *path = new KoPathShape(); + path->setShapeId(KoPathShapeId); + + QTransform resolutionMatrix; + resolutionMatrix.scale(1 / currentImage()->xRes(), 1 / currentImage()->yRes()); + path->moveTo(resolutionMatrix.map(m_points[0])); + for (int i = 1; i < m_points.count(); i++) + path->lineTo(resolutionMatrix.map(m_points[i])); + path->close(); + path->normalize(); + helper.addSelectionShape(path, selectionAction()); + } + QApplication::restoreOverrideCursor(); + } + + resetVariables(); +} // KisToolSelectMagnetic::finishSelectionAction + +void KisToolSelectMagnetic::resetVariables() +{ + m_points.clear(); + m_anchorPoints.clear(); + m_pointCollection.clear(); + m_paintPath = QPainterPath(); +} + +void KisToolSelectMagnetic::updatePaintPath() +{ + m_paintPath = QPainterPath(); + if (m_points.size() > 0) { + m_paintPath.moveTo(pixelToView(m_points[0])); + } + for (int i = 1; i < m_points.count(); i++) { + m_paintPath.lineTo(pixelToView(m_points[i])); + } + + updateFeedback(); + + if (m_continuedMode && mode() != PAINT_MODE) { + updateContinuedMode(); + } + + updateCanvasPixelRect(image()->bounds()); +} + +void KisToolSelectMagnetic::paint(QPainter& gc, const KoViewConverter &converter) +{ + Q_UNUSED(converter) + updatePaintPath(); + if ((mode() == KisTool::PAINT_MODE || m_continuedMode) && + !m_anchorPoints.isEmpty()) + { + QPainterPath outline = m_paintPath; + if (m_continuedMode && mode() != KisTool::PAINT_MODE) { + outline.lineTo(pixelToView(m_lastCursorPos)); + } + paintToolOutline(&gc, outline); + drawAnchors(gc); + } +} + +void KisToolSelectMagnetic::drawAnchors(QPainter &gc) +{ + int sides = updateInitialAnchorBounds(m_anchorPoints.first()); + Q_FOREACH (const QPoint pt, m_anchorPoints) { + KisHandlePainterHelper helper(&gc, handleRadius()); + QRect r(QPoint(0, 0), QSize(sides, sides)); + r.moveCenter(pt); + if (r.contains(m_lastCursorPos.toPoint())) { + helper.setHandleStyle(KisHandleStyle::highlightedPrimaryHandles()); + } else { + helper.setHandleStyle(KisHandleStyle::primarySelection()); + } + helper.drawHandleRect(pixelToView(pt), 4, QPoint(0, 0)); + } +} + +void KisToolSelectMagnetic::updateFeedback() +{ + if (m_points.count() > 1) { + qint32 lastPointIndex = m_points.count() - 1; + + QRectF updateRect = QRectF(m_points[lastPointIndex - 1], m_points[lastPointIndex]).normalized(); + updateRect = kisGrowRect(updateRect, FEEDBACK_LINE_WIDTH); + + updateCanvasPixelRect(updateRect); + } +} + +void KisToolSelectMagnetic::updateContinuedMode() +{ + if (!m_points.isEmpty()) { + qint32 lastPointIndex = m_points.count() - 1; + + QRectF updateRect = QRectF(m_points[lastPointIndex - 1], m_lastCursorPos).normalized(); + updateRect = kisGrowRect(updateRect, FEEDBACK_LINE_WIDTH); + + updateCanvasPixelRect(updateRect); + } +} + +void KisToolSelectMagnetic::activate(KoToolBase::ToolActivation activation, const QSet &shapes) +{ + m_worker = KisMagneticWorker(image()->projection()); + m_configGroup = KSharedConfig::openConfig()->group(toolId()); + connect(action("undo_polygon_selection"), SIGNAL(triggered()), SLOT(undoPoints()), Qt::UniqueConnection); + connect(&m_mouseHoverCompressor, SIGNAL(timeout()), this, SLOT(slotCalculateEdge())); + KisToolSelect::activate(activation, shapes); +} + +void KisToolSelectMagnetic::deactivate() +{ + KisCanvas2 *kisCanvas = dynamic_cast(canvas()); + KIS_ASSERT_RECOVER_RETURN(kisCanvas) + kisCanvas->updateCanvas(); + resetVariables(); + m_continuedMode = false; + disconnect(action("undo_polygon_selection"), nullptr, this, nullptr); + + KisTool::deactivate(); +} + +void KisToolSelectMagnetic::undoPoints() +{ + if (m_complete) return; + + if(m_anchorPoints.count() <= 1){ + resetVariables(); + return; + } + + m_anchorPoints.pop_back(); + m_pointCollection.pop_back(); + reEvaluatePoints(); +} + +void KisToolSelectMagnetic::requestStrokeEnd() +{ + if (m_finished || m_anchorPoints.count() < 2) return; + + finishSelectionAction(); + m_finished = false; +} + +void KisToolSelectMagnetic::requestStrokeCancellation() +{ + m_complete = false; + m_finished = false; + resetVariables(); +} + +QWidget * KisToolSelectMagnetic::createOptionWidget() +{ + KisToolSelectBase::createOptionWidget(); + KisSelectionOptions *selectionWidget = selectionOptionWidget(); + QHBoxLayout *f1 = new QHBoxLayout(); + QLabel *filterRadiusLabel = new QLabel(i18n("Filter Radius: "), selectionWidget); + f1->addWidget(filterRadiusLabel); + + KisDoubleSliderSpinBox *filterRadiusInput = new KisDoubleSliderSpinBox(selectionWidget); + filterRadiusInput->setObjectName("radius"); + filterRadiusInput->setRange(2.5, 100.0, 2); + filterRadiusInput->setSingleStep(0.5); + filterRadiusInput->setToolTip("Radius of the filter for the detecting edges, might take some time to calculate"); + f1->addWidget(filterRadiusInput); + connect(filterRadiusInput, SIGNAL(valueChanged(qreal)), this, SLOT(slotSetFilterRadius(qreal))); + + QHBoxLayout *f2 = new QHBoxLayout(); + QLabel *thresholdLabel = new QLabel(i18n("Threshold: "), selectionWidget); + f2->addWidget(thresholdLabel); + + KisSliderSpinBox *thresholdInput = new KisSliderSpinBox(selectionWidget); + thresholdInput->setObjectName("threshold"); + thresholdInput->setRange(1, 255); + thresholdInput->setSingleStep(10); + thresholdInput->setToolTip("Threshold for determining the minimum intensity of the edges"); + f2->addWidget(thresholdInput); + connect(thresholdInput, SIGNAL(valueChanged(int)), this, SLOT(slotSetThreshold(int))); + + QHBoxLayout *f3 = new QHBoxLayout(); + QLabel *searchRadiusLabel = new QLabel(i18n("Search Radius: "), selectionWidget); + f3->addWidget(searchRadiusLabel); + + KisSliderSpinBox *searchRadiusInput = new KisSliderSpinBox(selectionWidget); + searchRadiusInput->setObjectName("frequency"); + searchRadiusInput->setRange(20, 200); + searchRadiusInput->setSingleStep(10); + searchRadiusInput->setToolTip("Extra area to be searched"); + searchRadiusInput->setSuffix(" px"); + f3->addWidget(searchRadiusInput); + connect(searchRadiusInput, SIGNAL(valueChanged(int)), this, SLOT(slotSetSearchRadius(int))); + + QHBoxLayout *f4 = new QHBoxLayout(); + QLabel *anchorGapLabel = new QLabel(i18n("Anchor Gap: "), selectionWidget); + f4->addWidget(anchorGapLabel); + + KisSliderSpinBox *anchorGapInput = new KisSliderSpinBox(selectionWidget); + anchorGapInput->setObjectName("anchorgap"); + anchorGapInput->setRange(20, 200); + anchorGapInput->setSingleStep(10); + anchorGapInput->setToolTip("Gap between 2 anchors in interative mode"); + anchorGapInput->setSuffix(" px"); + f4->addWidget(anchorGapInput); + + connect(anchorGapInput, SIGNAL(valueChanged(int)), this, SLOT(slotSetAnchorGap(int))); + + QVBoxLayout *l = dynamic_cast(selectionWidget->layout()); + + l->insertLayout(1, f1); + l->insertLayout(2, f2); + l->insertLayout(3, f3); + l->insertLayout(5, f4); + + filterRadiusInput->setValue(m_configGroup.readEntry("filterradius", 3.0)); + thresholdInput->setValue(m_configGroup.readEntry("threshold", 100)); + searchRadiusInput->setValue(m_configGroup.readEntry("searchradius", 30)); + anchorGapInput->setValue(m_configGroup.readEntry("anchorgap", 20)); + + return selectionWidget; +} // KisToolSelectMagnetic::createOptionWidget + +void KisToolSelectMagnetic::slotSetFilterRadius(qreal r) +{ + m_filterRadius = r; + m_configGroup.writeEntry("filterradius", r); +} + +void KisToolSelectMagnetic::slotSetThreshold(int t) +{ + m_threshold = t; + m_configGroup.writeEntry("threshold", t); +} + +void KisToolSelectMagnetic::slotSetSearchRadius(int r) +{ + m_searchRadius = r; + m_configGroup.writeEntry("searchradius", r); +} + +void KisToolSelectMagnetic::slotSetAnchorGap(int g) +{ + m_anchorGap = g; + m_configGroup.writeEntry("anchorgap", g); +} + +void KisToolSelectMagnetic::resetCursorStyle() +{ + if (selectionAction() == SELECTION_ADD) { + useCursor(KisCursor::load("tool_magnetic_selection_cursor_add.png", 6, 6)); + } else if (selectionAction() == SELECTION_SUBTRACT) { + useCursor(KisCursor::load("tool_magnetic_selection_cursor_sub.png", 6, 6)); + } else { + KisToolSelect::resetCursorStyle(); + } +} diff --git a/plugins/tools/selectiontools/KisToolSelectMagnetic.h b/plugins/tools/selectiontools/KisToolSelectMagnetic.h new file mode 100644 index 0000000000..92be32a30c --- /dev/null +++ b/plugins/tools/selectiontools/KisToolSelectMagnetic.h @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2019 Kuntal Majumder + * + * 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_TOOL_SELECT_MAGNETIC_H_ +#define KIS_TOOL_SELECT_MAGNETIC_H_ + +#include +#include "KisSelectionToolFactoryBase.h" +#include +#include +#include +#include "KisMagneticWorker.h" + +class QPainterPath; + +class KisToolSelectMagnetic : public KisToolSelect +{ + Q_OBJECT + +public: + KisToolSelectMagnetic(KoCanvasBase *canvas); + ~KisToolSelectMagnetic() override = default; + void beginPrimaryAction(KoPointerEvent *event) override; + void continuePrimaryAction(KoPointerEvent *event) override; + void endPrimaryAction(KoPointerEvent *event) override; + void paint(QPainter& gc, const KoViewConverter &converter) override; + + void beginPrimaryDoubleClickAction(KoPointerEvent *event) override; + + void keyPressEvent(QKeyEvent *event) override; + void keyReleaseEvent(QKeyEvent *event) override; + + void mouseMoveEvent(KoPointerEvent *event) override; + + void resetCursorStyle() override; + void requestStrokeEnd() override; + void requestStrokeCancellation() override; + QWidget * createOptionWidget() override; + +public Q_SLOTS: + void deactivate() override; + void activate(KoToolBase::ToolActivation activation, const QSet &shapes) override; + void undoPoints(); + void slotSetFilterRadius(qreal); + void slotSetThreshold(int); + void slotSetSearchRadius(int); + void slotSetAnchorGap(int); + void slotCalculateEdge(); + +protected: + using KisToolSelectBase::m_widgetHelper; + +private: + void finishSelectionAction(); + void updateFeedback(); + void updateContinuedMode(); + void updateCanvas(); + void updatePaintPath(); + void resetVariables(); + void drawAnchors(QPainter &gc); + void checkIfAnchorIsSelected(QPointF pt); + vQPointF computeEdgeWrapper(QPoint a, QPoint b); + void reEvaluatePoints(); + void calculateCheckPoints(vQPointF points); + void deleteSelectedAnchor(); + void updateSelectedAnchor(); + int updateInitialAnchorBounds(QPoint pt); + + QPainterPath m_paintPath; + QVector m_points; + QVector m_anchorPoints; + bool m_continuedMode; + QPointF m_lastCursorPos, m_cursorOnPress; + QPoint m_lastAnchor; + bool m_complete, m_selected, m_finished; + KisMagneticWorker m_worker; + int m_threshold, m_searchRadius, m_selectedAnchor, m_anchorGap; + qreal m_filterRadius; + QRectF m_snapBound; + KConfigGroup m_configGroup; + QVector m_pointCollection; + KisSignalCompressor m_mouseHoverCompressor; +}; + +class KisToolSelectMagneticFactory : public KisSelectionToolFactoryBase +{ +public: + KisToolSelectMagneticFactory() + : KisSelectionToolFactoryBase("KisToolSelectMagnetic") + { + setToolTip(i18n("Magnetic Selection Tool")); + setSection(TOOL_TYPE_SELECTION); + setIconName(koIconNameCStr("tool_magnetic_selection")); + setPriority(8); + setActivationShapeId(KRITA_TOOL_ACTIVATION_ID); + } + + ~KisToolSelectMagneticFactory() override { } + + KoToolBase * createTool(KoCanvasBase *canvas) override + { + return new KisToolSelectMagnetic(canvas); + } + + QList createActionsImpl() override + { + KisActionRegistry *actionRegistry = KisActionRegistry::instance(); + QList actions = KisSelectionToolFactoryBase::createActionsImpl(); + + actions << actionRegistry->makeQAction("undo_polygon_selection"); + + return actions; + } +}; + + +#endif // __selecttoolmagnetic_h__ diff --git a/plugins/tools/selectiontools/selection_tools.cc b/plugins/tools/selectiontools/selection_tools.cc index e0e44fbe60..fb0af54745 100644 --- a/plugins/tools/selectiontools/selection_tools.cc +++ b/plugins/tools/selectiontools/selection_tools.cc @@ -1,59 +1,61 @@ /* * selection_tools.cc -- Part of Krita * * Copyright (c) 2004 Boudewijn Rempt (boud@valdyas.org) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "selection_tools.h" #include #include #include #include "KoToolRegistry.h" #include "kis_global.h" #include "kis_types.h" #include "kis_tool_select_outline.h" #include "kis_tool_select_polygonal.h" #include "kis_tool_select_rectangular.h" #include "kis_tool_select_contiguous.h" #include "kis_tool_select_elliptical.h" #include "kis_tool_select_path.h" #include "kis_tool_select_similar.h" +#include "KisToolSelectMagnetic.h" K_PLUGIN_FACTORY_WITH_JSON(SelectionToolsFactory, "kritaselectiontools.json", registerPlugin();) SelectionTools::SelectionTools(QObject *parent, const QVariantList &) : QObject(parent) { KoToolRegistry::instance()->add(new KisToolSelectOutlineFactory()); KoToolRegistry::instance()->add(new KisToolSelectPolygonalFactory()); KoToolRegistry::instance()->add(new KisToolSelectRectangularFactory()); KoToolRegistry::instance()->add(new KisToolSelectEllipticalFactory()); KoToolRegistry::instance()->add(new KisToolSelectContiguousFactory()); KoToolRegistry::instance()->add(new KisToolSelectPathFactory()); KoToolRegistry::instance()->add(new KisToolSelectSimilarFactory()); + KoToolRegistry::instance()->add(new KisToolSelectMagneticFactory()); } SelectionTools::~SelectionTools() { } #include "selection_tools.moc" diff --git a/plugins/tools/selectiontools/selectiontools.qrc b/plugins/tools/selectiontools/selectiontools.qrc index cd3646a389..37430ba7d6 100644 --- a/plugins/tools/selectiontools/selectiontools.qrc +++ b/plugins/tools/selectiontools/selectiontools.qrc @@ -1,24 +1,26 @@ - - - + + tool_contiguous_selection_cursor_add.png tool_contiguous_selection_cursor.png tool_contiguous_selection_cursor_sub.png tool_elliptical_selection_cursor_add.png tool_elliptical_selection_cursor.png tool_elliptical_selection_cursor_sub.png tool_eraser_selection_cursor.png tool_outline_selection_cursor_add.png tool_outline_selection_cursor.png tool_outline_selection_cursor_sub.png tool_polygonal_selection_cursor_add.png tool_polygonal_selection_cursor.png tool_polygonal_selection_cursor_sub.png tool_rectangular_selection_cursor_add.png tool_rectangular_selection_cursor.png tool_rectangular_selection_cursor_sub.png tool_similar_selection_cursor_add.png tool_similar_selection_cursor.png tool_similar_selection_cursor_sub.png + tool_magnetic_selection_cursor.png + tool_magnetic_selection_cursor_sub.png + tool_magnetic_selection_cursor_add.png diff --git a/plugins/tools/selectiontools/tests/CMakeLists.txt b/plugins/tools/selectiontools/tests/CMakeLists.txt new file mode 100644 index 0000000000..18ed773851 --- /dev/null +++ b/plugins/tools/selectiontools/tests/CMakeLists.txt @@ -0,0 +1,14 @@ +set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR}/.. + ${CMAKE_CURRENT_BINARY_DIR}/.. + ${CMAKE_SOURCE_DIR}/sdk/tests +) + +macro_add_unittest_definitions() + +########### next target ############### + +ecm_add_test(KisMagneticWorkerTest.cc + NAME_PREFIX plugins-magneticselection- + LINK_LIBRARIES kritaselectiontools kritaimage Qt5::Test) diff --git a/plugins/tools/selectiontools/tests/KisMagneticWorkerTest.cc b/plugins/tools/selectiontools/tests/KisMagneticWorkerTest.cc new file mode 100644 index 0000000000..33c9006f66 --- /dev/null +++ b/plugins/tools/selectiontools/tests/KisMagneticWorkerTest.cc @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2019 Kuntal Majumder + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "KisMagneticWorkerTest.h" + +#include +#include +#include +#include +#include + +#include + +inline KisPaintDeviceSP loadTestImage(const QString &name, bool convertToAlpha) +{ + QImage image(TestUtil::fetchDataFileLazy(name)); + KisPaintDeviceSP dev = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8()); + dev->convertFromQImage(image, 0); + + if (convertToAlpha) { + dev = KisPainter::convertToAlphaAsAlpha(dev); + } + + return dev; +} + +void KisMagneticWorkerTest::testWorker() +{ + KisPaintDeviceSP dev = loadTestImage("test_main.png", false); + const QRect rect = dev->exactBounds(); + KisPaintDeviceSP grayscaleDev = KisPainter::convertToAlphaAsGray(dev); + KisMagneticWorker worker(grayscaleDev); + + const QPoint startPos(80, 10); + const QPoint endPos(10, 100); + + auto points = worker.computeEdge(20, startPos, endPos, 3.0); + KIS_DUMP_DEVICE_2(grayscaleDev, rect, "draw", "dd"); + + /* + QVector result = { QPointF(50,65), + QPointF(49,64), + QPointF(48,63), + QPointF(47,62), + QPointF(46,61), + QPointF(45,60), + QPointF(44,59), + QPointF(44,58), + QPointF(44,57), + QPointF(44,56), + QPointF(44,55), + QPointF(44,54), + QPointF(44,53), + QPointF(44,52), + QPointF(44,51), + QPointF(44,50), + QPointF(44,49), + QPointF(44,48), + QPointF(44,47), + QPointF(44,46), + QPointF(44,45), + QPointF(44,44), + QPointF(44,43), + QPointF(44,42), + QPointF(43,41), + QPointF(43,40), + QPointF(43,39), + QPointF(44,38), + QPointF(44,37), + QPointF(44,36), + QPointF(44,35), + QPointF(44,34), + QPointF(44,33), + QPointF(44,32), + QPointF(44,31), + QPointF(44,30), + QPointF(44,29), + QPointF(44,28), + QPointF(44,27), + QPointF(44,26), + QPointF(44,25), + QPointF(44,24), + QPointF(44,23), + QPointF(44,22), + QPointF(44,21), + QPointF(44,20), + QPointF(44,19), + QPointF(44,18), + QPointF(43,17), + QPointF(44,16), + QPointF(44,15), + QPointF(44,14), + QPointF(44,13), + QPointF(43,12), + QPointF(42,11), + QPointF(41,11), + QPointF(40,10)}; + + QCOMPARE(result, points); + */ + + QImage img = dev->convertToQImage(0, rect); + img = img.convertToFormat(QImage::Format_ARGB32); + QPainter gc(&img); + + QPainterPath path; + + for (int i = 0; i < points.size(); i++) { + if (i == 0) { + path.moveTo(points[i]); + } else { + path.lineTo(points[i]); + } + } + + gc.setPen(Qt::blue); + gc.drawPath(path); + + gc.setPen(Qt::green); + gc.drawEllipse(startPos, 3, 3); + gc.setPen(Qt::red); + gc.drawEllipse(endPos, 2, 2); + + img.save("result.png"); + +} + +QTEST_MAIN(KisMagneticWorkerTest) diff --git a/plugins/tools/selectiontools/tests/KisMagneticWorkerTest.h b/plugins/tools/selectiontools/tests/KisMagneticWorkerTest.h new file mode 100644 index 0000000000..934b6f8a4f --- /dev/null +++ b/plugins/tools/selectiontools/tests/KisMagneticWorkerTest.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2019 Kuntal Majumder + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef KISMAGNETICWORKERTEST_H +#define KISMAGNETICWORKERTEST_H + +#include + +class KisMagneticWorkerTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void testWorker(); + +}; + +#endif diff --git a/plugins/tools/selectiontools/tests/data/test_main.png b/plugins/tools/selectiontools/tests/data/test_main.png new file mode 100644 index 0000000000..cb0e8e5429 Binary files /dev/null and b/plugins/tools/selectiontools/tests/data/test_main.png differ diff --git a/plugins/tools/selectiontools/tool_magnetic_selection_cursor.png b/plugins/tools/selectiontools/tool_magnetic_selection_cursor.png new file mode 100644 index 0000000000..302df70a6d Binary files /dev/null and b/plugins/tools/selectiontools/tool_magnetic_selection_cursor.png differ diff --git a/plugins/tools/selectiontools/tool_magnetic_selection_cursor_add.png b/plugins/tools/selectiontools/tool_magnetic_selection_cursor_add.png new file mode 100644 index 0000000000..951bea6c04 Binary files /dev/null and b/plugins/tools/selectiontools/tool_magnetic_selection_cursor_add.png differ diff --git a/plugins/tools/selectiontools/tool_magnetic_selection_cursor_sub.png b/plugins/tools/selectiontools/tool_magnetic_selection_cursor_sub.png new file mode 100644 index 0000000000..a196e597c0 Binary files /dev/null and b/plugins/tools/selectiontools/tool_magnetic_selection_cursor_sub.png differ diff --git a/sdk/.uncrustify.cfg b/sdk/.uncrustify.cfg new file mode 100644 index 0000000000..568bfda207 --- /dev/null +++ b/sdk/.uncrustify.cfg @@ -0,0 +1,396 @@ +# -------------------------------------------------------------------------------------------------# +# # +# _ _ _ _ __ ___ _____ _ _ __ _ # +# | | | |_ _ __ _ _ _ _ __| |_(_)/ _|_ _ / __| / / __|| |_ _| |_ __ ___ _ _ / _(_)__ _ # +# | |_| | ' \/ _| '_| || (_-< _| | _| || | | (__ / / (_|_ _|_ _| / _/ _ \ ' \| _| / _` | # +# \___/|_||_\__|_| \_,_/__/\__|_|_| \_, | \___/_/ \___||_| |_| \__\___/_||_|_| |_\__, | # +# |__/ |___/ # +# # +# -------------------------------------------------------------------------------------------------# +# # +# Style: rindeal # +# # +# -------------------------------------------------------------------------------------------------# +# Boilerplate: https://github.com/bengardner/uncrustify/blob/master/etc/defaults.cfg # +# -------------------------------------------------------------------------------------------------# + + +## General +## ------------------------------------------------------------------------------------------------- + +# The type of line endings +newlines = lf # auto/lf/crlf/cr + +code_width = 120 + +# empty_lines_max = nl_max - 1 +nl_max = 3 + + +## UNICODE +## ------------------------------------------------------------------------------------------------- +## Ideally ASCII, UTF-8 otherwise + +# If the file contains bytes with values between 128 and 255, but is not UTF-8, then output as UTF-8 +utf8_byte = false + +# Force the output encoding to UTF-8 +utf8_force = true + + +## Tabs +## ------------------------------------------------------------------------------------------------- +## Always use 4 spaces + +input_tab_size = 4 +output_tab_size = 4 + +indent_with_tabs = 0 + +# Comments that are not a brace level are indented with tabs on a tabstop. +# Requires indent_with_tabs = 2. If false, will use spaces. +indent_cmt_with_tabs = false + +# Whether to use tabs for aligning +align_with_tabs = false + +# Whether to keep non-indenting tabs +align_keep_tabs = false + +# Whether to bump out to the next tab when aligning +align_on_tabstop = false + + +## Indenting +## ------------------------------------------------------------------------------------------------- + +# True: indent_func_call_param will be used (default) +# False: indent_func_call_param will NOT be used. +use_indent_func_call_param = true # false/true + + +# The continuation indent for func_*_param if they are true. +# If non-zero, this overrides the indent. +indent_param = 1 # unsigned number + +# The number of columns to indent per level. +# Usually 2, 3, 4, or 8. +indent_columns = 4 + +# The continuation indent. If non-zero, this overrides the indent of '(' and '=' continuation indents. +# For FreeBSD, this is set to 4. Negative value is absolute and not increased for each ( level +indent_continue = 0 + + +## Spacing +## ------------------------------------------------------------------------------------------------- + +# Whether to balance spaces inside nested parens +sp_balance_nested_parens = false + + +## Parentheses +## ------------------------------------------------------------------------------------------------- + +# Controls the indent of a close paren after a newline. +# 0: Indent to body level +# 1: Align under the open paren +# 2: Indent to the brace level +indent_paren_close = 0 + + +## Preprocessor +## ------------------------------------------------------------------------------------------------- + +# Control indent of preprocessors inside #if blocks at brace level 0 +pp_indent = remove # ignore/add/remove/force + +# indent by 1 space +pp_space = add +pp_space_count = 1 + +# indent pp at code level +pp_indent_at_level = true +pp_define_at_level = true + +# Control whether to indent the code between #if, #else and #endif when not at file-level +pp_if_indent_code = false + +# # Align macro functions and variables together +align_pp_define_together = false + +# The minimum space between label and value of a preprocessor define +align_pp_define_gap = 1 + +# The span for aligning on '#define' bodies (0=don't align) +align_pp_define_span = 2 + +# Add or remove space around preprocessor '##' concatenation operator. Default=Add +sp_pp_concat = add # ignore/add/remove/force + +# Add or remove space after preprocessor '#' stringify operator. Also affects the '#@' charizing operator. +sp_pp_stringify = ignore # ignore/add/remove/force + +# Add or remove space before preprocessor '#' stringify operator as in '#define x(y) L#y'. +sp_before_pp_stringify = ignore # ignore/add/remove/force + + +# Template +# -------------------------------------------------------------------------------------------------- + +# Add or remove space in 'template <' vs 'template<'. +# If set to ignore, sp_before_angle is used. +sp_template_angle = add # ignore/add/remove/force + +# Add or remove space before '<>' +sp_before_angle = remove # ignore/add/remove/force + +# Add or remove space inside '<' and '>' +sp_inside_angle = remove # ignore/add/remove/force + +# Add or remove space after '<>' +sp_after_angle = add # ignore/add/remove/force + +# Add or remove space between '<>' and '(' as found in 'new List();' +sp_angle_paren = remove # ignore/add/remove/force + +# Add or remove space between '<>' and a word as in 'List m;' +sp_angle_word = add # ignore/add/remove/force + +# Add or remove space between '>' and '>' in '>>' (template stuff C++/C# only). Default=Add +sp_angle_shift = add # ignore/add/remove/force + + + + + +indent_align_string = false + +# Whether braces are indented to the body level +indent_braces = false +# Disabled indenting function braces if indent_braces is true +indent_braces_no_func = false +# Disabled indenting class braces if indent_braces is true +indent_braces_no_class = false +# Disabled indenting struct braces if indent_braces is true +indent_braces_no_struct = false +# Indent based on the size of the brace parent, i.e. 'if' => 3 spaces, 'for' => 4 spaces, etc. +indent_brace_parent = false + +indent_namespace = false +indent_extern = false +indent_class = true +indent_class_colon = false +indent_else_if = false +indent_var_def_cont = true + +indent_func_call_param = false +indent_func_def_param = true +indent_func_proto_param = true +indent_func_class_param = false +indent_func_ctor_var_param = false +indent_func_param_double = true + +indent_template_param = false +indent_relative_single_line_comments = false +indent_col1_comment = true +indent_access_spec_body = false +indent_paren_nl = false +indent_comma_paren = false +indent_bool_paren = false +indent_first_bool_expr = false +indent_square_nl = false +indent_preserve_sql = false +indent_align_assign = true + +#align_number_left = true +align_func_params = true +align_same_func_call_params = false +align_var_def_colon = false +align_var_def_attribute = true +align_var_def_inline = true +align_right_cmt_mix = false +align_on_operator = false +align_mix_var_proto = false +align_single_line_func = false +align_single_line_brace = false +align_nl_cont = false +align_left_shift = true +align_oc_decl_colon = false + +nl_collapse_empty_body = true +nl_assign_leave_one_liners = true +nl_class_leave_one_liners = true +nl_enum_leave_one_liners = true +nl_getset_leave_one_liners = true +nl_func_leave_one_liners = true +nl_if_leave_one_liners = true +nl_multi_line_cond = true +nl_multi_line_define = true +nl_before_case = false +nl_after_case = false +nl_after_return = true +nl_after_semicolon = true +nl_after_brace_open = false +nl_after_brace_open_cmt = false +nl_after_vbrace_open = false +nl_after_vbrace_open_empty = false +nl_after_brace_close = false +nl_after_vbrace_close = false +nl_define_macro = false +nl_squeeze_ifdef = false +nl_ds_struct_enum_cmt = false +nl_ds_struct_enum_close_brace = false +nl_create_if_one_liner = false +nl_create_for_one_liner = false +nl_create_while_one_liner = false +ls_for_split_full = true +ls_func_split_full = false +nl_after_multiline_comment = true +eat_blanks_after_open_brace = true +eat_blanks_before_close_brace = true +mod_full_brace_if_chain = true +mod_pawn_semicolon = false +mod_full_paren_if_bool = false +mod_remove_extra_semicolon = false +mod_sort_import = false +mod_sort_using = false +mod_sort_include = false +mod_move_case_break = false +mod_remove_empty_return = true +cmt_indent_multi = true +cmt_c_group = false +cmt_c_nl_start = false +cmt_c_nl_end = false +cmt_cpp_group = false +cmt_cpp_nl_start = false +cmt_cpp_nl_end = false +cmt_cpp_to_c = false +cmt_star_cont = true +cmt_multi_check_last = true +cmt_insert_before_preproc = false +indent_sing_line_comments = 0 +indent_switch_case = 4 +indent_case_shift = 0 + +align_var_def_star_style = 0 +align_var_def_amp_style = 1 +align_assign_span = 1 +align_assign_thresh = 8 +align_enum_equ_span = 3 +align_var_struct_span = 3 +align_var_struct_gap = 1 +align_struct_init_span = 2 +align_right_cmt_span = 2 +align_right_cmt_gap = 1 +align_right_cmt_at_col = 2 + +nl_end_of_file_min = 1 +nl_func_var_def_blk = 0 +nl_after_func_body = 2 +nl_after_func_body_one_liner = 1 +nl_before_block_comment = 2 +nl_after_struct = 1 +mod_full_brace_nl = 1 +mod_add_long_function_closebrace_comment = 32 +mod_add_long_ifdef_endif_comment = 10 +mod_add_long_ifdef_else_comment = 10 +sp_arith = force +sp_assign = force +sp_assign_default = add +sp_enum_assign = force +sp_bool = force +sp_compare = force +sp_before_ptr_star = add +sp_before_unnamed_ptr_star = add +sp_between_ptr_star = remove +sp_after_ptr_star = remove +sp_after_ptr_star_func = force +sp_before_ptr_star_func = force +sp_after_type = force +sp_before_sparen = force +sp_inside_sparen = remove +sp_after_sparen = add +sp_sparen_brace = add +sp_special_semi = remove +sp_before_semi = remove +sp_before_semi_for_empty = remove +sp_after_semi = add +sp_after_semi_for_empty = remove +sp_after_comma = force +sp_before_comma = remove +sp_before_case_colon = remove +sp_after_operator = add +sp_after_operator_sym = add +sp_after_cast = add +sp_inside_paren_cast = remove +sp_sizeof_paren = remove +sp_inside_braces_enum = add +sp_inside_braces_struct = add +sp_inside_braces = add +sp_inside_braces_empty = add +sp_func_proto_paren = remove +sp_func_def_paren = remove +sp_inside_fparens = remove +sp_inside_fparen = remove +sp_fparen_brace = remove +sp_func_call_paren = remove +sp_func_call_paren_empty = remove +sp_func_call_user_paren = remove +sp_return_paren = add +sp_attribute_paren = remove +sp_defined_paren = remove +sp_macro = add +sp_macro_func = add +sp_else_brace = add +sp_brace_else = add +sp_brace_typedef = add +sp_not = remove +sp_inv = remove +sp_addr = remove +sp_member = remove +sp_deref = remove +sp_sign = remove +sp_incdec = remove +sp_before_nl_cont = add +sp_cond_colon = force +sp_cond_question = force +sp_cmt_cpp_start = add +nl_start_of_file = remove +nl_end_of_file = force +nl_assign_brace = remove +nl_assign_square = remove +nl_enum_brace = remove +nl_struct_brace = remove +nl_union_brace = remove +nl_if_brace = remove +nl_brace_else = remove +nl_elseif_brace = remove +nl_else_brace = remove +nl_else_if = remove +nl_for_brace = remove +nl_while_brace = remove +nl_do_brace = remove +nl_brace_while = remove +nl_switch_brace = remove +nl_case_colon_brace = remove +nl_func_type_name = remove +nl_func_proto_type_name = remove +nl_func_paren = remove +nl_func_def_paren = remove +nl_func_decl_empty = remove +nl_func_def_empty = remove +nl_fdef_brace = add +nl_return_expr = remove +pos_arith = lead +pos_assign = trail +pos_bool = trail +pos_conditional = trail +pos_comma = trail +pos_class_comma = lead +pos_class_colon = lead +mod_full_brace_do = remove +mod_full_brace_for = remove +mod_full_brace_function = force +mod_full_brace_while = remove +mod_paren_on_return = ignore diff --git a/sdk/tests/qimage_based_test.h b/sdk/tests/qimage_based_test.h index 42dcd64ac2..453de817fc 100644 --- a/sdk/tests/qimage_based_test.h +++ b/sdk/tests/qimage_based_test.h @@ -1,329 +1,329 @@ /* * Copyright (c) 2011 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __QIMAGE_BASED_TEST_H #define __QIMAGE_BASED_TEST_H #ifndef USE_DOCUMENT #define USE_DOCUMENT 1 #endif /* USE_DOCUMENT */ #include "testutil.h" #include #include #include #include #include #if USE_DOCUMENT #include "KisDocument.h" #include "kis_shape_layer.h" #else #include "kis_filter_configuration.h" #endif /* USE_DOCUMENT */ #include "kis_undo_stores.h" #include "kis_image.h" #include "kis_selection.h" #include "kis_paint_layer.h" #include "kis_adjustment_layer.h" #include "kis_transparency_mask.h" #include "kis_clone_layer.h" #include "filter/kis_filter.h" #include "filter/kis_filter_registry.h" #include "commands/kis_selection_commands.h" namespace TestUtil { class QImageBasedTest { public: QImageBasedTest(const QString &directoryName) : m_directoryName(directoryName) { } // you need to declare your own test function // See KisProcessingTest for example protected: /** * Creates a complex image connected to a surrogate undo store */ KisImageSP createImage(KisSurrogateUndoStore *undoStore) { QImage sourceImage(fetchDataFileLazy("hakonepa.png")); QRect imageRect = QRect(QPoint(0,0), sourceImage.size()); QRect transpRect(50,50,300,300); QRect blurRect(66,66,300,300); QPoint blurShift(34,34); QPoint cloneShift(75,75); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(undoStore, imageRect.width(), imageRect.height(), cs, "merge test"); KisFilterSP filter = KisFilterRegistry::instance()->value("blur"); Q_ASSERT(filter); KisFilterConfigurationSP configuration = filter->defaultConfiguration(); Q_ASSERT(configuration); KisAdjustmentLayerSP blur1 = new KisAdjustmentLayer(image, "blur1", configuration, 0); blur1->internalSelection()->clear(); blur1->internalSelection()->pixelSelection()->select(blurRect); blur1->setX(blurShift.x()); blur1->setY(blurShift.y()); KisPaintLayerSP paintLayer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8); paintLayer1->paintDevice()->convertFromQImage(sourceImage, 0, 0, 0); KisCloneLayerSP cloneLayer1 = new KisCloneLayer(paintLayer1, image, "clone1", OPACITY_OPAQUE_U8); cloneLayer1->setX(cloneShift.x()); cloneLayer1->setY(cloneShift.y()); image->addNode(cloneLayer1); image->addNode(blur1); image->addNode(paintLayer1); KisTransparencyMaskSP transparencyMask1 = new KisTransparencyMask(); transparencyMask1->setName("tmask1"); transparencyMask1->testingInitSelection(transpRect, paintLayer1); image->addNode(transparencyMask1, paintLayer1); return image; } /** * Creates a simple image with one empty layer and connects it to * a surrogate undo store */ KisImageSP createTrivialImage(KisSurrogateUndoStore *undoStore) { QRect imageRect = QRect(0, 0, 640, 441); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(undoStore, imageRect.width(), imageRect.height(), cs, "merge test"); KisPaintLayerSP paintLayer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8); image->addNode(paintLayer1); return image; } void addGlobalSelection(KisImageSP image) { QRect selectionRect(40,40,300,300); - KisSelectionSP selection = new KisSelection(new KisSelectionDefaultBounds(0, image)); + KisSelectionSP selection = new KisSelection(new KisDefaultBounds(image)); KisPixelSelectionSP pixelSelection = selection->pixelSelection(); pixelSelection->select(selectionRect); KUndo2Command *cmd = new KisSetGlobalSelectionCommand(image, selection); image->undoAdapter()->addCommand(cmd); } #if USE_DOCUMENT void addShapeLayer(KisDocument *doc, KisImageSP image) { KisShapeLayerSP shapeLayer = new KisShapeLayer(doc->shapeController(), image.data(), "shape", OPACITY_OPAQUE_U8); image->addNode(shapeLayer); KoShapeFactoryBase *f1 = KoShapeRegistry::instance()->get("StarShape"); KoShapeFactoryBase *f2 = KoShapeRegistry::instance()->get("RectangleShape"); KoShape *shape1 = f1->createDefaultShape(); KoShape *shape2 = f2->createDefaultShape(); shape1->setPosition(QPointF(100,100)); shape2->setPosition(QPointF(200,200)); shapeLayer->addShape(shape1); shapeLayer->addShape(shape2); QApplication::processEvents(); } #endif /* USE_DOCUMENT*/ bool checkLayersInitial(KisImageWSP image, int baseFuzzyness = 0) { QString prefix = "initial_with_selection"; QString prefix2 = findNode(image->root(), "shape") ? "_with_shape" : ""; return checkLayers(image, prefix + prefix2, baseFuzzyness); } bool checkLayersInitialRootOnly(KisImageWSP image, int baseFuzzyness = 0) { QString prefix = "initial_with_selection"; QString prefix2 = findNode(image->root(), "shape") ? "_with_shape" : ""; return checkLayers(image, prefix + prefix2, baseFuzzyness, false); } /** * Checks the content of image's layers against the set of * QImages stored in @p prefix subfolder */ bool checkLayers(KisImageWSP image, const QString &prefix, int baseFuzzyness = 0, bool recursive = true) { QVector images; QVector names; fillNamesImages(image->root(), image->bounds(), images, names, recursive); bool valid = true; const int stackSize = images.size(); for(int i = 0; i < stackSize; i++) { if(!checkOneQImage(images[i], prefix, names[i], baseFuzzyness)) { valid = false; } } return valid; } /** * Checks the content of one image's layer against the QImage * stored in @p prefix subfolder */ bool checkOneLayer(KisImageWSP image, KisNodeSP node, const QString &prefix, int baseFuzzyness = 0) { QVector images; QVector names; fillNamesImages(node, image->bounds(), images, names); return checkOneQImage(images.first(), prefix, names.first(), baseFuzzyness); } // add default bounds param bool checkOneDevice(KisPaintDeviceSP device, const QString &prefix, const QString &name, int baseFuzzyness = 0) { QImage image = device->convertToQImage(0); return checkOneQImage(image, prefix, name, baseFuzzyness); } KisNodeSP findNode(KisNodeSP root, const QString &name) { return TestUtil::findNode(root, name); } private: bool checkOneQImage(const QImage &image, const QString &prefix, const QString &name, int baseFuzzyness) { QString realName = prefix + "_" + name + ".png"; QString expectedName = prefix + "_" + name + "_expected.png"; bool valid = true; QString fullPath = fetchDataFileLazy(m_directoryName + QDir::separator() + prefix + QDir::separator() + realName); if (fullPath.isEmpty()) { // Try without the testname subdirectory fullPath = fetchDataFileLazy(prefix + QDir::separator() + realName); } if (fullPath.isEmpty()) { // Try without the prefix subdirectory fullPath = fetchDataFileLazy(m_directoryName + QDir::separator() + realName); } QImage ref(fullPath); QPoint temp; int fuzzy = baseFuzzyness; { QStringList terms = name.split('_'); if(terms[0] == "root" || terms[0] == "blur1" || terms[0] == "shape") { fuzzy++; } } if(ref != image && !TestUtil::compareQImages(temp, ref, image, fuzzy, fuzzy)) { dbgKrita << "--- Wrong image:" << realName; valid = false; image.save(QString(FILES_OUTPUT_DIR) + QDir::separator() + realName); ref.save(QString(FILES_OUTPUT_DIR) + QDir::separator() + expectedName); } return valid; } void fillNamesImages(KisNodeSP node, const QRect &rc, QVector &images, QVector &names, bool recursive = true) { while (node) { if(node->paintDevice()) { names.append(node->name() + "_paintDevice"); images.append(node->paintDevice()-> convertToQImage(0, rc.x(), rc.y(), rc.width(), rc.height())); } if(node->original() && node->original() != node->paintDevice()) { names.append(node->name() + "_original"); images.append(node->original()-> convertToQImage(0, rc.x(), rc.y(), rc.width(), rc.height())); } if(node->projection() && node->projection() != node->paintDevice()) { names.append(node->name() + "_projection"); images.append(node->projection()-> convertToQImage(0, rc.x(), rc.y(), rc.width(), rc.height())); } if (recursive) { fillNamesImages(node->firstChild(), rc, images, names); } node = node->nextSibling(); } } private: QString m_directoryName; }; } #endif /* __QIMAGE_BASED_TEST_H */