diff --git a/3rdparty/README.md b/3rdparty/README.md index b747d7b77b..b0acc80f6c 100644 --- a/3rdparty/README.md +++ b/3rdparty/README.md @@ -1,289 +1,289 @@ # 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. Using the scripts mentioned above is strongly preferred because that's what the Krita team uses to build the binaries on the binary factory (https://binary-factory.kde.org/). 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://files.kde.org/krita/build/x86_64-7.3.0-release-posix-seh-rt_v5-rev0.7z * 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.9: 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. +4. On Windows, you will also need a release of Python 3.8 (not 3.7, probably not 3.9): 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 2 environment variables set: * `WindowsSdkDir` (typically set to `C:\Program Files (x86)\Windows Kits\10`) * `WindowsSdkVerBinPath` (typically set to `C:\Program Files (x86)\Windows Kits\10\bin\10.0.18362.0`) where 10.0.18362.0 is the version of the Window 10 SDK. * Example: set "WindowsSdkDir=%ProgramFiles(x86)%\Windows Kits\10" set "WindowsSdkVerBinPath=%ProgramFiles(x86)%\Windows Kits\10\bin\10.0.18362.0" THIS IS ALSO NEEDED IF YOU USE THE build.cmd script. ## 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 cmake --build . --config RelWithDebInfo --target ext_quazip ``` 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 ``` Troubleshooting: if you have problems with 'install' step of ext_sip or ext_pyqt, make sure you install it in single thread only (`mingw32-make -j1 install`). Otherwise, a race condition may happen in the post-install script and metadata generation will be started before actual libraries are installed. 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_frameworks/CMakeLists.txt b/3rdparty/ext_frameworks/CMakeLists.txt index e56639ff72..2de72b7378 100644 --- a/3rdparty/ext_frameworks/CMakeLists.txt +++ b/3rdparty/ext_frameworks/CMakeLists.txt @@ -1,226 +1,243 @@ SET(EXTPREFIX_frameworks "${EXTPREFIX}" ) # # All needed frameworks: # # Config # WidgetsAddons # Completion # CoreAddons # GuiAddons # I18n # ItemModels # ItemViews # WindowSystem # kimageformats # On Linux: # KCrash ExternalProject_Add( ext_extra_cmake_modules DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL https://download.kde.org/stable/frameworks/5.64/extra-cmake-modules-5.64.0.zip URL_MD5 e8fa4bba6a534feb9d9e39db036923da PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/ecm_install_to_share.diff INSTALL_DIR ${EXTPREFIX_frameworks} CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${EXTPREFIX_frameworks} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DCMAKE_SYSTEM_PREFIX_PATH=${EXTPREFIX} -DBUILD_TESTING=false UPDATE_COMMAND "" ) ExternalProject_Add( ext_kconfig DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL https://download.kde.org/stable/frameworks/5.64/kconfig-5.64.0.zip URL_MD5 52858f7fdcd17d0c5680acf96dcaae9a PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/kconfig.diff INSTALL_DIR ${EXTPREFIX_frameworks} CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${EXTPREFIX_frameworks} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DCMAKE_SYSTEM_PREFIX_PATH=${EXTPREFIX} -DBUILD_TESTING=false -DKCONFIG_USE_DBUS=off UPDATE_COMMAND "" DEPENDS ext_extra_cmake_modules ) +ExternalProject_Add( + ext_karchive + DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} + URL https://download.kde.org/stable/frameworks/5.64/karchive-5.64.0.zip + URL_MD5 26c7f89bfde97bc85f887f81a2d0f648 + PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/karchive.diff + INSTALL_DIR ${EXTPREFIX_frameworks} + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${EXTPREFIX_frameworks} + -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} + ${GLOBAL_PROFILE} + -DCMAKE_SYSTEM_PREFIX_PATH=${EXTPREFIX} + -DBUILD_TESTING=false + UPDATE_COMMAND "" + DEPENDS ext_extra_cmake_modules +) + + ExternalProject_Add( ext_kwidgetsaddons DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL https://download.kde.org/stable/frameworks/5.64/kwidgetsaddons-5.64.0.zip URL_MD5 d6578f8b5b4bf19fddb8c16918ec0117 INSTALL_DIR ${EXTPREFIX_frameworks} # PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/kwidgetsaddons.diff CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${EXTPREFIX_frameworks} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DCMAKE_SYSTEM_PREFIX_PATH=${EXTPREFIX} -DBUILD_TESTING=false UPDATE_COMMAND "" DEPENDS ext_kconfig ) ExternalProject_Add( ext_kcompletion DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL https://download.kde.org/stable/frameworks/5.64/kcompletion-5.64.0.zip URL_MD5 21fef280c2580da83df289707326d42a INSTALL_DIR ${EXTPREFIX_frameworks} CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${EXTPREFIX_frameworks} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DCMAKE_SYSTEM_PREFIX_PATH=${EXTPREFIX} -DBUILD_TESTING=false UPDATE_COMMAND "" DEPENDS ext_kwidgetsaddons ) ExternalProject_Add( ext_kcoreaddons DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL https://download.kde.org/stable/frameworks/5.64/kcoreaddons-5.64.0.zip URL_MD5 670a74cda110da89d1a7b0fd832fca42 INSTALL_DIR ${EXTPREFIX_frameworks} PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/desktoptojson.diff CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${EXTPREFIX_frameworks} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DCMAKE_SYSTEM_PREFIX_PATH=${EXTPREFIX} -DBUILD_TESTING=false UPDATE_COMMAND "" DEPENDS ext_kcompletion ) ExternalProject_Add( ext_kguiaddons DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL https://download.kde.org/stable/frameworks/5.64/kguiaddons-5.64.0.zip URL_MD5 565dcb39e812463100dddaf3f02bd2a0 INSTALL_DIR ${EXTPREFIX_frameworks} CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${EXTPREFIX_frameworks} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DCMAKE_SYSTEM_PREFIX_PATH=${EXTPREFIX} -DBUILD_TESTING=false UPDATE_COMMAND "" DEPENDS ext_kcoreaddons ) if(APPLE) ExternalProject_Add( ext_ki18n DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL https://download.kde.org/stable/frameworks/5.64/ki18n-5.64.0.zip URL_MD5 75a86675bf2b352b53cbcaece956b486 INSTALL_DIR ${EXTPREFIX_frameworks} PATCH_COMMAND COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/ki18n-appdatalocation.diff CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${EXTPREFIX_frameworks} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DCMAKE_SYSTEM_PREFIX_PATH=${EXTPREFIX} -DBUILD_TESTING=false UPDATE_COMMAND "" DEPENDS ext_kguiaddons ext_gettext ) else() ExternalProject_Add( ext_ki18n DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL https://download.kde.org/stable/frameworks/5.64/ki18n-5.64.0.zip URL_MD5 75a86675bf2b352b53cbcaece956b486 INSTALL_DIR ${EXTPREFIX_frameworks} PATCH_COMMAND COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/ki18n-appdatalocation.diff CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${EXTPREFIX_frameworks} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DCMAKE_SYSTEM_PREFIX_PATH=${EXTPREFIX} -DBUILD_TESTING=false UPDATE_COMMAND "" DEPENDS ext_kguiaddons ) endif() ExternalProject_Add( ext_kitemmodels DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL https://download.kde.org/stable/frameworks/5.64/kitemmodels-5.64.0.zip URL_MD5 e561031cafe7af08de3c62f01fa154b2 INSTALL_DIR ${EXTPREFIX_frameworks} CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${EXTPREFIX_frameworks} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DCMAKE_SYSTEM_PREFIX_PATH=${EXTPREFIX} -DBUILD_TESTING=false UPDATE_COMMAND "" DEPENDS ext_ki18n ) ExternalProject_Add( ext_kitemviews DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL https://download.kde.org/stable/frameworks/5.64/kitemviews-5.64.0.zip URL_MD5 bbd20e563e3f3cbc80252ba89d27962c INSTALL_DIR ${EXTPREFIX_frameworks} CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${EXTPREFIX_frameworks} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DCMAKE_SYSTEM_PREFIX_PATH=${EXTPREFIX} -DBUILD_TESTING=false UPDATE_COMMAND "" DEPENDS ext_kitemmodels ) ExternalProject_Add( ext_kimageformats DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL https://download.kde.org/stable/frameworks/5.64/kimageformats-5.64.0.zip URL_MD5 744090cf90ddde84f51dcabd167dd607 INSTALL_DIR ${EXTPREFIX_frameworks} PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/kimageformats.diff COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/disable_exr.diff CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${EXTPREFIX_frameworks} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DCMAKE_SYSTEM_PREFIX_PATH=${EXTPREFIX} -DBUILD_TESTING=false UPDATE_COMMAND "" DEPENDS ext_kitemviews ) ExternalProject_Add( ext_kwindowsystem DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL https://download.kde.org/stable/frameworks/5.64/kwindowsystem-5.64.0.zip URL_MD5 4e2819edb9d029ff33791d691637065a INSTALL_DIR ${EXTPREFIX_frameworks} PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/kwindowsystem-x11.diff CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${EXTPREFIX_frameworks} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DCMAKE_SYSTEM_PREFIX_PATH=${EXTPREFIX} -DBUILD_TESTING=false UPDATE_COMMAND "" DEPENDS ext_kimageformats ) ExternalProject_Add( ext_kcrash DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL https://download.kde.org/stable/frameworks/5.64/kcrash-5.64.0.zip URL_MD5 2d4d9c074c2e1d19804618d3f291a568 INSTALL_DIR ${EXTPREFIX_frameworks} CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${EXTPREFIX_frameworks} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DCMAKE_SYSTEM_PREFIX_PATH=${EXTPREFIX} -DBUILD_TESTING=false UPDATE_COMMAND "" DEPENDS ext_kwindowsystem ) diff --git a/3rdparty/ext_lcms2/CMakeLists.txt b/3rdparty/ext_lcms2/CMakeLists.txt index 79e77aae03..53bbedd5a5 100644 --- a/3rdparty/ext_lcms2/CMakeLists.txt +++ b/3rdparty/ext_lcms2/CMakeLists.txt @@ -1,27 +1,39 @@ SET(PREFIX_ext_lcms2 "${EXTPREFIX}" ) if (MINGW) ExternalProject_Add( ext_lcms2 DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} - URL http://files.kde.org/krita/build/dependencies/lcms2-2.9.tar.gz - URL_MD5 8de1b7724f578d2995c8fdfa35c3ad0e + URL http://files.kde.org/krita/build/dependencies/lcms2-2.10.tar.gz + URL_MD5 c5f915d681325e0767e40187799f23b1 - PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/lcms2-9.diff + PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/lcms2-10.diff INSTALL_DIR ${PREFIX_ext_lcms2} - CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${PREFIX_ext_lcms2} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DBUILD_TESTS=FALSE -DBUILD_UTILS=FALSE -DBUILD_STATIC=FALSE + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${PREFIX_ext_lcms2} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DBUILD_TESTS=FALSE -DBUILD_UTILS=FALSE -DBUILD_STATIC=FALSE -DBUILD_PLUGINS=FALSE UPDATE_COMMAND "" DEPENDS ext_patch ) +elseif (ANDROID) +ExternalProject_Add( ext_lcms2 + DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} + URL http://files.kde.org/krita/build/dependencies/lcms2-2.10.tar.gz + URL_MD5 c5f915d681325e0767e40187799f23b1 + + PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/lcms2-10.diff + INSTALL_DIR ${PREFIX_ext_lcms2} + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${PREFIX_ext_lcms2} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DBUILD_TESTS=FALSE -DBUILD_UTILS=FALSE -DBUILD_STATIC=FALSE -DBUILD_PLUGINS=FALSE + + UPDATE_COMMAND "" +) else () ExternalProject_Add( ext_lcms2 DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} - URL http://files.kde.org/krita/build/dependencies/lcms2-2.9.tar.gz - URL_MD5 8de1b7724f578d2995c8fdfa35c3ad0e + URL http://files.kde.org/krita/build/dependencies/lcms2-2.10.tar.gz + URL_MD5 c5f915d681325e0767e40187799f23b1 - PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/lcms2-9.diff + PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/lcms2-10.diff INSTALL_DIR ${PREFIX_ext_lcms2} - CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${PREFIX_ext_lcms2} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DBUILD_TESTS=FALSE -DBUILD_UTILS=FALSE -DBUILD_STATIC=FALSE + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${PREFIX_ext_lcms2} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DBUILD_TESTS=FALSE -DBUILD_UTILS=FALSE -DBUILD_STATIC=FALSE -DBUILD_PLUGINS=FALSE UPDATE_COMMAND "" ) endif () diff --git a/3rdparty/ext_lcms2/lcms2-9.diff b/3rdparty/ext_lcms2/lcms2-10.diff similarity index 84% rename from 3rdparty/ext_lcms2/lcms2-9.diff rename to 3rdparty/ext_lcms2/lcms2-10.diff index 841615dd40..698c3764d4 100644 --- a/3rdparty/ext_lcms2/lcms2-9.diff +++ b/3rdparty/ext_lcms2/lcms2-10.diff @@ -1,250 +1,258 @@ -commit e346c86dea71e995623f3a1702ff4ade847c16c7 +commit 5f0c7d33243388e0fc840c5e2aba49e1c6147a21 Author: Boudewijn Rempt -Date: Thu Jan 4 11:49:42 2018 +0100 +Date: Tue Jun 2 12:49:24 2020 +0200 - Add cmake build system to lcms 2.9 + Add cmake build system diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 -index 0000000..735dd7a +index 0000000..213c808 --- /dev/null +++ b/CMakeLists.txt -@@ -0,0 +1,22 @@ +@@ -0,0 +1,25 @@ +project(lcms2) + +option(BUILD_TESTS "build the test executable" OFF) +option(BUILD_STATIC "build the static library" OFF) +option(BUILD_UTILS "build the utilities executables" OFF) ++option(BUILD_PLUGINS "build the plugins" ON) + +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) + +cmake_minimum_required(VERSION 2.6) + +include_directories( +${CMAKE_BINARY_DIR}/include +${CMAKE_SOURCE_DIR}/include +) + +add_subdirectory(src) ++ +if(BUILD_TESTS) + add_subdirectory(testbed) +endif(BUILD_TESTS) ++ +if(BUILD_UTILS) + add_subdirectory(utils) +endif(BUILD_UTILS) diff --git a/include/lcms2.h b/include/lcms2.h -index 9e7ee4c..4ee1b7b 100644 +index 99e0308..3056533 100644 --- a/include/lcms2.h +++ b/include/lcms2.h -@@ -215,15 +215,14 @@ typedef int cmsBool; +@@ -225,15 +225,14 @@ typedef int cmsBool; #endif // CMS_USE_BIG_ENDIAN - // Calling convention -- this is hardly platform and compiler dependent #ifdef CMS_IS_WINDOWS_ -# if defined(CMS_DLL) || defined(CMS_DLL_BUILD) +# if !defined(CMS_STATIC) # ifdef __BORLANDC__ # define CMSEXPORT __stdcall _export # define CMSAPI # else -# define CMSEXPORT __stdcall +# define CMSEXPORT # ifdef CMS_DLL_BUILD # define CMSAPI __declspec(dllexport) # else diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 -index 0000000..ac575c7 +index 0000000..a8a0640 --- /dev/null +++ b/src/CMakeLists.txt -@@ -0,0 +1,64 @@ +@@ -0,0 +1,82 @@ +# some configure checks: +if(WIN32) + if(MSVC) + set(UINT8_T "unsigned char") + set(UINT16_T "unsigned short") + set(UINT32_T "unsigned long") + set(INT8_T "char") + set(INT16_T "short") + set(INT32_T "long") + else(MSVC) + set(UINT8_T "uint8_t") + set(UINT16_T "uint16_t") + set(UINT32_T "uint32_t") + set(INT8_T "int8_t") + set(INT16_T "int16_t") + set(INT32_T "int32_t") + endif(MSVC) +endif(WIN32) + +set(lcms_SRCS + cmscnvrt.c + cmserr.c + cmsgamma.c + cmsgmt.c + cmsintrp.c + cmsio0.c + cmsio1.c + cmslut.c + cmsplugin.c + cmssm.c + cmsmd5.c + cmsmtrx.c + cmspack.c + cmspcs.c + cmswtpnt.c + cmsxform.c + cmssamp.c + cmsnamed.c + cmscam02.c + cmsvirt.c + cmstypes.c + cmscgats.c + cmsps2.c + cmsopt.c + cmshalf.c + cmsalpha.c +) + -+include_directories(${CMAKE_BINARY_DIR}) ++if (BUILD_PLUGINS) ++ set(lcms_SRCS ${lcms_SRCS} ++ ../plugins/fast_float/src/fast_16_tethra.c ++ ../plugins/fast_float/src/fast_8_curves.c ++ ../plugins/fast_float/src/fast_8_matsh.c ++ ../plugins/fast_float/src/fast_8_matsh_sse.c ++ ../plugins/fast_float/src/fast_8_tethra.c ++ ../plugins/fast_float/src/fast_float_15bits.c ++ ../plugins/fast_float/src/fast_float_15mats.c ++ ../plugins/fast_float/src/fast_float_cmyk.c ++ ../plugins/fast_float/src/fast_float_curves.c ++ ../plugins/fast_float/src/fast_float_matsh.c ++ ../plugins/fast_float/src/fast_float_separate.c ++ ../plugins/fast_float/src/fast_float_sup.c ++ ../plugins/fast_float/src/fast_float_tethra.c ++ ) ++endif() ++ ++include_directories(${CMAKE_BINARY_DIR} ../plugins/fast_float/include) + +add_library(lcms SHARED ${lcms_SRCS}) +set_target_properties(lcms PROPERTIES OUTPUT_NAME "lcms2" + DEFINE_SYMBOL CMS_DLL_BUILD) + +if(BUILD_TESTS OR BUILD_UTILS OR BUILD_STATIC) + add_library(lcms_static ${lcms_SRCS}) + set_target_properties(lcms_static PROPERTIES COMPILE_FLAGS -DCMS_STATIC) + set(LCMS_STATIC lcms_static) +endif(BUILD_TESTS OR BUILD_UTILS OR BUILD_STATIC) + +install(TARGETS lcms ${LCMS_STATIC} RUNTIME DESTINATION bin + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib) +install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/../include/lcms2.h ${CMAKE_CURRENT_SOURCE_DIR}/../include/lcms2_plugin.h DESTINATION include) diff --git a/testbed/CMakeLists.txt b/testbed/CMakeLists.txt new file mode 100644 index 0000000..ca9008b --- /dev/null +++ b/testbed/CMakeLists.txt @@ -0,0 +1,5 @@ +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../include ${CMAKE_CURRENT_SOURCE_DIR}/../src) + +add_executable(testcms testcms2.c testplugin.c zoo_icc.c) +target_link_libraries(testcms lcms_static) +set_target_properties(testcms PROPERTIES COMPILE_FLAGS -DCMS_STATIC) -diff --git a/testbed/testcms2.c b/testbed/testcms2.c -index 5bd5447..ff650f2 100644 ---- a/testbed/testcms2.c -+++ b/testbed/testcms2.c -@@ -28,7 +28,7 @@ - #include "testcms2.h" - - // On Visual Studio, use debug CRT --#ifdef _MSC_VER -+#ifdef _WIN32 - # include "crtdbg.h" - # include - #endif diff --git a/testbed/zoo_icc.c b/testbed/zoo_icc.c -index f68861c..fdd0b7c 100755 +index a220db7..b5e253a 100755 --- a/testbed/zoo_icc.c +++ b/testbed/zoo_icc.c @@ -27,6 +27,11 @@ #include "testcms2.h" +#ifdef _WIN32 +# include "crtdbg.h" +# include +#endif + // ZOO checks ------------------------------------------------------------------------------------------------------------ diff --git a/utils/CMakeLists.txt b/utils/CMakeLists.txt new file mode 100644 index 0000000..c3be5bf --- /dev/null +++ b/utils/CMakeLists.txt @@ -0,0 +1,78 @@ +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR}/../include + ${CMAKE_CURRENT_SOURCE_DIR}/../src + ${CMAKE_CURRENT_SOURCE_DIR}/../utils/common +) + +find_package(JPEG) +find_package(TIFF) + +set(UTILS_EXECUTABLES ) +set(UTILS_MANPAGES ) + +############################################################################### +if(JPEG_FOUND) + include_directories(${JPEG_INCLUDE_DIR}) + set(JPGICC_SRCS + jpgicc/jpgicc.c + jpgicc/iccjpeg.c + common/xgetopt.c + common/vprf.c + ) + add_executable(jpgicc ${JPGICC_SRCS}) + target_link_libraries(jpgicc lcms ${JPEG_LIBRARIES}) + list(APPEND UTILS_EXECUTABLES jpgicc) + list(APPEND UTILS_MANPAGES jpgicc/jpgicc.1) +endif(JPEG_FOUND) + +############################################################################### +set(LINKICC_SRCS + linkicc/linkicc.c + common/xgetopt.c + common/vprf.c +) +add_executable(linkicc ${LINKICC_SRCS}) +target_link_libraries(linkicc lcms) +list(APPEND UTILS_EXECUTABLES linkicc) +list(APPEND UTILS_MANPAGES linkicc/linkicc.1) + +############################################################################### +set(PSICC_SRCS + psicc/psicc.c + common/xgetopt.c + common/vprf.c +) +add_executable(psicc ${PSICC_SRCS}) +target_link_libraries(psicc lcms) +list(APPEND UTILS_EXECUTABLES psicc) +list(APPEND UTILS_MANPAGES psicc/psicc.1) + +############################################################################### +if(TIFF_FOUND) + include_directories(${TIFF_INCLUDE_DIR}) + set(JPGICC_SRCS + tificc/tificc.c + common/xgetopt.c + common/vprf.c + ) + add_executable(tificc ${JPGICC_SRCS}) + target_link_libraries(tificc lcms ${TIFF_LIBRARIES}) + list(APPEND UTILS_EXECUTABLES tificc) + list(APPEND UTILS_MANPAGES tificc/tificc.1) +endif(TIFF_FOUND) + +############################################################################### +set(TRANSICC_SRCS + transicc/transicc.c + common/xgetopt.c + common/vprf.c +) +add_executable(transicc ${TRANSICC_SRCS}) +target_link_libraries(transicc lcms) +list(APPEND UTILS_EXECUTABLES transicc) +list(APPEND UTILS_MANPAGES transicc/transicc.1) + +install(TARGETS ${UTILS_EXECUTABLES} RUNTIME DESTINATION bin + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib) +install(FILES ${UTILS_MANPAGES} DESTINATION share/man/man1) diff --git a/krita/data/cursors/cursors.qrc b/krita/data/cursors/cursors.qrc index 46529e3237..1c51ade673 100644 --- a/krita/data/cursors/cursors.qrc +++ b/krita/data/cursors/cursors.qrc @@ -1,27 +1,28 @@ color-picker_image_background.xpm color-picker_image_foreground.xpm color-picker_layer_background.xpm color-picker_layer_foreground.xpm cursor-cross.xpm cursor-pixel-white.xpm cursor-pixel-black.xpm cursor-round.xpm cursor-triangle_lefthanded.xpm cursor-triangle_righthanded.xpm exposure-cursor-gesture.xpm gamma-cursor-gesture.xpm precise-pick-layer-icon.xpm rotate_discrete.xpm rotate_smooth.xpm move-tool.png move-selection.png shear_x.xpm shear_y.xpm zoom_discrete.xpm zoom_smooth.xpm tool_freehand_cursor.png + tool_color_picker_cursor.png diff --git a/krita/data/cursors/tool_color_picker_cursor.png b/krita/data/cursors/tool_color_picker_cursor.png new file mode 100644 index 0000000000..6bed15cc3c Binary files /dev/null and b/krita/data/cursors/tool_color_picker_cursor.png differ diff --git a/krita/data/shaders/overlay_inverted.frag b/krita/data/shaders/overlay_inverted.frag new file mode 100644 index 0000000000..762c8b234c --- /dev/null +++ b/krita/data/shaders/overlay_inverted.frag @@ -0,0 +1,10 @@ +uniform vec4 fragColor; +uniform sampler2D texture0; + +in vec4 v_textureCoordinate; +out vec4 resultFragmentColor; + +void main(void) +{ + resultFragmentColor = fragColor * vec4(vec3(1.0 - texture(texture0, v_textureCoordinate.xy)), 1.0); +} diff --git a/krita/data/shaders/shaders.qrc b/krita/data/shaders/shaders.qrc index c3b076ac46..646a8a1c41 100644 --- a/krita/data/shaders/shaders.qrc +++ b/krita/data/shaders/shaders.qrc @@ -1,17 +1,18 @@ bilinear_gradient.frag conical_gradient.frag conical_symetric_gradient.frag highq_downscale.frag linear_gradient.frag matrix_transform.vert radial_gradient.frag simple_texture.frag square_gradient.frag matrix_transform_legacy.vert simple_texture_legacy.frag solid_color.frag solid_color_legacy.frag + overlay_inverted.frag diff --git a/krita/krita.qrc b/krita/krita.qrc index 4e55340c89..34975a8d2d 100644 --- a/krita/krita.qrc +++ b/krita/krita.qrc @@ -1,75 +1,75 @@ data/kritarc - krita4.xmlgui + krita5.xmlgui pics/broken-preset.png pics/dark_layer-locked.png pics/dark_layer-unlocked.png pics/dark_novisible.svg pics/dark_passthrough-disabled.png pics/dark_passthrough-enabled.png pics/dark_selection-mode_ants.png pics/dark_selection-mode_invisible.png pics/dark_selection-mode_mask.png pics/dark_transparency-disabled.png pics/dark_transparency-enabled.png pics/dark_trim-to-image.png pics/dark_visible.svg pics/delete.png pics/dirty-preset.svg pics/height.png pics/height_icon.png pics/hi16-add_dialog.png pics/hi16-palette_library.png pics/icon-kritasketch-256.png pics/layer-style-disabled.png pics/layer-style-enabled.png pics/light_layer-locked.png pics/light_layer-unlocked.png pics/light_novisible.svg pics/light_passthrough-disabled.png pics/light_passthrough-enabled.png pics/light_selection-mode_ants.png pics/light_selection-mode_invisible.png pics/light_selection-mode_mask.png pics/light_transparency-disabled.png pics/light_transparency-enabled.png pics/light_trim-to-image.png pics/light_visible.svg pics/linked.png pics/local_selection_active.png pics/local_selection_inactive.png pics/mirrorAxis-HorizontalMove.png pics/mirrorAxis-VerticalMove.png pics/novisible.svg pics/offset_horizontal.png pics/offset_vertical.png pics/ratio_icon.png pics/select_pixel.png pics/select_shape.png pics/selection_add.png pics/selection_symmetric_difference.png pics/selection_intersect.png pics/selection_replace.png pics/selection_subtract.png pics/shade.png pics/shear_horizontal.png pics/shear_vertical.png pics/sidebaricon.png pics/tablet.png pics/tool_screenshot.png pics/transparency-locked.png pics/transparency-unlocked.png pics/unlinked.png pics/visible.svg pics/width.png pics/width_icon.png pics/workspace-chooser.png diff --git a/krita/krita4.xmlgui b/krita/krita5.xmlgui similarity index 99% rename from krita/krita4.xmlgui rename to krita/krita5.xmlgui index 106802d024..e325777399 100644 --- a/krita/krita4.xmlgui +++ b/krita/krita5.xmlgui @@ -1,412 +1,413 @@ &File &Edit Fill Special &View &Canvas &Snap To &Image &Rotate &Layer &New &Import/Export Import &Convert &Select &Group &Transform &Rotate Transform &All Layers &Rotate S&plit S&plit Alpha &Select Select &Opaque Filte&r &Tools Scripts Setti&ngs + &Help File Brushes and Stuff diff --git a/krita/kritamenu.action b/krita/kritamenu.action index bc5a1844e8..af463440b6 100644 --- a/krita/kritamenu.action +++ b/krita/kritamenu.action @@ -1,1813 +1,1823 @@ File document-new &New... Create new document New 0 0 Ctrl+N false document-open &Open... Open an existing document Open 0 0 Ctrl+O false document-open-recent Open &Recent Open a document which was recently opened Open Recent 1 0 false document-save &Save Save Save 1 0 Ctrl+S false document-save-as Save &As... Save document under a new name Save As 1 0 Ctrl+Shift+S false Sessions... Open session manager Sessions 0 0 false document-import Open ex&isting Document as Untitled Document... Open existing Document as Untitled Document Open existing Document as Untitled Document 0 0 false document-export E&xport... Export Export 1 0 false Import animation frames... Import animation frames Import animation frames 1 0 false &Render Animation... Render Animation to GIF, Image Sequence or Video Render Animation 1000 0 false &Render Animation Again Render Animation Again Render Animation 1000 0 false Save Incremental &Version Save Incremental Version Save Incremental Version 1 0 Ctrl+Alt+S false Save Incremental &Backup Save Incremental Backup Save Incremental Backup 1 0 F4 false &Create Template From Image... Create Template From Image Create Template From Image 1 0 false Create Copy &From Current Image Create Copy From Current Image Create Copy From Current Image 1 0 false configure &Document Information Document Information Document Information 1 0 false &Close All Close All Close All 1 0 Ctrl+Shift+W false C&lose Close Close 1 0 Ctrl+W false &Quit Quit application Quit 0 0 Ctrl+Q false Edit edit-undo Undo Undo last action Undo 1 0 Ctrl+Z false edit-redo Redo Redo last undone action Redo 1 0 Ctrl+Shift+Z false edit-cut Cu&t Cut selection to clipboard Cut 0 0 Ctrl+X false edit-copy &Copy Copy selection to clipboard Copy 0 0 Ctrl+C false C&opy (sharp) Copy (sharp) Copy (sharp) 100000000 0 false Cut (&sharp) Cut (sharp) Cut (sharp) 100000000 0 false Copy &merged Copy merged Copy merged 100000000 0 Ctrl+Shift+C false edit-paste &Paste Paste clipboard content Paste 0 0 Ctrl+V false Paste at Cursor Paste at cursor Paste at cursor 0 0 Ctrl+Alt+V false Paste into &New Image Paste into New Image Paste into New Image 0 0 Ctrl+Shift+N false Paste as R&eference Image Paste as Reference Image Paste as Reference Image 1 0 Ctrl+Shift+R false edit-clear C&lear Clear Clear 1 0 Del false &Fill with Foreground Color Fill with Foreground Color Fill with Foreground Color 10000 1 Shift+Backspace false Fill &with Background Color Fill with Background Color Fill with Background Color 10000 1 Backspace false F&ill with Pattern Fill with Pattern Fill with Pattern 10000 1 false Fill Special Fill with Foreground Color (Opacity) Fill with Foreground Color (Opacity) Fill with Foreground Color (Opacity) 10000 1 Ctrl+Shift+Backspace false Fill with Background Color (Opacity) Fill with Background Color (Opacity) Fill with Background Color (Opacity) 10000 1 Ctrl+Backspace false Fill with Pattern (Opacity) Fill with Pattern (Opacity) Fill with Pattern (Opacity) 10000 1 false Stro&ke selected shapes Stroke selected shapes Stroke selected shapes 1000000000 0 false Stroke Selec&tion... Stroke selection Stroke selection 10000000000 0 false Delete keyframe Delete keyframe Delete keyframe 100000 0 false Window window-new &New Window New Window New Window 0 0 false N&ext Next Next 10 0 false Previous Previous Previous false View document-new &Show Canvas Only Show just the canvas or the whole window Show Canvas Only 0 0 Tab true view-fullscreen F&ull Screen Mode Display the window in full screen Full Screen Mode 0 0 Ctrl+Shift+F true Detach canvas Show the canvas on a separate window Detach canvas 0 0 true &Wrap Around Mode Wrap Around Mode Wrap Around Mode 1 0 true &Instant Preview Mode Instant Preview Mode Instant Preview Mode 1 0 Shift+L true Soft Proofing Turns on Soft Proofing Turns on Soft Proofing Ctrl+Y true Out of Gamut Warnings Turns on warnings for colors out of proofed gamut, needs soft proofing to be turned on. Turns on warnings for colors out of proofed gamut, needs soft proofing to be turned on. Ctrl+Shift+Y true mirror-view Mirror View Mirror View Mirror View M false zoom-original &Reset zoom Reset zoom Reset zoom 1 0 Ctrl+0 false zoom-in Zoom &In Zoom In 0 0 Ctrl++ false zoom-out Zoom &Out Zoom Out 0 0 Ctrl+- false rotate-canvas-right Rotate &Canvas Right Rotate Canvas Right Rotate Canvas Right 1 0 Ctrl+] false rotate-canvas-left Rotate Canvas &Left Rotate Canvas Left Rotate Canvas Left 1 0 Ctrl+[ false rotation-reset Reset Canvas Rotation Reset Canvas Rotation Reset Canvas Rotation 1 0 false Show &Rulers The rulers show the horizontal and vertical positions of the mouse on the image and can be used to position your mouse at the right place on the canvas. <p>Uncheck this to hide the rulers.</p> Show Rulers Show Rulers 1 0 true Rulers Track Pointer The rulers will track current mouse position and show it on screen. It can cause suptle performance slowdown Rulers Track Pointer Rulers Track Pointer 1 0 true Show Guides Show or hide guides Show Guides 1 0 true Lock Guides Lock or unlock guides Lock Guides 1 0 true Snap to Guides Snap cursor to guides position Snap to Guides 1 0 true Show Status &Bar Show or hide the status bar Show Status Bar 0 0 true Show Pixel Grid Show Pixel Grid Show Pixel Grid 1000 1000 true view-grid Show &Grid Show Grid Show Grid 1000 0 Ctrl+Shift+' true Snap To Grid Snap To Grid Snap To Grid 1000 Ctrl+Shift+; true Show Snap Options Popup Show Snap Options Popup Show Snap Options Popup 1000 Shift+s false Snap Orthogonal Snap Orthogonal Snap Orthogonal 1000 true Snap Node Snap Node Snap Node 1000 true Snap Extension Snap Extension Snap Extension 1000 true Snap Pixel Snap Pixel Snap Pixel 1000 true Snap Intersection Snap Intersection Snap Intersection 1000 true Snap Bounding Box Snap Bounding Box Snap Bounding Box 1000 true Snap Image Bounds Snap Image Bounds Snap Image Bounds 1000 true Snap Image Center Snap Image Center Snap Image Center 1000 true S&how Painting Assistants Show Painting Assistants Show Painting Assistants 1000 0 true Show &Assistant Previews Show Assistant Previews Show Assistant Previews 1000 0 true S&how Reference Images Show Reference Images Show Reference Images 1000 0 true Image document-properties &Properties... Properties Properties 1000 0 false format-stroke-color &Image Background Color and Transparency... Change the background color of the image Image Background Color and Transparency 1000 0 false &Convert Image Color Space... Convert Image Color Space Convert Image Color Space 1000 0 false trim-to-image &Trim to Image Size Trim to Image Size Trim to Image Size 1 0 false Trim to Current &Layer Trim to Current Layer Trim to Current Layer 100000 0 false Trim to S&election Trim to Selection Trim to Selection 100000000 0 false &Rotate Image... Rotate Image Rotate Image 1000 0 false object-rotate-right Rotate &Image 90° to the Right Rotate Image 90° to the Right Rotate Image 90° to the Right 1000 0 false object-rotate-left Rotate Image &90° to the Left Rotate Image 90° to the Left Rotate Image 90° to the Left 1000 0 false Rotate Image &180° Rotate Image 180° Rotate Image 180° 1000 0 false &Shear Image... Shear Image Shear Image 1000 0 false symmetry-horizontal &Mirror Image Horizontally Mirror Image Horizontally Mirror Image Horizontally 1000 0 false symmetry-vertical Mirror Image &Vertically Mirror Image Vertically Mirror Image Vertically 1000 0 false Scale Image To &New Size... Scale Image To New Size Scale Image To New Size 1000 0 Ctrl+Alt+I false &Offset Image... Offset Image Offset Image 1000 0 false R&esize Canvas... Resize Canvas Resize Canvas 1000 0 Ctrl+Alt+C false Im&age Split Image Split Image Split 1000 0 false Separate Ima&ge... Separate Image Separate Image 1000 0 false Select edit-select-all Select &All Select All Select All 0 0 Ctrl+A false edit-select-none &Deselect Deselect Deselect 1100000000 0 Ctrl+Shift+A false &Reselect Reselect Reselect 0 0 Ctrl+Shift+D false &Convert to Vector Selection Convert to Vector Selection Convert to Vector Selection 100000000000000000 0 false &Convert to Raster Selection Convert to Raster Selection Convert to Raster Selection 10000000000000000 0 false Edit Selection Edit Selection Edit Selection 10000000000 100 false Convert Shapes to &Vector Selection Convert Shapes to Vector Selection Convert Shapes to Vector Selection 1000000000 0 false &Feather Selection... Feather Selection Feather Selection 10000000000 100 Shift+F6 false Dis&play Selection Display Selection Display Selection 1000 0 Ctrl+H true Sca&le... Scale Scale 100000000 100 false S&elect from Color Range... Select from Color Range Select from Color Range 10000 100 false Select &Opaque (Replace) Select Opaque Select Opaque 1 100 false Select Opaque (&Add) Select Opaque (Add) Select Opaque (Add) 1 100 false Select Opaque (&Subtract) Select Opaque (Subtract) Select Opaque (Subtract) 1 100 false Select Opaque (&Intersect) Select Opaque (Intersect) Select Opaque (Intersect) 1 100 false &Grow Selection... Grow Selection Grow Selection 10000000000 100 false S&hrink Selection... Shrink Selection Shrink Selection 10000000000 100 false &Border Selection... Border Selection Border Selection 10000000000 100 false S&mooth Smooth Smooth 10000000000 100 false Filter &Apply Filter Again Apply Filter Again Apply Filter Again 0 0 Ctrl+F false Adjust Adjust Adjust false Artistic Artistic Artistic false Blur Blur Blur false Colors Colors Colors false Edge Detection Edge Detection Edge Detection false Enhance Enhance Enhance false Emboss Emboss Emboss false Map Map Map false Other Other Other false gmic Start G'MIC-Qt Start G'Mic-Qt Start G'Mic-Qt false gmic Re-apply the last G'MIC filter Apply the last G'Mic-Qt action again Apply the last G'Mic-Qt action again false Settings configure &Configure Krita... Configure Krita Configure Krita 0 0 false &Manage Resources... Manage Resources Manage Resources 0 0 false preferences-desktop-locale Switch Application &Language... Switch Application Language Switch Application Language false &Show Dockers Show Dockers Show Dockers 0 0 true configure Configure Tool&bars... Configure Toolbars Configure Toolbars 0 0 false Dockers Dockers Dockers false &Themes Themes Themes false + + + &Styles + + Styles + Styles + + false + + im-user Active Author Profile Active Author Profile Active Author Profile Reset Krita Configurations Reset Krita Configurations Reset Krita Configurations false configure-shortcuts Configure S&hortcuts... Configure Shortcuts Configure Shortcuts 0 0 false &Window Window Window false Help help-contents Krita &Handbook Krita Handbook Krita Handbook F1 false tools-report-bug &Report Bug... Report Bug Report Bug false krita &About Krita About Krita About Krita false kde About &KDE About KDE About KDE false Brushes and Stuff &Gradients Gradients Gradients false &Patterns Patterns Patterns false &Color Color Color false &Painter's Tools Painter's Tools Painter's Tools false Brush composite Brush composite Brush composite false Brush option slider 1 Brush option slider 1 Brush option slider 1 false Brush option slider 2 Brush option slider 2 Brush option slider 2 false Brush option slider 3 Brush option slider 3 Brush option slider 3 false Mirror Mirror Mirror false Layouts Select layout false Workspaces Workspaces Workspaces false diff --git a/krita/main.cc b/krita/main.cc index 3f57d0d924..27302bc42e 100644 --- a/krita/main.cc +++ b/krita/main.cc @@ -1,602 +1,603 @@ /* * 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 enableOpenGLDebug = false; bool openGLDebugSynchronous = false; bool logUsage = true; { 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 QLocale locale(language.split(":").first()); QLocale::setDefault(locale); #ifdef Q_OS_MAC // prevents python >=3.7 nl_langinfo(CODESET) fail bug 417312. qputenv("LANG", (locale.name() + ".UTF-8").toLocal8Bit()); #else qputenv("LANG", locale.name().toLocal8Bit()); #endif 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"; } } if (uiLanguages.size() > 0 ) { QString envLanguage = uiLanguages.first(); envLanguage.replace(QChar('-'), QChar('_')); for (int i = 0; i < uiLanguages.size(); i++) { QString uiLanguage = uiLanguages[i]; // Strip the country code int idx = uiLanguage.indexOf(QChar('-')); if (idx != -1) { uiLanguage = uiLanguage.left(idx); uiLanguages.replace(i, uiLanguage); } } dbgKrita << "Converted ui languages:" << uiLanguages; #ifdef Q_OS_MAC // See https://bugs.kde.org/show_bug.cgi?id=396370 KLocalizedString::setLanguages(QStringList() << uiLanguages.first()); qputenv("LANG", (envLanguage + ".UTF-8").toLocal8Bit()); #else KLocalizedString::setLanguages(QStringList() << uiLanguages); qputenv("LANG", envLanguage.toLocal8Bit()); #endif } } } #if defined Q_OS_WIN && defined USE_QT_TABLET_WINDOWS && defined QT_HAS_WINTAB_SWITCH const bool forceWinTab = !KisConfig::useWin8PointerInputNoApp(&kritarc); QCoreApplication::setAttribute(Qt::AA_MSWindowsUseWinTabAPI, forceWinTab); if (qEnvironmentVariableIsEmpty("QT_WINTAB_DESKTOP_RECT") && qEnvironmentVariableIsEmpty("QT_IGNORE_WINTAB_MAPPING")) { QRect customTabletRect; KisDlgCustomTabletResolution::Mode tabletMode = KisDlgCustomTabletResolution::getTabletMode(&customTabletRect); KisDlgCustomTabletResolution::applyConfiguration(tabletMode, customTabletRect); } #endif // first create the application so we can create a pixmap KisApplication app(key, argc, argv); KisUsageLogger::writeHeader(); KisOpenGL::initialize(); #ifdef HAVE_SET_HAS_BORDER_IN_FULL_SCREEN_DEFAULT if (QCoreApplication::testAttribute(Qt::AA_UseDesktopOpenGL)) { QWindowsWindowFunctions::setHasBorderInFullScreenDefault(true); } #endif if (!language.isEmpty()) { if (rightToLeft) { app.setLayoutDirection(Qt::RightToLeft); } else { app.setLayoutDirection(Qt::LeftToRight); } } KLocalizedString::setApplicationDomain("krita"); dbgKrita << "Available translations" << KLocalizedString::availableApplicationTranslations(); dbgKrita << "Available domain translations" << KLocalizedString::availableDomainTranslations("krita"); #ifdef Q_OS_WIN QDir appdir(KoResourcePaths::getApplicationRoot()); QString path = qgetenv("PATH"); qputenv("PATH", QFile::encodeName(appdir.absolutePath() + "/bin" + ";" + appdir.absolutePath() + "/lib" + ";" + appdir.absolutePath() + "/Frameworks" + ";" + appdir.absolutePath() + ";" + path)); dbgKrita << "PATH" << qgetenv("PATH"); #endif if (qApp->applicationDirPath().contains(KRITA_BUILD_DIR)) { qFatal("FATAL: You're trying to run krita from the build location. You can only run Krita from the installation location."); } #if defined HAVE_KCRASH KCrash::initialize(); #elif defined USE_DRMINGW tryInitDrMingw(); #endif KisApplicationArguments args(app); if (app.isRunning()) { // only pass arguments to main instance if they are not for batch processing // any batch processing would be done in this separate instance const bool batchRun = args.exportAs() || args.exportSequence(); if (!batchRun) { QByteArray ba = args.serialize(); if (app.sendMessage(ba)) { return 0; } } } if (!runningInKDE) { // Icons in menus are ugly and distracting app.setAttribute(Qt::AA_DontShowIconsInMenus); } #if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) app.setAttribute(Qt::AA_DisableWindowContextHelpButton); #endif app.installEventFilter(KisQtWidgetsTweaker::instance()); if (!args.noSplash()) { // then create the pixmap from an xpm: we cannot get the // location of our datadir before we've started our components, // so use an xpm. QDate currentDate = QDate::currentDate(); QWidget *splash = 0; if (currentDate > QDate(currentDate.year(), 12, 4) || currentDate < QDate(currentDate.year(), 1, 9)) { splash = new KisSplashScreen(app.applicationVersion(), QPixmap(splash_holidays_xpm), QPixmap(splash_holidays_x2_xpm)); } else { splash = new KisSplashScreen(app.applicationVersion(), QPixmap(splash_screen_xpm), QPixmap(splash_screen_x2_xpm)); } app.setSplashScreen(splash); } #if defined Q_OS_WIN KisConfig cfg(false); bool supportedWindowsVersion = true; QOperatingSystemVersion osVersion = QOperatingSystemVersion::current(); if (osVersion.type() == QOperatingSystemVersion::Windows) { if (osVersion.majorVersion() >= QOperatingSystemVersion::Windows7.majorVersion()) { supportedWindowsVersion = true; } else { supportedWindowsVersion = false; if (cfg.readEntry("WarnedAboutUnsupportedWindows", false)) { QMessageBox::information(0, i18nc("@title:window", "Krita: Warning"), i18n("You are running an unsupported version of Windows: %1.\n" "This is not recommended. Do not report any bugs.\n" "Please update to a supported version of Windows: Windows 7, 8, 8.1 or 10.", osVersion.name())); cfg.writeEntry("WarnedAboutUnsupportedWindows", true); } } } #ifndef USE_QT_TABLET_WINDOWS { if (cfg.useWin8PointerInput() && !KisTabletSupportWin8::isAvailable()) { cfg.setUseWin8PointerInput(false); } if (!cfg.useWin8PointerInput()) { bool hasWinTab = KisTabletSupportWin::init(); if (!hasWinTab && supportedWindowsVersion) { if (KisTabletSupportWin8::isPenDeviceAvailable()) { // Use WinInk automatically cfg.setUseWin8PointerInput(true); } else if (!cfg.readEntry("WarnedAboutMissingWinTab", false)) { if (KisTabletSupportWin8::isAvailable()) { QMessageBox::information(nullptr, i18n("Krita Tablet Support"), i18n("Cannot load WinTab driver and no Windows Ink pen devices are found. If you have a drawing tablet, please make sure the tablet driver is properly installed."), QMessageBox::Ok, QMessageBox::Ok); } else { QMessageBox::information(nullptr, i18n("Krita Tablet Support"), i18n("Cannot load WinTab driver. If you have a drawing tablet, please make sure the tablet driver is properly installed."), QMessageBox::Ok, QMessageBox::Ok); } cfg.writeEntry("WarnedAboutMissingWinTab", true); } } } if (cfg.useWin8PointerInput()) { KisTabletSupportWin8 *penFilter = new KisTabletSupportWin8(); if (penFilter->init()) { // penFilter.registerPointerDeviceNotifications(); app.installNativeEventFilter(penFilter); dbgKrita << "Using Win8 Pointer Input for tablet support"; } else { dbgKrita << "No Win8 Pointer Input available"; delete penFilter; } } } #elif defined QT_HAS_WINTAB_SWITCH // Check if WinTab/WinInk has actually activated const bool useWinTabAPI = app.testAttribute(Qt::AA_MSWindowsUseWinTabAPI); if (useWinTabAPI != !cfg.useWin8PointerInput()) { cfg.setUseWin8PointerInput(useWinTabAPI); } #endif #endif app.setAttribute(Qt::AA_CompressHighFrequencyEvents, false); // Set up remote arguments. QObject::connect(&app, SIGNAL(messageReceived(QByteArray,QObject*)), &app, SLOT(remoteArguments(QByteArray,QObject*))); QObject::connect(&app, SIGNAL(fileOpenRequest(QString)), &app, SLOT(fileOpenRequested(QString))); // Hardware information KisUsageLogger::writeSysInfo("\nHardware Information\n"); KisUsageLogger::writeSysInfo(QString(" GPU Acceleration: %1").arg(kritarc.value("OpenGLRenderer", "auto").toString())); KisUsageLogger::writeSysInfo(QString(" Memory: %1 Mb").arg(KisImageConfig(true).totalRAM())); KisUsageLogger::writeSysInfo(QString(" Number of Cores: %1").arg(QThread::idealThreadCount())); KisUsageLogger::writeSysInfo(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 d0ab80a54e..e579137447 100644 --- a/krita/org.kde.krita.appdata.xml +++ b/krita/org.kde.krita.appdata.xml @@ -1,480 +1,486 @@ 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 sihtasutus Krita Fundazioa Krita Foundation - La Fondation Krita + La fondation Krita Fundación Krita Krita Foundation (Fundatiomn de 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 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 til 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 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 El Krita és un estudi de pintura digital ple de funcionalitats El Krita és un estudi de pintura digital ple de funcionalitats Krita is a full-featured digital painting studio Krita es un completo estudio de dibujo digital Krita on rohkete võimalustega digitaalkunstistuudio Krita pintura-digital lantegi osoa bat da + Krita est un studio d'art numérique complet. Krita è uno studio d'arte digitale completo Krita는 다기능 디지털 예술 스튜디오입니다 Krita is een digitale schilderstudio vol mogelijkheden Krita er ei funksjonsrik digital teiknestove. O Krita é um estúdio de arte digital completo O Krita é um estúdio de pintura digital completo Krita är en fullfjädrad digital konststudio Krita — повноцінний комплекс для цифрового малювання xxKrita is a full-featured digital painting studioxx 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 també ofereix les últimes notícies sobre el Krita La finestra d'inici també ofereix les últimes notícies sobre el 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 Käivitusaken jagab nüüd ka Krita värskemaid uudiseid Abioko leihoak orain Krita-ri buruzko albiste berrienak ematen dizkizu + Maintenant, la fenêtre de démarrage vous donne aussi les dernières informations concernant 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. 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 Startfönstret ger nu också senaste nytt om Krita У початковому вікні програми ви можете бачити найсвіжіші новини щодо Krita xxThe startup window now also gives you the latest news about Kritaxx 它的启动画面可以向你展示 Krita 的最新官方新闻。 開始視窗也提供給您關於 Krita 的最新消息 https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_002.png There are over ten immensely powerful brush engines Hi ha més de deu motors de pinzell immensament potents Hi ha més de deu motors de pinzell immensament potents There are over ten immensely powerful brush engines Existen unos diez inmensamente potentes motores de pinceles Üle kümne ääretult võimeka pintslimootori Hamarretik gora isipu motor ikaragarri ahaltsu daude + Il y a plus de 10 moteurs de brosse, tous extrêmement puissants 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. Existem mais de dez motores de pincéis extremamente poderosos Mais de dez engines de pincéis incrivelmente poderosos disponíveis Det finns mer än tio enormt kraftfulla penselgränssnitt У програмі передбачено понад десяток надзвичайно потужних рушіїв пензлів xxThere are over ten immensely powerful brush enginesxx 它内建了超过十种功能强大的笔刷引擎。 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 Värviulatuse maskide loomine ja kasutamine piltidele kooskõlalise välimuse andmiseks Sortu eta erabili gama-maskarak zure irudiei izaera koherentea emateko + Créer et utiiser les masques de Gamut pour donner à vos images une apparence cohérente 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. 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 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 feelxx 它支持建立并使用色域蒙版,让图像的颜色选用更有条理。 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 tot 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 Sind huvitab animatsioon? Krita pakub kõike, mida läheb tarvis traditsioonilise käsitsi loodud animatsiooni jaoks Animaziorako? Krita-k ohiko eskuz-marraztutako animazioetarako behar duzun guztia dakar + Vous réalisez des animations ? Krita vous fournit tout ce dont vous avez besoin pour l'animation traditionnelle et dessinée à la main 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. 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 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 animationxx Krita 还提供了手绘动画制作所需的全套工具和面板。 想做動畫?Krita 提供您在傳統、手繪動畫所需的任何東西 https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_005.png If you're new to digital painting, there's an extensive, up-to-date manual Si sou nou a la pintura digital, hi ha un manual extens i actualitzat Si sou nou a la pintura digital, hi ha un manual extens i actualitzat If you're new to digital painting, there's an extensive, up-to-date manual Si está empezando con el dibujo digital, dispone de un extenso y actualizado manual Kui oled digitaalkunstis alles uustulnuk, on meil välja pakkuda mahukas ajakohane käsiraamat Pintura digitalean berria bazara, gaurkotutako eskuliburu zabal bat dago + Si vous êtes nouveau en art graphique, il y a un manuel à jour et très fourni Se sei nuovo del disegno digitale è disponibile un manuale completo e aggiornato 디지털 페인팅을 처음 시작하시거나, Krita의 기능을 더 알아 보려면 사용 설명서를 참조하십시오 Als u nieuw bent in digitaal schilderen, dan is er een uitgebreide, bijgewerkte handleiding. Viss du er nybegynnar innan digital teikning, finst det ei omfattande og oppdatert brukarhandbok. 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, há um extenso e atualizado manual Om digital målning är nytt för dig, finns en omfattande, aktuell handbok Якщо ви не маєте достатнього досвіду у цифровому малюванні, скористайтеся нашим докладним і актуальним підручником xxIf you're new to digital painting, there's an extensive, up-to-date manualxx 如果你是数字绘画的初学者,我们还准备了内容详尽,及时更新的使用手册。 https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_006.png Graphics 2DGraphics RasterGraphics KDE krita org.kde.krita.desktop https://krita.org/en/item/krita-4-2-9-released/ https://download.kde.org/stable/krita/4.2.9/krita-4.2.9-x86_64.appimage 4b23574456338b4f5e2d7e6ba66ca5dce2d6924468fe6613bc468674ebec77d4 207515688 krita-4.2-9 https://download.kde.org/stable/krita/4.2.9/gmic_krita_qt-x86_64.appimage 5182b77ff35de7d9aa850ffbe6fd953b2eb7c20d39cf56302a938cab9c2497a0 32002088 gmic_krita_qt-4.2-9 https://download.kde.org/stable/krita/4.2.9/krita-4.2.9.dmg 34b606dcdbdf1c3702cebc924b9c11e1c9181ca6a9d5fa91e605bbfca554df9b 195113969 https://download.kde.org/stable/krita/4.2.9/krita-x64-4.2.9-setup.exe fd8345a4d4170c62e410f8cfe1547bb811eb312de953a540b3bf12bb79137b9f 110753224 https://download.kde.org/stable/krita/4.2.9/krita-x86-4.2.9-setup.exe c8efe945804ec9f08f019853c60895e390edfeb03bde13874b4d05231c03261d 110315944 https://download.kde.org/stable/krita/4.2.9/krita-4.2.9.tar.xz 4ef711887dd3ec5f2a1c42a80f2fd0fec1de0d4f3d0147b0efd418ac6e4d7567 170082028 https://krita.org/en/item/krita-4-2-8-released/ https://download.kde.org/stable/krita/4.2.8/krita-4.2.8-x86_64.appimage 4b23574456338b4f5e2d7e6ba66ca5dce2d6924468fe6613bc468674ebec77d4 207515688 krita-4.2-8 https://download.kde.org/stable/krita/4.2.8/gmic_krita_qt-x86_64.appimage e48ac43f86a22b7015ee2dc5ce4f35a72f22070150d926553ab0aafc8616a08f 32944104 gmic_krita_qt-4.2-8 https://download.kde.org/stable/krita/4.2.9/krita-4.2.9.dmg 34b606dcdbdf1c3702cebc924b9c11e1c9181ca6a9d5fa91e605bbfca554df9b 195113969 https://download.kde.org/stable/krita/4.2.8/krita-x64-4.2.8-setup.exe dca15dad13684622ae2704d77b34f0662bc109b7b6e3e7393a549b6418fcd419 109267000 https://download.kde.org/stable/krita/4.2.8/krita-x86-4.2.8-setup.exe 94fbdee4a923682fd64c0010b17ebd002e52de8f2ae239e611f3bda60d8abdee 107842760 https://download.kde.org/stable/krita/4.2.8/krita-4.2.8.2.tar.xz 1c3bb8a28ef8f7945e5f21f9ad87e01d8b831eea3487ff92742c930f3b7f744a 169994064 https://krita.org/en/item/krita-4-2-7-released/ https://download.kde.org/stable/krita/4.2.7.1/krita-4.2.7.1b-x86_64.appimage ddaeb8e02bad9d09fd3fc2d4ecf7ee677c3786cb13bb8e50eeaebf00e573f2d9 192188392 krita-4.2-7-1 https://download.kde.org/stable/krita/4.2.7.1/gmic_krita_qt-x86_64.appimage ea6e151399e850feb6e177ab52a40b32d9070888339f0c35c92ceff17816cae1 32968680 gmic_krita_qt-4-2-7-1 https://download.kde.org/stable/krita/4.2.7.1/krita-4.2.7.1.dmg eca62444e27ed51b177e75e9e674d726285e58483b41a37fa2b0d0ad2a8b34ba 173627205 https://download.kde.org/stable/krita/4.2.7.1/krita-x64-4.2.7.1-setup.exe 31538a959e7b271cf7c166f4e1e6162e5958e9b54055c4ecb1f50335fab2f01f 109190240 https://download.kde.org/stable/krita/4.2.8/krita-x86-4.2.8-setup.exe 3cb92f77e0913a20e2e02a0ba953460a7e39ad0e7a24b9530fc707f6808e8fec 107846008 https://www.microsoft.com/store/apps/9n6x57zgrw96
diff --git a/krita/pics/svg/dark_python.svg b/krita/pics/svg/dark_python.svg new file mode 100644 index 0000000000..72fbb7f2e9 --- /dev/null +++ b/krita/pics/svg/dark_python.svg @@ -0,0 +1,88 @@ + + + + + + + + + image/svg+xml + + + 2015 + + + Timothée Giet + + + + + + + + + + + + + + + + + diff --git a/krita/pics/svg/light_python.svg b/krita/pics/svg/light_python.svg new file mode 100644 index 0000000000..9617a92a0c --- /dev/null +++ b/krita/pics/svg/light_python.svg @@ -0,0 +1,92 @@ + + + + + + + + + image/svg+xml + + + 2015 + + + Timothée Giet + + + + + + + + + + + + + + + + + + diff --git a/krita/pics/svg/svg-icons.qrc b/krita/pics/svg/svg-icons.qrc index 13f02593e8..3efa01283a 100644 --- a/krita/pics/svg/svg-icons.qrc +++ b/krita/pics/svg/svg-icons.qrc @@ -1,159 +1,161 @@ broken-preset.svgz dark_addblankframe.svg dark_addcolor.svg dark_addduplicateframe.svg dark_deletekeyframe.svg dark_docker_lock_a.svg dark_docker_lock_b.svg dark_layer-locked.svg dark_layer-unlocked.svg dark_nextframe.svg dark_nextkeyframe.svg dark_lastframe.svg dark_prevkeyframe.svg dark_firstframe.svg dark_pallete_librarysvg.svg dark_passthrough-disabled.svg dark_passthrough-enabled.svg dark_prevframe.svg dark_selection-mode_ants.svg dark_selection-mode_invisible.svg dark_selection-mode_mask.svg dark_transparency-disabled.svg dark_transparency-enabled.svg dark_trim-to-image.svg dark_warning.svg delete.svgz layer-style-disabled.svg layer-style-enabled.svg light_addblankframe.svg light_addcolor.svg light_addduplicateframe.svg light_deletekeyframe.svg light_docker_lock_a.svg light_docker_lock_b.svg light_layer-locked.svg light_layer-unlocked.svg light_nextframe.svg light_pallete_library.svg light_passthrough-disabled.svgz light_passthrough-enabled.svgz light_prevframe.svg light_nextkeyframe.svg light_lastframe.svg light_prevkeyframe.svg light_firstframe.svg light_selection-mode_ants.svg light_selection-mode_invisible.svg light_selection-mode_mask.svg light_timeline_keyframe.svg light_transparency-disabled.svg light_transparency-enabled.svg light_trim-to-image.svg light_warning.svg paintop_presets_disabled.svgz paintop_settings_01.svgz selection-info.svg selection-mode_invisible.svg svg-icons.qrc transparency-locked.svg transparency-unlocked.svg workspace-chooser.svg light_lazyframeOn.svg light_lazyframeOff.svg dark_lazyframeOn.svg dark_lazyframeOff.svg dark_mirror-view.svg light_mirror-view.svg dark_rotation-reset.svg light_rotation-reset.svg light_smoothing-basic.svg light_smoothing-no.svg light_smoothing-stabilizer.svg light_smoothing-weighted.svg dark_smoothing-basic.svg dark_smoothing-no.svg dark_smoothing-stabilizer.svg dark_smoothing-weighted.svg light_merge-layer-below.svg dark_merge-layer-below.svg light_rotate-canvas-left.svg light_rotate-canvas-right.svg dark_rotate-canvas-left.svg dark_rotate-canvas-right.svg light_gmic.svg dark_gmic.svg light_split-layer.svg dark_split-layer.svg light_color-to-alpha.svg dark_color-to-alpha.svg light_preset-switcher.svg dark_preset-switcher.svg dark_animation_play.svg dark_animation_stop.svg dark_dropframe.svg dark_droppedframes.svg light_animation_play.svg light_animation_stop.svg light_dropframe.svg light_droppedframes.svg dark_landscape.svg dark_portrait.svg light_landscape.svg light_portrait.svg dark_interpolation_constant.svg dark_interpolation_linear.svg dark_interpolation_bezier.svg dark_interpolation_sharp.svg dark_interpolation_smooth.svg light_interpolation_bezier.svg light_interpolation_constant.svg light_interpolation_linear.svg light_interpolation_sharp.svg light_interpolation_smooth.svg dark_audio-none.svg dark_audio-volume-high.svg dark_audio-volume-mute.svg dark_keyframe-add.svg dark_keyframe-remove.svg dark_zoom-fit.svg dark_zoom-horizontal.svg dark_zoom-vertical.svg light_audio-none.svg light_audio-volume-high.svg light_audio-volume-mute.svg light_keyframe-add.svg light_keyframe-remove.svg light_zoom-fit.svg light_zoom-horizontal.svg light_zoom-vertical.svg dark_showColoring.svg dark_showMarks.svg dark_showColoringOff.svg dark_showMarksOff.svg dark_updateColorize.svg light_showColoring.svg light_showMarks.svg light_showColoringOff.svg light_showMarksOff.svg light_updateColorize.svg light_wheel-light.svg light_wheel-rings.svg light_wheel-sectors.svg dark_wheel-light.svg dark_wheel-rings.svg dark_wheel-sectors.svg dark_infinity.svg light_infinity.svg dark_gamut-mask-on.svg dark_gamut-mask-off.svg light_gamut-mask-off.svg light_gamut-mask-on.svg dark_ratio.svg light_ratio.svg dark_bundle_archive.svg light_bundle_archive.svg + dark_python.svg + light_python.svg diff --git a/libs/command/kis_command_utils.cpp b/libs/command/kis_command_utils.cpp index 5a78d82b83..d6224a87e5 100644 --- a/libs/command/kis_command_utils.cpp +++ b/libs/command/kis_command_utils.cpp @@ -1,207 +1,207 @@ /* * 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_command_utils.h" namespace KisCommandUtils { AggregateCommand::AggregateCommand(KUndo2Command *parent) : KUndo2Command(parent), m_firstRedo(true) {} AggregateCommand::AggregateCommand(const KUndo2MagicString &text, KUndo2Command *parent) : KUndo2Command(text, parent), m_firstRedo(true) {} void AggregateCommand::redo() { if (m_firstRedo) { m_firstRedo = false; populateChildCommands(); } m_store.redoAll(); } void AggregateCommand::undo() { m_store.undoAll(); } void AggregateCommand::addCommand(KUndo2Command *cmd) { if (!cmd) return; m_store.addCommand(cmd); } LambdaCommand::LambdaCommand(std::function createCommandFunc) : m_createCommandFunc(createCommandFunc) { } LambdaCommand::LambdaCommand(const KUndo2MagicString &text, std::function createCommandFunc) : AggregateCommand(text), m_createCommandFunc(createCommandFunc) { } LambdaCommand::LambdaCommand(const KUndo2MagicString &text, KUndo2Command *parent, std::function createCommandFunc) : AggregateCommand(text, parent), m_createCommandFunc(createCommandFunc) { } LambdaCommand::LambdaCommand(KUndo2Command *parent, std::function createCommandFunc) : AggregateCommand(parent), m_createCommandFunc(createCommandFunc) { } void LambdaCommand::populateChildCommands() { if (m_createCommandFunc) { addCommand(m_createCommandFunc()); } } SkipFirstRedoWrapper::SkipFirstRedoWrapper(KUndo2Command *child, KUndo2Command *parent) : KUndo2Command(child ? child->text() : kundo2_noi18n(""), parent), m_firstRedo(true), m_child(child) {} void SkipFirstRedoWrapper::redo() { if (m_firstRedo) { m_firstRedo = false; } else { if (m_child) { m_child->redo(); } KUndo2Command::redo(); } } void SkipFirstRedoWrapper::undo() { KUndo2Command::undo(); if (m_child) { m_child->undo(); } } SkipFirstRedoBase::SkipFirstRedoBase(bool skipFirstRedo, KUndo2Command *parent) : KUndo2Command(parent), m_firstRedo(skipFirstRedo) { } SkipFirstRedoBase::SkipFirstRedoBase(bool skipFirstRedo, const KUndo2MagicString &text, KUndo2Command *parent) : KUndo2Command(text, parent), m_firstRedo(skipFirstRedo) { } void SkipFirstRedoBase::redo() { if (m_firstRedo) { m_firstRedo = false; } else { redoImpl(); KUndo2Command::redo(); } } void SkipFirstRedoBase::undo() { KUndo2Command::undo(); undoImpl(); } void SkipFirstRedoBase::setSkipOneRedo(bool value) { m_firstRedo = value; } FlipFlopCommand::FlipFlopCommand(bool finalizing, KUndo2Command *parent) : KUndo2Command(parent), m_firstRedo(true) { m_currentState = finalizing ? State::FINALIZING : State::INITIALIZING; } FlipFlopCommand::FlipFlopCommand(State initialState, KUndo2Command *parent) : KUndo2Command(parent), m_currentState(initialState) {} void FlipFlopCommand::redo() { if (m_currentState == FlipFlopCommand::State::INITIALIZING) { partA(); } else { partB(); } m_firstRedo = false; } void FlipFlopCommand::undo() { if (m_currentState == FlipFlopCommand::State::FINALIZING) { partA(); } else { partB(); } } void FlipFlopCommand::partA() {} void FlipFlopCommand::partB() {} CompositeCommand::CompositeCommand(KUndo2Command *parent) : KUndo2Command(parent) {} CompositeCommand::~CompositeCommand() { qDeleteAll(m_commands); } void CompositeCommand::addCommand(KUndo2Command *cmd) { if (cmd) { m_commands << cmd; } } void CompositeCommand::redo() { KUndo2Command::redo(); Q_FOREACH (KUndo2Command *cmd, m_commands) { cmd->redo(); } } void CompositeCommand::undo() { KUndo2Command::undo(); - Q_FOREACH (KUndo2Command *cmd, m_commands) { - cmd->undo(); + for (auto it = m_commands.rbegin(); it != m_commands.rend(); ++it) { + (*it)->undo(); } } } diff --git a/libs/flake/CMakeLists.txt b/libs/flake/CMakeLists.txt index a0523a67a9..1793a0d3ef 100644 --- a/libs/flake/CMakeLists.txt +++ b/libs/flake/CMakeLists.txt @@ -1,235 +1,236 @@ 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 KoHatchBackground.cpp KoPatternBackground.cpp KoVectorPatternBackground.cpp KoShapeFillWrapper.cpp KoShapeFillResourceConnector.cpp KoShapeConfigWidgetBase.cpp KoDrag.cpp KoSvgPaste.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 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 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/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/KoMultiPathPointMergeCommand.cpp commands/KoMultiPathPointJoinCommand.cpp commands/KoKeepShapesSelectedCommand.cpp commands/KoPathMergeUtils.cpp + commands/KoAddRemoveShapeCommands.cpp html/HtmlSavingContext.cpp html/HtmlWriter.cpp tools/KoPathToolFactory.cpp tools/KoPathTool.cpp tools/KoPathToolSelection.cpp tools/KoPathToolHandle.cpp tools/PathToolOptionWidget.cpp tools/KoPathPointRubberSelectStrategy.cpp tools/KoPathPointMoveStrategy.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/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 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 kritacommand KF5::WidgetsAddons Qt5::Svg KF5::CoreAddons KF5::ConfigCore KF5::I18n Qt5::Gui Qt5::Xml) 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/KoMarker.cpp b/libs/flake/KoMarker.cpp index c099d02c2d..fcd39d3828 100644 --- a/libs/flake/KoMarker.cpp +++ b/libs/flake/KoMarker.cpp @@ -1,413 +1,409 @@ /* This file is part of the KDE project Copyright (C) 2011 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 "KoMarker.h" #include #include #include "KoPathShape.h" #include "KoPathShapeLoader.h" #include "KoShapeLoadingContext.h" #include "KoShapeSavingContext.h" #include "KoShapePainter.h" #include #include #include #include #include #include #include #include "kis_global.h" #include "kis_algebra_2d.h" class Q_DECL_HIDDEN KoMarker::Private { public: Private() : coordinateSystem(StrokeWidth), referenceSize(3,3), hasAutoOrientation(false), explicitOrientation(0) {} ~Private() { // shape manager that is stored in the painter should be destroyed // before the shapes themselves shapePainter.reset(); qDeleteAll(shapes); } bool operator==(const KoMarker::Private &other) const { // WARNING: comparison of shapes is extremely fuzzy! Don't // trust it in life-critical cases! return name == other.name && coordinateSystem == other.coordinateSystem && referencePoint == other.referencePoint && referenceSize == other.referenceSize && hasAutoOrientation == other.hasAutoOrientation && explicitOrientation == other.explicitOrientation && compareShapesTo(other.shapes); } Private(const Private &rhs) : name(rhs.name), coordinateSystem(rhs.coordinateSystem), referencePoint(rhs.referencePoint), referenceSize(rhs.referenceSize), hasAutoOrientation(rhs.hasAutoOrientation), explicitOrientation(rhs.explicitOrientation) { Q_FOREACH (KoShape *shape, rhs.shapes) { shapes << shape->cloneShape(); } } QString name; MarkerCoordinateSystem coordinateSystem; QPointF referencePoint; QSizeF referenceSize; bool hasAutoOrientation; qreal explicitOrientation; QList shapes; QScopedPointer shapePainter; bool compareShapesTo(const QList other) const { if (shapes.size() != other.size()) return false; for (int i = 0; i < shapes.size(); i++) { if (shapes[i]->outline() != other[i]->outline() || shapes[i]->absoluteTransformation() != other[i]->absoluteTransformation()) { return false; } } return true; } QTransform markerTransform(qreal strokeWidth, qreal nodeAngle, const QPointF &pos = QPointF()) { const QTransform translate = QTransform::fromTranslate(-referencePoint.x(), -referencePoint.y()); QTransform t = translate; if (coordinateSystem == StrokeWidth) { t *= QTransform::fromScale(strokeWidth, strokeWidth); } const qreal angle = hasAutoOrientation ? nodeAngle : explicitOrientation; if (angle != 0.0) { QTransform r; r.rotateRadians(angle); t *= r; } t *= QTransform::fromTranslate(pos.x(), pos.y()); return t; } }; KoMarker::KoMarker() : d(new Private()) { } KoMarker::~KoMarker() { delete d; } QString KoMarker::name() const { return d->name; } KoMarker::KoMarker(const KoMarker &rhs) : QSharedData(rhs), d(new Private(*rhs.d)) { } bool KoMarker::operator==(const KoMarker &other) const { return *d == *other.d; } void KoMarker::setCoordinateSystem(KoMarker::MarkerCoordinateSystem value) { d->coordinateSystem = value; } KoMarker::MarkerCoordinateSystem KoMarker::coordinateSystem() const { return d->coordinateSystem; } KoMarker::MarkerCoordinateSystem KoMarker::coordinateSystemFromString(const QString &value) { MarkerCoordinateSystem result = StrokeWidth; if (value == "userSpaceOnUse") { result = UserSpaceOnUse; } return result; } QString KoMarker::coordinateSystemToString(KoMarker::MarkerCoordinateSystem value) { return value == StrokeWidth ? "strokeWidth" : "userSpaceOnUse"; } void KoMarker::setReferencePoint(const QPointF &value) { d->referencePoint = value; } QPointF KoMarker::referencePoint() const { return d->referencePoint; } void KoMarker::setReferenceSize(const QSizeF &size) { d->referenceSize = size; } QSizeF KoMarker::referenceSize() const { return d->referenceSize; } bool KoMarker::hasAutoOtientation() const { return d->hasAutoOrientation; } void KoMarker::setAutoOrientation(bool value) { d->hasAutoOrientation = value; } qreal KoMarker::explicitOrientation() const { return d->explicitOrientation; } void KoMarker::setExplicitOrientation(qreal value) { d->explicitOrientation = value; } void KoMarker::setShapes(const QList &shapes) { d->shapes = shapes; if (d->shapePainter) { d->shapePainter->setShapes(shapes); } } QList KoMarker::shapes() const { return d->shapes; } void KoMarker::paintAtPosition(QPainter *painter, const QPointF &pos, qreal strokeWidth, qreal nodeAngle) { QTransform oldTransform = painter->transform(); if (!d->shapePainter) { d->shapePainter.reset(new KoShapePainter()); d->shapePainter->setShapes(d->shapes); } painter->setTransform(d->markerTransform(strokeWidth, nodeAngle, pos), true); d->shapePainter->paint(*painter); painter->setTransform(oldTransform); } qreal KoMarker::maxInset(qreal strokeWidth) const { QRectF shapesBounds = boundingRect(strokeWidth, 0.0); // normalized to 0,0 qreal result = 0.0; result = qMax(KisAlgebra2D::norm(shapesBounds.topLeft()), result); result = qMax(KisAlgebra2D::norm(shapesBounds.topRight()), result); result = qMax(KisAlgebra2D::norm(shapesBounds.bottomLeft()), result); result = qMax(KisAlgebra2D::norm(shapesBounds.bottomRight()), result); - if (d->coordinateSystem == StrokeWidth) { - result *= strokeWidth; - } - return result; } QRectF KoMarker::boundingRect(qreal strokeWidth, qreal nodeAngle) const { QRectF shapesBounds = KoShape::boundingRect(d->shapes); const QTransform t = d->markerTransform(strokeWidth, nodeAngle); if (!t.isIdentity()) { shapesBounds = t.mapRect(shapesBounds); } return shapesBounds; } QPainterPath KoMarker::outline(qreal strokeWidth, qreal nodeAngle) const { QPainterPath outline; Q_FOREACH (KoShape *shape, d->shapes) { outline |= shape->absoluteTransformation().map(shape->outline()); } const QTransform t = d->markerTransform(strokeWidth, nodeAngle); if (!t.isIdentity()) { outline = t.map(outline); } return outline; } void KoMarker::drawPreview(QPainter *painter, const QRectF &previewRect, const QPen &pen, KoFlake::MarkerPosition position) { const QRectF outlineRect = outline(pen.widthF(), 0).boundingRect(); // normalized to 0,0 QPointF marker; QPointF start; QPointF end; if (position == KoFlake::StartMarker) { marker = QPointF(-outlineRect.left() + previewRect.left(), previewRect.center().y()); start = marker; end = QPointF(previewRect.right(), start.y()); } else if (position == KoFlake::MidMarker) { start = QPointF(previewRect.left(), previewRect.center().y()); marker = QPointF(-outlineRect.center().x() + previewRect.center().x(), start.y()); end = QPointF(previewRect.right(), start.y()); } else if (position == KoFlake::EndMarker) { start = QPointF(previewRect.left(), previewRect.center().y()); marker = QPointF(-outlineRect.right() + previewRect.right(), start.y()); end = marker; } painter->save(); painter->setPen(pen); painter->setClipRect(previewRect); painter->drawLine(start, end); paintAtPosition(painter, marker, pen.widthF(), 0); painter->restore(); } void KoMarker::applyShapeStroke(const KoShape *parentShape, KoShapeStroke *stroke, const QPointF &pos, qreal strokeWidth, qreal nodeAngle) { const QGradient *originalGradient = stroke->lineBrush().gradient(); if (!originalGradient) { QList linearizedShapes = KoShape::linearizeSubtree(d->shapes); Q_FOREACH(KoShape *shape, linearizedShapes) { // update the stroke KoShapeStrokeSP shapeStroke = shape->stroke() ? qSharedPointerDynamicCast(shape->stroke()) : KoShapeStrokeSP(); if (shapeStroke) { shapeStroke = toQShared(new KoShapeStroke(*shapeStroke)); shapeStroke->setLineBrush(QBrush()); shapeStroke->setColor(stroke->color()); shape->setStroke(shapeStroke); } // update the background if (shape->background()) { QSharedPointer bg(new KoColorBackground(stroke->color())); shape->setBackground(bg); } } } else { QScopedPointer g(KoFlake::cloneGradient(originalGradient)); KIS_ASSERT_RECOVER_RETURN(g); const QTransform markerTransformInverted = d->markerTransform(strokeWidth, nodeAngle, pos).inverted(); QTransform gradientToUser; // Unwrap the gradient to work in global mode if (g->coordinateMode() == QGradient::ObjectBoundingMode) { QRectF boundingRect = parentShape ? parentShape->outline().boundingRect() : this->boundingRect(strokeWidth, nodeAngle); boundingRect = KisAlgebra2D::ensureRectNotSmaller(boundingRect, QSizeF(1.0, 1.0)); gradientToUser = QTransform(boundingRect.width(), 0, 0, boundingRect.height(), boundingRect.x(), boundingRect.y()); g->setCoordinateMode(QGradient::LogicalMode); } QList linearizedShapes = KoShape::linearizeSubtree(d->shapes); Q_FOREACH(KoShape *shape, linearizedShapes) { // shape-unwinding transform QTransform t = gradientToUser * markerTransformInverted * shape->absoluteTransformation().inverted(); // update the stroke KoShapeStrokeSP shapeStroke = shape->stroke() ? qSharedPointerDynamicCast(shape->stroke()) : KoShapeStrokeSP(); if (shapeStroke) { shapeStroke = toQShared(new KoShapeStroke(*shapeStroke)); QBrush brush(*g); brush.setTransform(t); shapeStroke->setLineBrush(brush); shapeStroke->setColor(Qt::transparent); shape->setStroke(shapeStroke); } // update the background if (shape->background()) { QSharedPointer bg(new KoGradientBackground(KoFlake::cloneGradient(g.data()), t)); shape->setBackground(bg); } } } } diff --git a/libs/flake/KoPathShape.cpp b/libs/flake/KoPathShape.cpp index feb185fbad..5277331738 100644 --- a/libs/flake/KoPathShape.cpp +++ b/libs/flake/KoPathShape.cpp @@ -1,1401 +1,1405 @@ /* 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 "KoPathPoint.h" #include "KoShapeStrokeModel.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 "KisQPainterStateSaver.h" #include #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() : fillRule(Qt::OddEvenFill) , autoFillMarkers(false) { } KoPathShape::Private::Private(const Private &rhs) : 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); } KoPathShape::KoPathShape() : KoTosContainer() , d(new Private) { } KoPathShape::KoPathShape(const KoPathShape &rhs) : KoTosContainer(rhs) , d(new Private(*rhs.d)) { // local data cannot be shared via QSharedData because // every path point holds a pointer to the parent shape 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::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, KoShapePaintingContext &paintContext) const { KisQPainterStateSaver saver(&painter); QPainterPath path(outline()); path.setFillRule(d->fillRule); if (background()) { background()->paint(painter, 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; for (auto subpathIt = d->subpaths.constBegin(); subpathIt != d->subpaths.constEnd(); ++subpathIt) { const KoSubpath * subpath = *subpathIt; const KoPathPoint * lastPoint = subpath->constFirst(); bool activeCP = false; for (auto pointIt = subpath->constBegin(); pointIt != subpath->constEnd(); ++pointIt) { const KoPathPoint * currPoint = *pointIt; KoPathPoint::PointProperties currProperties = currPoint->properties(); 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(); /** * 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)}); } /// NOTE: stroking the entire shape might be too expensive, so try to /// estimate the bounds using insets only... #if 0 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(); #endif - QRectF bb = transform.mapRect(kisGrowRect(outline().boundingRect(), outlineSweepWidth)); + // add 10% extra update area around the doubled insets + QRectF bb = transform.mapRect(kisGrowRect(outline().boundingRect(), 1.1 * 0.5 * outlineSweepWidth)); 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(); QTransform myMatrix = absoluteTransformation().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(); 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(); if (cubicSeg.first() && cubicSeg.second()) { 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(); if (cubicSeg.first() && cubicSeg.second()) { 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().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().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; } + + notifyChanged(); + shapeChangedPriv(StrokeChanged); } 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/KoShape.cpp b/libs/flake/KoShape.cpp index 110f50b40d..91164813f9 100644 --- a/libs/flake/KoShape.cpp +++ b/libs/flake/KoShape.cpp @@ -1,1353 +1,1353 @@ /* This file is part of the KDE project Copyright (C) 2006 C. Boemann Rasmussen Copyright (C) 2006-2010 Thomas Zander Copyright (C) 2006-2010 Thorsten Zachmann Copyright (C) 2007-2009,2011 Jan Hambrecht CopyRight (C) 2010 Boudewijn Rempt This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include "KoShape.h" #include "KoShape_p.h" #include "KoShapeContainer.h" #include "KoShapeLayer.h" #include "KoShapeContainerModel.h" #include "KoSelection.h" #include "KoPointerEvent.h" #include "KoInsets.h" #include "KoShapeStrokeModel.h" #include "KoShapeBackground.h" #include "KoColorBackground.h" #include "KoHatchBackground.h" #include "KoGradientBackground.h" #include "KoPatternBackground.h" #include "KoShapeManager.h" #include "KoShapeUserData.h" #include "KoShapeApplicationData.h" #include "KoShapeSavingContext.h" #include "KoShapeLoadingContext.h" #include "KoViewConverter.h" #include "KoShapeStroke.h" #include "KoShapeShadow.h" #include "KoClipPath.h" #include "KoPathShape.h" #include "KoFilterEffectStack.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_assert.h" #include // KoShape::Private KoShape::SharedData::SharedData() : QSharedData() , size(50, 50) , shadow(0) , filterEffectStack(0) , transparency(0.0) , zIndex(0) , runThrough(0) , visible(true) , printable(true) , geometryProtected(false) , keepAspect(false) , selectable(true) , protectContent(false) , textRunAroundSide(KoShape::BiggestRunAroundSide) , textRunAroundDistanceLeft(0.0) , textRunAroundDistanceTop(0.0) , textRunAroundDistanceRight(0.0) , textRunAroundDistanceBottom(0.0) , textRunAroundThreshold(0.0) , textRunAroundContour(KoShape::ContourFull) { } KoShape::SharedData::SharedData(const SharedData &rhs) : QSharedData() , size(rhs.size) , shapeId(rhs.shapeId) , name(rhs.name) , localMatrix(rhs.localMatrix) , userData(rhs.userData ? rhs.userData->clone() : 0) , stroke(rhs.stroke) , fill(rhs.fill) , inheritBackground(rhs.inheritBackground) , inheritStroke(rhs.inheritStroke) , shadow(0) // WARNING: not implemented in Krita , clipPath(rhs.clipPath ? rhs.clipPath->clone() : 0) , clipMask(rhs.clipMask ? rhs.clipMask->clone() : 0) , additionalAttributes(rhs.additionalAttributes) , additionalStyleAttributes(rhs.additionalStyleAttributes) , filterEffectStack(0) // WARNING: not implemented in Krita , transparency(rhs.transparency) , hyperLink(rhs.hyperLink) , zIndex(rhs.zIndex) , runThrough(rhs.runThrough) , visible(rhs.visible) , printable(rhs.visible) , geometryProtected(rhs.geometryProtected) , keepAspect(rhs.keepAspect) , selectable(rhs.selectable) , protectContent(rhs.protectContent) , textRunAroundSide(rhs.textRunAroundSide) , textRunAroundDistanceLeft(rhs.textRunAroundDistanceLeft) , textRunAroundDistanceTop(rhs.textRunAroundDistanceTop) , textRunAroundDistanceRight(rhs.textRunAroundDistanceRight) , textRunAroundDistanceBottom(rhs.textRunAroundDistanceBottom) , textRunAroundThreshold(rhs.textRunAroundThreshold) , textRunAroundContour(rhs.textRunAroundContour) { } KoShape::SharedData::~SharedData() { if (shadow && !shadow->deref()) delete shadow; if (filterEffectStack && !filterEffectStack->deref()) delete filterEffectStack; } void KoShape::shapeChangedPriv(KoShape::ChangeType type) { if (d->parent) d->parent->model()->childChanged(this, type); this->shapeChanged(type); Q_FOREACH (KoShape * shape, d->dependees) { shape->shapeChanged(type, this); } Q_FOREACH (KoShape::ShapeChangeListener *listener, d->listeners) { listener->notifyShapeChangedImpl(type, this); } } void KoShape::addShapeManager(KoShapeManager *manager) { d->shapeManagers.insert(manager); } void KoShape::removeShapeManager(KoShapeManager *manager) { d->shapeManagers.remove(manager); } // ======== KoShape const qint16 KoShape::maxZIndex = std::numeric_limits::max(); const qint16 KoShape::minZIndex = std::numeric_limits::min(); KoShape::KoShape() : d(new Private()), s(new SharedData) { notifyChanged(); } KoShape::KoShape(const KoShape &rhs) : d(new Private()), s(rhs.s) { } KoShape::~KoShape() { shapeChangedPriv(Deleted); d->listeners.clear(); /** * The shape must have already been detached from all the parents and * shape managers. Otherwise we migh accidentally request some RTTI * information, which is not available anymore (we are in d-tor). * * TL;DR: fix the code that caused this destruction without unparenting * instead of trying to remove these assert! */ KIS_SAFE_ASSERT_RECOVER (!d->parent) { d->parent->removeShape(this); } KIS_SAFE_ASSERT_RECOVER (d->shapeManagers.isEmpty()) { Q_FOREACH (KoShapeManager *manager, d->shapeManagers) { manager->shapeInterface()->notifyShapeDestructed(this); } d->shapeManagers.clear(); } } KoShape *KoShape::cloneShape() const { KIS_SAFE_ASSERT_RECOVER_NOOP(0 && "not implemented!"); qWarning() << shapeId() << "cannot be cloned"; return 0; } void KoShape::paintStroke(QPainter &painter, KoShapePaintingContext &paintcontext) const { Q_UNUSED(paintcontext); if (stroke()) { stroke()->paint(this, painter); } } void KoShape::scale(qreal sx, qreal sy) { QPointF pos = position(); QTransform scaleMatrix; scaleMatrix.translate(pos.x(), pos.y()); scaleMatrix.scale(sx, sy); scaleMatrix.translate(-pos.x(), -pos.y()); s->localMatrix = s->localMatrix * scaleMatrix; notifyChanged(); shapeChangedPriv(ScaleChanged); } void KoShape::rotate(qreal angle) { QPointF center = s->localMatrix.map(QPointF(0.5 * size().width(), 0.5 * size().height())); QTransform rotateMatrix; rotateMatrix.translate(center.x(), center.y()); rotateMatrix.rotate(angle); rotateMatrix.translate(-center.x(), -center.y()); s->localMatrix = s->localMatrix * rotateMatrix; notifyChanged(); shapeChangedPriv(RotationChanged); } void KoShape::shear(qreal sx, qreal sy) { QPointF pos = position(); QTransform shearMatrix; shearMatrix.translate(pos.x(), pos.y()); shearMatrix.shear(sx, sy); shearMatrix.translate(-pos.x(), -pos.y()); s->localMatrix = s->localMatrix * shearMatrix; notifyChanged(); shapeChangedPriv(ShearChanged); } void KoShape::setSize(const QSizeF &newSize) { QSizeF oldSize(size()); // always set size, as d->size and size() may vary setSizeImpl(newSize); if (oldSize == newSize) return; notifyChanged(); shapeChangedPriv(SizeChanged); } void KoShape::setSizeImpl(const QSizeF &size) const { s->size = size; } void KoShape::setPosition(const QPointF &newPosition) { QPointF currentPos = position(); if (newPosition == currentPos) return; QTransform translateMatrix; translateMatrix.translate(newPosition.x() - currentPos.x(), newPosition.y() - currentPos.y()); s->localMatrix = s->localMatrix * translateMatrix; notifyChanged(); shapeChangedPriv(PositionChanged); } bool KoShape::hitTest(const QPointF &position) const { if (d->parent && d->parent->isClipped(this) && !d->parent->hitTest(position)) return false; QPointF point = absoluteTransformation().inverted().map(position); QRectF bb = outlineRect(); if (s->stroke) { KoInsets insets; s->stroke->strokeInsets(this, insets); bb.adjust(-insets.left, -insets.top, insets.right, insets.bottom); } if (bb.contains(point)) return true; // if there is no shadow we can as well just leave if (! s->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().inverted().map(position - s->shadow->offset()); return bb.contains(point); } QRectF KoShape::boundingRect() const { QTransform transform = absoluteTransformation(); QRectF bb = outlineRect(); if (s->stroke) { KoInsets insets; s->stroke->strokeInsets(this, insets); bb.adjust(-insets.left, -insets.top, insets.right, insets.bottom); } bb = transform.mapRect(bb); if (s->shadow) { KoInsets insets; s->shadow->insets(insets); bb.adjust(-insets.left, -insets.top, insets.right, insets.bottom); } if (s->filterEffectStack) { QRectF clipRect = s->filterEffectStack->clipRectForBoundingRect(outlineRect()); bb |= transform.mapRect(clipRect); } return bb; } QRectF KoShape::boundingRect(const QList &shapes) { QRectF boundingRect; Q_FOREACH (KoShape *shape, shapes) { boundingRect |= shape->boundingRect(); } return boundingRect; } QRectF KoShape::absoluteOutlineRect() const { return absoluteTransformation().map(outline()).boundingRect(); } QRectF KoShape::absoluteOutlineRect(const QList &shapes) { QRectF absoluteOutlineRect; Q_FOREACH (KoShape *shape, shapes) { absoluteOutlineRect |= shape->absoluteOutlineRect(); } return absoluteOutlineRect; } QTransform KoShape::absoluteTransformation() const { QTransform matrix; // apply parents matrix to inherit any transformations done there. KoShapeContainer * container = d->parent; if (container) { if (container->inheritsTransform(this)) { matrix = container->absoluteTransformation(); } else { QSizeF containerSize = container->size(); QPointF containerPos = container->absolutePosition() - QPointF(0.5 * containerSize.width(), 0.5 * containerSize.height()); matrix.translate(containerPos.x(), containerPos.y()); } } return s->localMatrix * matrix; } void KoShape::applyAbsoluteTransformation(const QTransform &matrix) { QTransform globalMatrix = absoluteTransformation(); // the transformation is relative to the global coordinate system // but we want to change the local matrix, so convert the matrix // to be relative to the local coordinate system QTransform transformMatrix = globalMatrix * matrix * globalMatrix.inverted(); applyTransformation(transformMatrix); } void KoShape::applyTransformation(const QTransform &matrix) { s->localMatrix = matrix * s->localMatrix; notifyChanged(); shapeChangedPriv(GenericMatrixChange); } void KoShape::setTransformation(const QTransform &matrix) { s->localMatrix = matrix; notifyChanged(); shapeChangedPriv(GenericMatrixChange); } QTransform KoShape::transformation() const { return s->localMatrix; } KoShape::ChildZOrderPolicy KoShape::childZOrderPolicy() { return ChildZDefault; } bool KoShape::compareShapeZIndex(KoShape *s1, KoShape *s2) { /** * WARNING: Our definition of zIndex is not yet compatible with SVG2's * definition. In SVG stacking context of groups with the same * zIndex are **merged**, while in Krita the contents of groups * is never merged. One group will always below than the other. * Therefore, when zIndex of two groups inside the same parent * coincide, the resulting painting order in Krita is * **UNDEFINED**. * * To avoid this trouble we use KoShapeReorderCommand::mergeInShape() * inside KoShapeCreateCommand. */ /** * The algorithm below doesn't correctly handle the case when the two pointers actually * point to the same shape. So just check it in advance to guarantee strict weak ordering * relation requirement */ if (s1 == s2) return false; // First sort according to runThrough which is sort of a master level KoShape *parentShapeS1 = s1->parent(); KoShape *parentShapeS2 = s2->parent(); int runThrough1 = s1->runThrough(); int runThrough2 = s2->runThrough(); while (parentShapeS1) { if (parentShapeS1->childZOrderPolicy() == KoShape::ChildZParentChild) { runThrough1 = parentShapeS1->runThrough(); } else { runThrough1 = runThrough1 + parentShapeS1->runThrough(); } parentShapeS1 = parentShapeS1->parent(); } while (parentShapeS2) { if (parentShapeS2->childZOrderPolicy() == KoShape::ChildZParentChild) { runThrough2 = parentShapeS2->runThrough(); } else { runThrough2 = runThrough2 + parentShapeS2->runThrough(); } parentShapeS2 = parentShapeS2->parent(); } if (runThrough1 > runThrough2) { return false; } if (runThrough1 < runThrough2) { return true; } // If on the same runThrough level then the zIndex is all that matters. // // We basically walk up through the parents until we find a common base parent // To do that we need two loops where the inner loop walks up through the parents // of s2 every time we step up one parent level on s1 // // We don't update the index value until after we have seen that it's not a common base // That way we ensure that two children of a common base are sorted according to their respective // z value bool foundCommonParent = false; int index1 = s1->zIndex(); int index2 = s2->zIndex(); parentShapeS1 = s1; parentShapeS2 = s2; while (parentShapeS1 && !foundCommonParent) { parentShapeS2 = s2; index2 = parentShapeS2->zIndex(); while (parentShapeS2) { if (parentShapeS2 == parentShapeS1) { foundCommonParent = true; break; } if (parentShapeS2->childZOrderPolicy() == KoShape::ChildZParentChild) { index2 = parentShapeS2->zIndex(); } parentShapeS2 = parentShapeS2->parent(); } if (!foundCommonParent) { if (parentShapeS1->childZOrderPolicy() == KoShape::ChildZParentChild) { index1 = parentShapeS1->zIndex(); } parentShapeS1 = parentShapeS1->parent(); } } // If the one shape is a parent/child of the other then sort so. if (s1 == parentShapeS2) { return true; } if (s2 == parentShapeS1) { return false; } // If we went that far then the z-Index is used for sorting. return index1 < index2; } void KoShape::setParent(KoShapeContainer *parent) { if (d->parent == parent) { return; } KoShapeContainer *oldParent = d->parent; d->parent = 0; // avoids recursive removing if (oldParent) { oldParent->shapeInterface()->removeShape(this); } KIS_SAFE_ASSERT_RECOVER_NOOP(parent != this); if (parent && parent != this) { d->parent = parent; parent->shapeInterface()->addShape(this); } notifyChanged(); shapeChangedPriv(ParentChanged); } bool KoShape::inheritsTransformFromAny(const QList ancestorsInQuestion) const { bool result = false; KoShape *shape = const_cast(this); while (shape) { KoShapeContainer *parent = shape->parent(); if (parent && !parent->inheritsTransform(shape)) { break; } if (ancestorsInQuestion.contains(shape)) { result = true; break; } shape = parent; } return result; } bool KoShape::hasCommonParent(const KoShape *shape) const { const KoShape *thisShape = this; while (thisShape) { const KoShape *otherShape = shape; while (otherShape) { if (thisShape == otherShape) { return true; } otherShape = otherShape->parent(); } thisShape = thisShape->parent(); } return false; } qint16 KoShape::zIndex() const { return s->zIndex; } void KoShape::update() const { if (!d->shapeManagers.empty()) { - QRectF rect(boundingRect()); + const QRectF rect(boundingRect()); Q_FOREACH (KoShapeManager * manager, d->shapeManagers) { manager->update(rect, this, true); } } } void KoShape::updateAbsolute(const QRectF &rect) const { if (rect.isEmpty() && !rect.isNull()) { return; } if (!d->shapeManagers.empty() && isVisible()) { Q_FOREACH (KoShapeManager *manager, d->shapeManagers) { manager->update(rect); } } } QPainterPath KoShape::outline() const { QPainterPath path; path.addRect(outlineRect()); return path; } QRectF KoShape::outlineRect() const { const QSizeF s = size(); return QRectF(QPointF(0, 0), QSizeF(qMax(s.width(), qreal(0.0001)), qMax(s.height(), qreal(0.0001)))); } QPainterPath KoShape::shadowOutline() const { if (background()) { return outline(); } return QPainterPath(); } QPointF KoShape::absolutePosition(KoFlake::AnchorPosition anchor) const { const QRectF rc = outlineRect(); QPointF point = rc.topLeft(); bool valid = false; QPointF anchoredPoint = KoFlake::anchorToPoint(anchor, rc, &valid); if (valid) { point = anchoredPoint; } return absoluteTransformation().map(point); } void KoShape::setAbsolutePosition(const QPointF &newPosition, KoFlake::AnchorPosition anchor) { QPointF currentAbsPosition = absolutePosition(anchor); QPointF translate = newPosition - currentAbsPosition; QTransform translateMatrix; translateMatrix.translate(translate.x(), translate.y()); applyAbsoluteTransformation(translateMatrix); notifyChanged(); shapeChangedPriv(PositionChanged); } void KoShape::copySettings(const KoShape *shape) { s->size = shape->size(); s->zIndex = shape->zIndex(); s->visible = shape->isVisible(false); // Ensure printable is true by default if (!s->visible) s->printable = true; else s->printable = shape->isPrintable(); s->geometryProtected = shape->isGeometryProtected(); s->protectContent = shape->isContentProtected(); s->selectable = shape->isSelectable(); s->keepAspect = shape->keepAspectRatio(); s->localMatrix = shape->s->localMatrix; } void KoShape::notifyChanged() { Q_FOREACH (KoShapeManager * manager, d->shapeManagers) { manager->notifyShapeChanged(this); } } void KoShape::setUserData(KoShapeUserData *userData) { s->userData.reset(userData); } KoShapeUserData *KoShape::userData() const { return s->userData.data(); } bool KoShape::hasTransparency() const { QSharedPointer bg = background(); return !bg || bg->hasTransparency() || s->transparency > 0.0; } void KoShape::setTransparency(qreal transparency) { s->transparency = qBound(0.0, transparency, 1.0); shapeChangedPriv(TransparencyChanged); notifyChanged(); } qreal KoShape::transparency(bool recursive) const { if (!recursive || !parent()) { return s->transparency; } else { const qreal parentOpacity = 1.0-parent()->transparency(recursive); const qreal childOpacity = 1.0-s->transparency; return 1.0-(parentOpacity*childOpacity); } } KoInsets KoShape::strokeInsets() const { KoInsets answer; if (s->stroke) s->stroke->strokeInsets(this, answer); return answer; } qreal KoShape::rotation() const { // try to extract the rotation angle out of the local matrix // if it is a pure rotation matrix // check if the matrix has shearing mixed in if (fabs(fabs(s->localMatrix.m12()) - fabs(s->localMatrix.m21())) > 1e-10) return std::numeric_limits::quiet_NaN(); // check if the matrix has scaling mixed in if (fabs(s->localMatrix.m11() - s->localMatrix.m22()) > 1e-10) return std::numeric_limits::quiet_NaN(); // calculate the angle from the matrix elements qreal angle = atan2(-s->localMatrix.m21(), s->localMatrix.m11()) * 180.0 / M_PI; if (angle < 0.0) angle += 360.0; return angle; } QSizeF KoShape::size() const { return s->size; } QPointF KoShape::position() const { QPointF center = outlineRect().center(); return s->localMatrix.map(center) - center; } KoShape::TextRunAroundSide KoShape::textRunAroundSide() const { return s->textRunAroundSide; } void KoShape::setTextRunAroundSide(TextRunAroundSide side, RunThroughLevel runThrought) { if (side == RunThrough) { if (runThrought == Background) { setRunThrough(-1); } else { setRunThrough(1); } } else { setRunThrough(0); } if ( s->textRunAroundSide == side) { return; } s->textRunAroundSide = side; notifyChanged(); shapeChangedPriv(TextRunAroundChanged); } qreal KoShape::textRunAroundDistanceTop() const { return s->textRunAroundDistanceTop; } void KoShape::setTextRunAroundDistanceTop(qreal distance) { s->textRunAroundDistanceTop = distance; } qreal KoShape::textRunAroundDistanceLeft() const { return s->textRunAroundDistanceLeft; } void KoShape::setTextRunAroundDistanceLeft(qreal distance) { s->textRunAroundDistanceLeft = distance; } qreal KoShape::textRunAroundDistanceRight() const { return s->textRunAroundDistanceRight; } void KoShape::setTextRunAroundDistanceRight(qreal distance) { s->textRunAroundDistanceRight = distance; } qreal KoShape::textRunAroundDistanceBottom() const { return s->textRunAroundDistanceBottom; } void KoShape::setTextRunAroundDistanceBottom(qreal distance) { s->textRunAroundDistanceBottom = distance; } qreal KoShape::textRunAroundThreshold() const { return s->textRunAroundThreshold; } void KoShape::setTextRunAroundThreshold(qreal threshold) { s->textRunAroundThreshold = threshold; } KoShape::TextRunAroundContour KoShape::textRunAroundContour() const { return s->textRunAroundContour; } void KoShape::setTextRunAroundContour(KoShape::TextRunAroundContour contour) { s->textRunAroundContour = contour; } void KoShape::setBackground(QSharedPointer fill) { s->inheritBackground = false; s->fill = fill; shapeChangedPriv(BackgroundChanged); notifyChanged(); } QSharedPointer KoShape::background() const { QSharedPointer bg; if (!s->inheritBackground) { bg = s->fill; } else if (parent()) { bg = parent()->background(); } return bg; } void KoShape::setInheritBackground(bool value) { s->inheritBackground = value; if (s->inheritBackground) { s->fill.clear(); } } bool KoShape::inheritBackground() const { return s->inheritBackground; } void KoShape::setZIndex(qint16 zIndex) { if (s->zIndex == zIndex) return; s->zIndex = zIndex; notifyChanged(); } int KoShape::runThrough() const { return s->runThrough; } void KoShape::setRunThrough(short int runThrough) { s->runThrough = runThrough; } void KoShape::setVisible(bool on) { int _on = (on ? 1 : 0); if (s->visible == _on) return; s->visible = _on; } bool KoShape::isVisible(bool recursive) const { if (!recursive) return s->visible; if (!s->visible) return false; KoShapeContainer * parentShape = parent(); if (parentShape) { return parentShape->isVisible(true); } return true; } void KoShape::setPrintable(bool on) { s->printable = on; } bool KoShape::isPrintable() const { if (s->visible) return s->printable; else return false; } void KoShape::setSelectable(bool selectable) { s->selectable = selectable; } bool KoShape::isSelectable() const { return s->selectable; } void KoShape::setGeometryProtected(bool on) { s->geometryProtected = on; } bool KoShape::isGeometryProtected() const { return s->geometryProtected; } void KoShape::setContentProtected(bool protect) { s->protectContent = protect; } bool KoShape::isContentProtected() const { return s->protectContent; } KoShapeContainer *KoShape::parent() const { return d->parent; } void KoShape::setKeepAspectRatio(bool keepAspect) { s->keepAspect = keepAspect; shapeChangedPriv(KeepAspectRatioChange); notifyChanged(); } bool KoShape::keepAspectRatio() const { return s->keepAspect; } QString KoShape::shapeId() const { return s->shapeId; } void KoShape::setShapeId(const QString &id) { s->shapeId = id; } KoShapeStrokeModelSP KoShape::stroke() const { KoShapeStrokeModelSP stroke; if (!s->inheritStroke) { stroke = s->stroke; } else if (parent()) { stroke = parent()->stroke(); } return stroke; } void KoShape::setStroke(KoShapeStrokeModelSP stroke) { s->inheritStroke = false; s->stroke = stroke; shapeChangedPriv(StrokeChanged); notifyChanged(); } void KoShape::setInheritStroke(bool value) { s->inheritStroke = value; if (s->inheritStroke) { s->stroke.clear(); } } bool KoShape::inheritStroke() const { return s->inheritStroke; } void KoShape::setShadow(KoShapeShadow *shadow) { if (s->shadow) s->shadow->deref(); s->shadow = shadow; if (s->shadow) { s->shadow->ref(); // TODO update changed area } shapeChangedPriv(ShadowChanged); notifyChanged(); } KoShapeShadow *KoShape::shadow() const { return s->shadow; } void KoShape::setClipPath(KoClipPath *clipPath) { s->clipPath.reset(clipPath); shapeChangedPriv(ClipPathChanged); notifyChanged(); } KoClipPath * KoShape::clipPath() const { return s->clipPath.data(); } void KoShape::setClipMask(KoClipMask *clipMask) { s->clipMask.reset(clipMask); shapeChangedPriv(ClipMaskChanged); notifyChanged(); } KoClipMask* KoShape::clipMask() const { return s->clipMask.data(); } QTransform KoShape::transform() const { return s->localMatrix; } QString KoShape::name() const { return s->name; } void KoShape::setName(const QString &name) { s->name = name; } void KoShape::waitUntilReady(bool asynchronous) const { Q_UNUSED(asynchronous); } bool KoShape::isShapeEditable(bool recursive) const { if (!s->visible || s->geometryProtected) return false; if (recursive && d->parent) { return d->parent->isShapeEditable(true); } return true; } KisHandlePainterHelper KoShape::createHandlePainterHelperView(QPainter *painter, KoShape *shape, const KoViewConverter &converter, qreal handleRadius) { const QTransform originalPainterTransform = painter->transform(); painter->setTransform(shape->absoluteTransformation() * converter.documentToView() * painter->transform()); // move c-tor return KisHandlePainterHelper(painter, originalPainterTransform, handleRadius); } KisHandlePainterHelper KoShape::createHandlePainterHelperDocument(QPainter *painter, KoShape *shape, qreal handleRadius) { const QTransform originalPainterTransform = painter->transform(); painter->setTransform(shape->absoluteTransformation() * painter->transform()); // move c-tor return KisHandlePainterHelper(painter, originalPainterTransform, handleRadius); } QPointF KoShape::shapeToDocument(const QPointF &point) const { return absoluteTransformation().map(point); } QRectF KoShape::shapeToDocument(const QRectF &rect) const { return absoluteTransformation().mapRect(rect); } QPointF KoShape::documentToShape(const QPointF &point) const { return absoluteTransformation().inverted().map(point); } QRectF KoShape::documentToShape(const QRectF &rect) const { return absoluteTransformation().inverted().mapRect(rect); } bool KoShape::addDependee(KoShape *shape) { if (! shape) return false; // refuse to establish a circular dependency if (shape->hasDependee(this)) return false; if (! d->dependees.contains(shape)) d->dependees.append(shape); return true; } void KoShape::removeDependee(KoShape *shape) { int index = d->dependees.indexOf(shape); if (index >= 0) d->dependees.removeAt(index); } bool KoShape::hasDependee(KoShape *shape) const { return d->dependees.contains(shape); } QList KoShape::dependees() const { return d->dependees; } void KoShape::shapeChanged(ChangeType type, KoShape *shape) { Q_UNUSED(type); Q_UNUSED(shape); } KoSnapData KoShape::snapData() const { return KoSnapData(); } void KoShape::setAdditionalAttribute(const QString &name, const QString &value) { s->additionalAttributes.insert(name, value); } void KoShape::removeAdditionalAttribute(const QString &name) { s->additionalAttributes.remove(name); } bool KoShape::hasAdditionalAttribute(const QString &name) const { return s->additionalAttributes.contains(name); } QString KoShape::additionalAttribute(const QString &name) const { return s->additionalAttributes.value(name); } void KoShape::setAdditionalStyleAttribute(const char *name, const QString &value) { s->additionalStyleAttributes.insert(name, value); } void KoShape::removeAdditionalStyleAttribute(const char *name) { s->additionalStyleAttributes.remove(name); } KoFilterEffectStack *KoShape::filterEffectStack() const { return s->filterEffectStack; } void KoShape::setFilterEffectStack(KoFilterEffectStack *filterEffectStack) { if (s->filterEffectStack) s->filterEffectStack->deref(); s->filterEffectStack = filterEffectStack; if (s->filterEffectStack) { s->filterEffectStack->ref(); } notifyChanged(); } QSet KoShape::toolDelegates() const { return d->toolDelegates; } void KoShape::setToolDelegates(const QSet &delegates) { d->toolDelegates = delegates; } QString KoShape::hyperLink () const { return s->hyperLink; } void KoShape::setHyperLink(const QString &hyperLink) { s->hyperLink = hyperLink; } KoShape::ShapeChangeListener::~ShapeChangeListener() { Q_FOREACH(KoShape *shape, m_registeredShapes) { shape->removeShapeChangeListener(this); } } void KoShape::ShapeChangeListener::registerShape(KoShape *shape) { KIS_SAFE_ASSERT_RECOVER_RETURN(!m_registeredShapes.contains(shape)); m_registeredShapes.append(shape); } void KoShape::ShapeChangeListener::unregisterShape(KoShape *shape) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_registeredShapes.contains(shape)); m_registeredShapes.removeAll(shape); } void KoShape::ShapeChangeListener::notifyShapeChangedImpl(KoShape::ChangeType type, KoShape *shape) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_registeredShapes.contains(shape)); notifyShapeChanged(type, shape); if (type == KoShape::Deleted) { unregisterShape(shape); } } void KoShape::addShapeChangeListener(KoShape::ShapeChangeListener *listener) { KIS_SAFE_ASSERT_RECOVER_RETURN(!d->listeners.contains(listener)); listener->registerShape(this); d->listeners.append(listener); } void KoShape::removeShapeChangeListener(KoShape::ShapeChangeListener *listener) { KIS_SAFE_ASSERT_RECOVER_RETURN(d->listeners.contains(listener)); d->listeners.removeAll(listener); listener->unregisterShape(this); } QList KoShape::listeners() const { return d->listeners; } QList KoShape::linearizeSubtree(const QList &shapes) { QList result; Q_FOREACH (KoShape *shape, shapes) { result << shape; KoShapeContainer *container = dynamic_cast(shape); if (container) { result << linearizeSubtree(container->shapes()); } } return result; } QList KoShape::linearizeSubtreeSorted(const QList &shapes) { QList sortedShapes = shapes; std::sort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex); QList result; Q_FOREACH (KoShape *shape, sortedShapes) { result << shape; KoShapeContainer *container = dynamic_cast(shape); if (container) { result << linearizeSubtreeSorted(container->shapes()); } } return result; } diff --git a/libs/flake/KoShapeController.cpp b/libs/flake/KoShapeController.cpp index 8cc064e320..23fb99c4e4 100644 --- a/libs/flake/KoShapeController.cpp +++ b/libs/flake/KoShapeController.cpp @@ -1,190 +1,196 @@ /* This file is part of the KDE project * * Copyright (C) 2006-2007, 2010 Thomas Zander * Copyright (C) 2006-2008 Thorsten Zachmann * Copyright (C) 2011 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 "KoShapeController.h" #include "KoShapeControllerBase.h" #include "KoShapeRegistry.h" #include "KoDocumentResourceManager.h" #include "KoShapeManager.h" #include "KoShapeLayer.h" #include "KoSelection.h" #include "commands/KoShapeCreateCommand.h" #include "commands/KoShapeDeleteCommand.h" #include "KoCanvasBase.h" #include "KoShapeConfigWidgetBase.h" #include "KoShapeFactoryBase.h" #include "KoShape.h" #include #include #include #include class KoShapeController::Private { public: Private() : canvas(0), shapeController(0) { } KoCanvasBase *canvas; KoShapeControllerBase *shapeController; KUndo2Command* addShape(KoShape *shape, bool showDialog, KoShapeContainer *parentShape, KUndo2Command *parent) { if (canvas) { if (showDialog && !shape->shapeId().isEmpty()) { KoShapeFactoryBase *factory = KoShapeRegistry::instance()->value(shape->shapeId()); Q_ASSERT(factory); qint16 z = 0; Q_FOREACH (KoShape *sh, canvas->shapeManager()->shapes()) { z = qMax(z, sh->zIndex()); } shape->setZIndex(z + 1); // show config dialog. KPageDialog *dialog = new KPageDialog(canvas->canvasWidget()); dialog->setWindowTitle(i18n("%1 Options", factory->name())); int pageCount = 0; QList widgets; Q_FOREACH (KoShapeConfigWidgetBase* panel, factory->createShapeOptionPanels()) { if (! panel->showOnShapeCreate()) continue; panel->open(shape); panel->connect(panel, SIGNAL(accept()), dialog, SLOT(accept())); widgets.append(panel); panel->setResourceManager(canvas->resourceManager()); panel->setUnit(canvas->unit()); QString title = panel->windowTitle().isEmpty() ? panel->objectName() : panel->windowTitle(); dialog->addPage(panel, title); pageCount ++; } if (pageCount > 0) { if (pageCount > 1) dialog->setFaceType(KPageDialog::Tabbed); if (dialog->exec() != KPageDialog::Accepted) { delete dialog; return 0; } Q_FOREACH (KoShapeConfigWidgetBase *widget, widgets) widget->save(); } delete dialog; } } return addShapesDirect({shape}, parentShape, parent); } KUndo2Command* addShapesDirect(const QList shapes, KoShapeContainer *parentShape, KUndo2Command *parent) { - return new KoShapeCreateCommand(shapeController, shapes, parentShape, parent); + KUndo2Command *resultCommand = 0; + + if (!parentShape) { + resultCommand = new KUndo2Command(parent); + parentShape = shapeController->createParentForShapes(shapes, resultCommand); + KUndo2Command *addShapeCommand = new KoShapeCreateCommand(shapeController, shapes, parentShape, resultCommand); + resultCommand->setText(addShapeCommand->text()); + } else { + resultCommand = new KoShapeCreateCommand(shapeController, shapes, parentShape, parent); + } + + return resultCommand; } }; KoShapeController::KoShapeController(KoCanvasBase *canvas, KoShapeControllerBase *shapeController) : d(new Private()) { d->canvas = canvas; d->shapeController = shapeController; } KoShapeController::~KoShapeController() { delete d; } void KoShapeController::reset() { d->canvas = 0; d->shapeController = 0; } KUndo2Command* KoShapeController::addShape(KoShape *shape, KoShapeContainer *parentShape, KUndo2Command *parent) { return d->addShape(shape, true, parentShape, parent); } KUndo2Command* KoShapeController::addShapeDirect(KoShape *shape, KoShapeContainer *parentShape, KUndo2Command *parent) { return d->addShapesDirect({shape}, parentShape, parent); } KUndo2Command *KoShapeController::addShapesDirect(const QList shapes, KoShapeContainer *parentShape, KUndo2Command *parent) { return d->addShapesDirect(shapes, parentShape, parent); } KUndo2Command* KoShapeController::removeShape(KoShape *shape, KUndo2Command *parent) { - KUndo2Command *cmd = new KoShapeDeleteCommand(d->shapeController, shape, parent); - QList shapes; - shapes.append(shape); - d->shapeController->shapesRemoved(shapes, cmd); - return cmd; + return removeShapes({shape}, parent); } KUndo2Command* KoShapeController::removeShapes(const QList &shapes, KUndo2Command *parent) { KUndo2Command *cmd = new KoShapeDeleteCommand(d->shapeController, shapes, parent); - d->shapeController->shapesRemoved(shapes, cmd); return cmd; } void KoShapeController::setShapeControllerBase(KoShapeControllerBase *shapeController) { d->shapeController = shapeController; } QRectF KoShapeController::documentRectInPixels() const { return d->shapeController ? d->shapeController->documentRectInPixels() : QRectF(0,0,1920,1080); } qreal KoShapeController::pixelsPerInch() const { return d->shapeController ? d->shapeController->pixelsPerInch() : 72.0; } QRectF KoShapeController::documentRect() const { return d->shapeController ? d->shapeController->documentRect() : documentRectInPixels(); } KoDocumentResourceManager *KoShapeController::resourceManager() const { if (!d->shapeController) { qWarning() << "THIS IS NOT GOOD!"; return 0; } return d->shapeController->resourceManager(); } KoShapeControllerBase *KoShapeController::documentBase() const { return d->shapeController; } diff --git a/libs/flake/KoShapeControllerBase.cpp b/libs/flake/KoShapeControllerBase.cpp index ce0e445d25..824bf8cd3c 100644 --- a/libs/flake/KoShapeControllerBase.cpp +++ b/libs/flake/KoShapeControllerBase.cpp @@ -1,92 +1,92 @@ /* This file is part of the KDE project Copyright (C) 2006, 2010 Thomas Zander Copyright (C) 2011 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 #include #include "KoShapeControllerBase.h" #include "KoDocumentResourceManager.h" #include "KoShapeRegistry.h" #include "KoShapeFactoryBase.h" #include #include #include +#include class KoshapeControllerBasePrivate { public: KoshapeControllerBasePrivate() : resourceManager(new KoDocumentResourceManager()) { KoShapeRegistry *registry = KoShapeRegistry::instance(); foreach (const QString &id, registry->keys()) { KoShapeFactoryBase *shapeFactory = registry->value(id); shapeFactory->newDocumentResourceManager(resourceManager); } // read persistent application wide resources KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup miscGroup = config->group("Misc"); const uint grabSensitivity = miscGroup.readEntry("GrabSensitivity", 10); resourceManager->setGrabSensitivity(grabSensitivity); const uint handleRadius = miscGroup.readEntry("HandleRadius", 5); resourceManager->setHandleRadius(handleRadius); } ~KoshapeControllerBasePrivate() { delete resourceManager; } QPointer resourceManager; }; KoShapeControllerBase::KoShapeControllerBase() : d(new KoshapeControllerBasePrivate()) { } KoShapeControllerBase::~KoShapeControllerBase() { delete d; } -void KoShapeControllerBase::addShape(KoShape *shape) +KoShapeContainer* KoShapeControllerBase::createParentForShapes(const QList shapes, KUndo2Command *parentCommand) { - addShapes({shape}); -} + Q_UNUSED(parentCommand); + Q_UNUSED(shapes); -void KoShapeControllerBase::shapesRemoved(const QList & /*shapes*/, KUndo2Command * /*command*/) -{ + return 0; } KoDocumentResourceManager *KoShapeControllerBase::resourceManager() const { return d->resourceManager; } QRectF KoShapeControllerBase::documentRect() const { const qreal pxToPt = 72.0 / pixelsPerInch(); QTransform t = QTransform::fromScale(pxToPt, pxToPt); return t.mapRect(documentRectInPixels()); } diff --git a/libs/flake/KoShapeControllerBase.h b/libs/flake/KoShapeControllerBase.h index f14eee34a5..6491dfa700 100644 --- a/libs/flake/KoShapeControllerBase.h +++ b/libs/flake/KoShapeControllerBase.h @@ -1,110 +1,88 @@ /* This file is part of the KDE project Copyright (C) 2006 Jan Hambrecht Copyright (C) 2006, 2010 Thomas Zander Copyright (C) 2008 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. */ #ifndef KOshapeControllerBASE_H #define KOshapeControllerBASE_H #include "kritaflake_export.h" #include class QRectF; class KoShape; +class KoShapeContainer; class KoshapeControllerBasePrivate; class KoDocumentResourceManager; class KUndo2Command; /** * The KoshapeControllerBase is an abstract interface that the application's class * that owns the shapes should implement. This tends to be the document. * @see KoShapeDeleteCommand, KoShapeCreateCommand */ class KRITAFLAKE_EXPORT KoShapeControllerBase { public: KoShapeControllerBase(); virtual ~KoShapeControllerBase(); /** - * Add a shape to the shape controller, allowing it to be seen and saved. - * The controller should add the shape to the ShapeManager instance(s) manually - * if the shape is one that should be currently shown on screen. - * @param shape the new shape - */ - void addShape(KoShape *shape); - - /** - * Add shapes to the shape controller, allowing it to be seen and saved. - * The controller should add the shape to the ShapeManager instance(s) manually - * if the shape is one that should be currently shown on screen. - * @param shapes the shapes to add - */ - virtual void addShapes(const QList shapes) = 0; - - /** - * Remove a shape from the shape controllers control, allowing it to be deleted shortly after - * The controller should remove the shape from all the ShapeManager instance(s) manually - * @param shape the shape to remove - */ - virtual void removeShape(KoShape *shape) = 0; - - /** - * This method gets called after the KoShapeDeleteCommand is executed + * When shapes are dropped to the canvas, the document should decide, where to + * which parent to put them. In some cases the document should even create a + * special layer for the new \p shapes. * - * This passes the KoShapeDeleteCommand as the command parameter. This makes it possible - * for applications that need to do something after the KoShapeDeleteCommand is done, e.g. - * adding one commands that need to be executed when a shape was deleted. - * The default implementation is empty. - * @param shapes The list of shapes that got removed. - * @param command The command that was used to remove the shapes from the document. + * \return the proposed parent for \p shapes + * \param parentCommand the command, which should be executed before the + * proposed parent will be added to the document (if + * new layer should be created) */ - virtual void shapesRemoved(const QList &shapes, KUndo2Command *command); + virtual KoShapeContainer* createParentForShapes(const QList shapes, KUndo2Command *parentCommand); /** * Return a pointer to the resource manager associated with the * shape-set (typically a document). The resource manager contains * document wide resources * such as variable managers, the image * collection and others. */ virtual KoDocumentResourceManager *resourceManager() const; /** * The size of the document measured in rasterized pixels. This information is needed for loading * SVG documents that use 'px' as the default unit. */ virtual QRectF documentRectInPixels() const = 0; /** * The size of the document measured in 'pt' */ QRectF documentRect() const; /** * Resolution of the rasterized representation of the document. Used to load SVG documents correctly. */ virtual qreal pixelsPerInch() const = 0; private: KoshapeControllerBasePrivate * const d; }; #endif diff --git a/libs/flake/KoShapeManager.cpp b/libs/flake/KoShapeManager.cpp index b4eb4cceec..68890c9f67 100644 --- a/libs/flake/KoShapeManager.cpp +++ b/libs/flake/KoShapeManager.cpp @@ -1,784 +1,784 @@ /* 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 #include "kis_painting_tweaks.h" #include "kis_debug.h" #include "KisForest.h" #include namespace { /** * Returns whether the shape should be added to the RTree for collision and ROI * detection. */ inline bool shapeUsedInRenderingTree(KoShape *shape) { // FIXME: make more general! return !dynamic_cast(shape) && !dynamic_cast(shape) && !(dynamic_cast(shape) && !dynamic_cast(shape)); } /** * Returns whether a shape should be added to the rendering tree because of * its clip mask/path or effects. */ inline bool shapeHasGroupEffects(KoShape *shape) { return shape->clipPath() || (shape->filterEffectStack() && !shape->filterEffectStack()->isEmpty()) || shape->clipMask(); } /** * Returns true if the shape is not fully transparent */ inline bool shapeIsVisible(KoShape *shape) { return shape->isVisible(false) && shape->transparency() < 1.0; } /** * Populate \p tree with the subtree of shapes pointed by a shape \p parentShape. * All new shapes are added as children of \p parentIt. Please take it into account * that \c *parentIt might be not the same as \c parentShape, because \c parentShape * may be hidden from rendering. */ void populateRenderSubtree(KoShape *parentShape, KisForest::child_iterator parentIt, KisForest &tree, std::function shouldIncludeNode, std::function shouldEnterSubtree) { KoShapeContainer *parentContainer = dynamic_cast(parentShape); if (!parentContainer) return; QList children = parentContainer->shapes(); std::sort(children.begin(), children.end(), KoShape::compareShapeZIndex); for (auto it = children.constBegin(); it != children.constEnd(); ++it) { auto newParentIt = parentIt; if (shouldIncludeNode(*it)) { newParentIt = tree.insert(childEnd(parentIt), *it); } if (shouldEnterSubtree(*it)) { populateRenderSubtree(*it, newParentIt, tree, shouldIncludeNode, shouldEnterSubtree); } } } /** * Build a rendering tree for **leaf** nodes defined by \p leafNodes * * Sometimes we should render only a part of the layer (e.g. when we render * in patches). So we shouldn't render the whole graph. The problem is that * some of the shapes may have parents with clip paths/masks and/or effects. * In such a case, these parents should be also included into the rendering * process. * * \c buildRenderTree() builds a graph for such rendering. It includes the * leaf shapes themselves, and all parent shapes that have some effects affecting * these shapes. */ void buildRenderTree(QList leafShapes, KisForest &tree) { QList sortedShapes = leafShapes; std::sort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex); std::unordered_set includedShapes; Q_FOREACH (KoShape *shape, sortedShapes) { bool shouldSkipShape = !shapeIsVisible(shape); if (shouldSkipShape) continue; bool shapeIsPartOfIncludedSubtree = false; QVector hierarchy = {shape}; while ((shape = shape->parent())) { if (!shapeIsVisible(shape)) { shouldSkipShape = true; break; } if (includedShapes.find(shape) != end(includedShapes)) { shapeIsPartOfIncludedSubtree = true; break; } if (shapeHasGroupEffects(shape)) { hierarchy << shape; } } if (shouldSkipShape) continue; if (!shapeIsPartOfIncludedSubtree && includedShapes.find(hierarchy.last()) == end(includedShapes)) { tree.insert(childEnd(tree), hierarchy.last()); } std::copy(hierarchy.begin(), hierarchy.end(), std::inserter(includedShapes, end(includedShapes))); } auto shouldIncludeShape = [includedShapes] (KoShape *shape) { // included shapes are guaranteed to be visible return includedShapes.find(shape) != end(includedShapes); }; for (auto it = childBegin(tree); it != childEnd(tree); ++it) { populateRenderSubtree(*it, it, tree, shouldIncludeShape, &shapeIsVisible); } } /** * Render the prebuilt rendering tree on \p painter */ void renderShapes(typename KisForest::child_iterator beginIt, typename KisForest::child_iterator endIt, QPainter &painter, KoShapePaintingContext &paintContext) { for (auto it = beginIt; it != endIt; ++it) { KoShape *shape = *it; KisQPainterStateSaver saver(&painter); if (!isEnd(parent(it))) { painter.setTransform(shape->transformation() * painter.transform()); } else { painter.setTransform(shape->absoluteTransformation() * painter.transform()); } KoClipPath::applyClipping(shape, painter); qreal transparency = shape->transparency(true); if (transparency > 0.0) { painter.setOpacity(1.0-transparency); } if (shape->shadow()) { KisQPainterStateSaver saver(&painter); shape->shadow()->paint(shape, painter); } QScopedPointer clipMaskPainter; QPainter *shapePainter = &painter; KoClipMask *clipMask = shape->clipMask(); if (clipMask) { const QRectF bounds = painter.transform().mapRect(shape->outlineRect()); clipMaskPainter.reset(new KoClipMaskPainter(&painter, bounds/*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(); renderShapes(childBegin(it), childEnd(it), *shapePainter, paintContext); shape->paint(*shapePainter, paintContext); shape->paintStroke(*shapePainter, paintContext); KIS_SAFE_ASSERT_RECOVER(shapePainter->transform() == sanityCheckTransformSaved) { shapePainter->setTransform(sanityCheckTransformSaved); } if (clipMask) { clipMaskPainter->maskPainter()->save(); shape->clipMask()->drawMask(clipMaskPainter->maskPainter(), shape); clipMaskPainter->renderOnGlobalPainter(); clipMaskPainter->maskPainter()->restore(); } } } } void KoShapeManager::Private::updateTree() { bool selectionModified = false; bool anyModified = false; { QMutexLocker l(&this->treeMutex); Q_FOREACH (KoShape *shape, aggregate4update) { 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); } aggregate4update.clear(); shapeIndexesBeforeUpdate.clear(); } 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); } 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 (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); } } } void KoShapeManager::remove(KoShape *shape) { QRectF dirtyRect; { QMutexLocker l1(&d->shapesMutex); QMutexLocker l2(&d->treeMutex); - dirtyRect = shape->absoluteOutlineRect(); + dirtyRect = shape->boundingRect(); shape->removeShapeManager(this); d->selection->deselect(shape); d->aggregate4update.remove(shape); d->compressedUpdatedShapes.remove(shape); if (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::preparePaintJobs(PaintJobsOrder &jobsOrder, KoShape *excludeRoot) { d->updateTree(); QMutexLocker l1(&d->shapesMutex); QSet rootShapesSet; Q_FOREACH (KoShape *shape, d->shapes) { while (shape->parent() && shape->parent() != excludeRoot) { shape = shape->parent(); } if (!rootShapesSet.contains(shape) && shape != excludeRoot) { rootShapesSet.insert(shape); } } #if QT_VERSION >= QT_VERSION_CHECK(5,14,0) const QList rootShapes(rootShapesSet.begin(), rootShapesSet.end()); #else const QList rootShapes = QList::fromSet(rootShapesSet); #endif QList newRootShapes; Q_FOREACH (KoShape *srcShape, rootShapes) { KIS_SAFE_ASSERT_RECOVER(srcShape->parent() == excludeRoot) { continue; } KoShape *clonedShape = srcShape->cloneShape(); KoShapeContainer *parentShape = srcShape->parent(); if (parentShape && !parentShape->transformation().isIdentity()) { clonedShape->applyAbsoluteTransformation(parentShape->transformation()); } newRootShapes << clonedShape; } PaintJobsOrder result; PaintJob::SharedSafeStorage shapesStorage = std::make_shared(); Q_FOREACH (KoShape *shape, newRootShapes) { shapesStorage->emplace_back(std::unique_ptr(shape)); } const QList originalShapes = KoShape::linearizeSubtreeSorted(rootShapes); const QList clonedShapes = KoShape::linearizeSubtreeSorted(newRootShapes); KIS_SAFE_ASSERT_RECOVER_RETURN(clonedShapes.size() == originalShapes.size()); QHash clonedFromOriginal; for (int i = 0; i < originalShapes.size(); i++) { clonedFromOriginal[originalShapes[i]] = clonedShapes[i]; } for (auto it = std::begin(jobsOrder.jobs); it != std::end(jobsOrder.jobs); ++it) { QMutexLocker l(&d->treeMutex); QList unsortedOriginalShapes = d->tree.intersects(it->docUpdateRect); it->allClonedShapes = shapesStorage; Q_FOREACH (KoShape *shape, unsortedOriginalShapes) { KIS_SAFE_ASSERT_RECOVER(shapeUsedInRenderingTree(shape)) { continue; } it->shapes << clonedFromOriginal[shape]; } } } void KoShapeManager::paintJob(QPainter &painter, const KoShapeManager::PaintJob &job, bool forPrint) { painter.setPen(Qt::NoPen); // painters by default have a black stroke, lets turn that off. painter.setBrush(Qt::NoBrush); KisForest renderTree; buildRenderTree(job.shapes, renderTree); KoShapePaintingContext paintContext(d->canvas, forPrint); //FIXME renderShapes(childBegin(renderTree), childEnd(renderTree), painter, paintContext); } void KoShapeManager::paint(QPainter &painter, bool forPrint) { d->updateTree(); QMutexLocker l1(&d->shapesMutex); 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 = 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!"; } KoShapePaintingContext paintContext(d->canvas, forPrint); //FIXME KisForest renderTree; buildRenderTree(unsortedShapes, renderTree); renderShapes(childBegin(renderTree), childEnd(renderTree), painter, paintContext); } void KoShapeManager::renderSingleShape(KoShape *shape, QPainter &painter, KoShapePaintingContext &paintContext) { KisForest renderTree; KoViewConverter converter; auto root = renderTree.insert(childBegin(renderTree), shape); populateRenderSubtree(shape, root, renderTree, &shapeIsVisible, &shapeIsVisible); renderShapes(childBegin(renderTree), childEnd(renderTree), painter, paintContext); } KoShape *KoShapeManager::shapeAt(const QPointF &position, KoFlake::ShapeSelection selection, bool omitHiddenShapes) { d->updateTree(); QMutexLocker l(&d->shapesMutex); 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().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/SimpleShapeContainerModel.h b/libs/flake/SimpleShapeContainerModel.h index 8a777b36c5..ff2c935adc 100644 --- a/libs/flake/SimpleShapeContainerModel.h +++ b/libs/flake/SimpleShapeContainerModel.h @@ -1,126 +1,167 @@ /* This file is part of the KDE project * Copyright (C) 2006-2007, 2010 Thomas Zander * Copyright (C) 2011 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef SIMPLESHAPECONTAINERMODEL_H #define SIMPLESHAPECONTAINERMODEL_H #include "KoShapeContainerModel.h" #include +#include /// \internal class SimpleShapeContainerModel: public KoShapeContainerModel { public: SimpleShapeContainerModel() {} ~SimpleShapeContainerModel() override {} SimpleShapeContainerModel(const SimpleShapeContainerModel &rhs) : KoShapeContainerModel(rhs), m_inheritsTransform(rhs.m_inheritsTransform), m_clipped(rhs.m_clipped) { Q_FOREACH (KoShape *shape, rhs.m_members) { KoShape *clone = shape->cloneShape(); KIS_SAFE_ASSERT_RECOVER_NOOP(clone && "Copying this shape is not implemented!"); if (clone) { m_members << clone; } } KIS_ASSERT_RECOVER(m_members.size() == m_inheritsTransform.size() && m_members.size() == m_clipped.size()) { qDeleteAll(m_members); m_members.clear(); m_inheritsTransform.clear(); m_clipped.clear(); } } void add(KoShape *child) override { if (m_members.contains(child)) return; m_members.append(child); m_clipped.append(false); m_inheritsTransform.append(true); } void setClipped(const KoShape *shape, bool value) override { const int index = indexOf(shape); KIS_SAFE_ASSERT_RECOVER_RETURN(index >= 0); m_clipped[index] = value; } bool isClipped(const KoShape *shape) const override { const int index = indexOf(shape); KIS_SAFE_ASSERT_RECOVER(index >= 0) { return false;} return m_clipped[index]; } void remove(KoShape *shape) override { const int index = indexOf(shape); KIS_SAFE_ASSERT_RECOVER_RETURN(index >= 0); m_members.removeAt(index); m_clipped.removeAt(index); m_inheritsTransform.removeAt(index); } int count() const override { return m_members.count(); } QList shapes() const override { return QList(m_members); } void containerChanged(KoShapeContainer *, KoShape::ChangeType) override { } void setInheritsTransform(const KoShape *shape, bool value) override { const int index = indexOf(shape); KIS_SAFE_ASSERT_RECOVER_RETURN(index >= 0); m_inheritsTransform[index] = value; } bool inheritsTransform(const KoShape *shape) const override { const int index = indexOf(shape); KIS_SAFE_ASSERT_RECOVER(index >= 0) { return true;} return m_inheritsTransform[index]; } void proposeMove(KoShape *shape, QPointF &move) override { KoShapeContainer *parent = shape->parent(); bool allowedToMove = true; while (allowedToMove && parent) { allowedToMove = parent->isShapeEditable(); parent = parent->parent(); } if (! allowedToMove) { move.setX(0); move.setY(0); } } + void shapeHasBeenAddedToHierarchy(KoShape *shape, KoShapeContainer *addedToSubtree) override { + if (m_associatedRootShapeManager) { + m_associatedRootShapeManager->addShape(shape); + } + KoShapeContainerModel::shapeHasBeenAddedToHierarchy(shape, addedToSubtree); + } + + void shapeToBeRemovedFromHierarchy(KoShape *shape, KoShapeContainer *removedFromSubtree) override { + if (m_associatedRootShapeManager) { + m_associatedRootShapeManager->remove(shape); + } + KoShapeContainerModel::shapeToBeRemovedFromHierarchy(shape, removedFromSubtree); + } + + KoShapeManager *associatedRootShapeManager() const { + return m_associatedRootShapeManager; + } + + /** + * If the container is the root of shapes hierarchy, it should also track the content + * of the shape manager. Add all added/removed shapes should be also + * added to \p shapeManager. + */ + void setAssociatedRootShapeManager(KoShapeManager *manager) { + if (m_associatedRootShapeManager) { + Q_FOREACH(KoShape *shape, this->shapes()) { + m_associatedRootShapeManager->remove(shape); + } + } + + m_associatedRootShapeManager = manager; + + if (m_associatedRootShapeManager) { + Q_FOREACH(KoShape *shape, this->shapes()) { + m_associatedRootShapeManager->addShape(shape); + } + } + } + private: int indexOf(const KoShape *shape) const { // workaround indexOf constness! return m_members.indexOf(const_cast(shape)); } private: // members QList m_members; QList m_inheritsTransform; QList m_clipped; + KoShapeManager *m_associatedRootShapeManager = 0; }; #endif diff --git a/libs/flake/commands/KoAddRemoveShapeCommands.cpp b/libs/flake/commands/KoAddRemoveShapeCommands.cpp new file mode 100644 index 0000000000..1b4181431e --- /dev/null +++ b/libs/flake/commands/KoAddRemoveShapeCommands.cpp @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2020 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 "KoAddRemoveShapeCommands.h" + +#include +#include + + +KoAddRemoveShapeCommandImpl::KoAddRemoveShapeCommandImpl(KoShape *shape, KoShapeContainer *parent, KisCommandUtils::FlipFlopCommand::State state, KUndo2Command *parentCommand) + : KisCommandUtils::FlipFlopCommand(state, parentCommand), + m_shape(shape), + m_parent(parent) +{ +} + +KoAddRemoveShapeCommandImpl::~KoAddRemoveShapeCommandImpl() { + if (m_ownShape) { + delete m_shape; + } +} + +void KoAddRemoveShapeCommandImpl::partB() +{ + KIS_SAFE_ASSERT_RECOVER_RETURN(m_shape); + KIS_SAFE_ASSERT_RECOVER_RETURN(m_parent); + + m_parent->removeShape(m_shape); + m_ownShape = true; +} + +void KoAddRemoveShapeCommandImpl::partA() +{ + KIS_SAFE_ASSERT_RECOVER_RETURN(m_shape); + KIS_SAFE_ASSERT_RECOVER_RETURN(m_parent); + + m_parent->addShape(m_shape); + m_ownShape = false; +} + +KoAddShapeCommand::KoAddShapeCommand(KoShape *shape, KoShapeContainer *parent, KUndo2Command *parentCommand) + : KoAddRemoveShapeCommandImpl(shape, parent, INITIALIZING, parentCommand) +{ +} + +KoRemoveShapeCommand::KoRemoveShapeCommand(KoShape *shape, KoShapeContainer *parent, KUndo2Command *parentCommand) + : KoAddRemoveShapeCommandImpl(shape, parent, FINALIZING, parentCommand) +{ +} diff --git a/libs/flake/commands/KoAddRemoveShapeCommands.h b/libs/flake/commands/KoAddRemoveShapeCommands.h new file mode 100644 index 0000000000..410093de36 --- /dev/null +++ b/libs/flake/commands/KoAddRemoveShapeCommands.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2020 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 KoAddRemoveShapeCommands_H +#define KoAddRemoveShapeCommands_H + +#include "kritaflake_export.h" + +#include "kis_command_utils.h" + +class KoShape; +class KoShapeContainer; + + +struct KRITAFLAKE_EXPORT KoAddRemoveShapeCommandImpl : public KisCommandUtils::FlipFlopCommand +{ + KoAddRemoveShapeCommandImpl(KoShape *shape, KoShapeContainer *parent, State state, KUndo2Command *parentCommand); + ~KoAddRemoveShapeCommandImpl(); + + void partA() override; + void partB() override; + +private: + bool m_ownShape = true; + KoShape *m_shape = 0; + KoShapeContainer *m_parent = 0; +}; + +struct KRITAFLAKE_EXPORT KoAddShapeCommand : public KoAddRemoveShapeCommandImpl +{ + KoAddShapeCommand(KoShape *shape, KoShapeContainer *parent, KUndo2Command *parentCommand = 0); +}; + +struct KRITAFLAKE_EXPORT KoRemoveShapeCommand : public KoAddRemoveShapeCommandImpl +{ + KoRemoveShapeCommand(KoShape *shape, KoShapeContainer *parent, KUndo2Command *parentCommand = 0); +}; + +#endif // KoAddRemoveShapeCommands_H diff --git a/libs/flake/commands/KoPathCombineCommand.cpp b/libs/flake/commands/KoPathCombineCommand.cpp index ae2334e88c..9b9d472292 100644 --- a/libs/flake/commands/KoPathCombineCommand.cpp +++ b/libs/flake/commands/KoPathCombineCommand.cpp @@ -1,144 +1,140 @@ /* This file is part of the KDE project * Copyright (C) 2006,2008 Jan Hambrecht * Copyright (C) 2006,2007 Thorsten Zachmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoPathCombineCommand.h" #include "KoShapeControllerBase.h" #include "KoShapeContainer.h" #include "KoPathShape.h" #include #include "kis_assert.h" #include #include class Q_DECL_HIDDEN KoPathCombineCommand::Private { public: Private(KoShapeControllerBase *c, const QList &p) : controller(c), paths(p) , combinedPath(0) , combinedPathParent(0) , isCombined(false) { foreach (KoPathShape * path, paths) { oldParents.append(path->parent()); } } ~Private() { if (isCombined && controller) { Q_FOREACH (KoPathShape* path, paths) { delete path; } } else { delete combinedPath; } } KoShapeControllerBase *controller; QList paths; QList oldParents; KoPathShape *combinedPath; KoShapeContainer *combinedPathParent; QHash shapeStartSegmentIndex; bool isCombined; }; KoPathCombineCommand::KoPathCombineCommand(KoShapeControllerBase *controller, const QList &paths, KUndo2Command *parent) : KUndo2Command(kundo2_i18n("Combine paths"), parent) , d(new Private(controller, paths)) { KIS_SAFE_ASSERT_RECOVER_RETURN(!paths.isEmpty()); Q_FOREACH (KoPathShape* path, d->paths) { if (!d->combinedPath) { KoPathShape *clone = dynamic_cast(path->cloneShape()); KIS_ASSERT_RECOVER_BREAK(clone); d->combinedPath = clone; d->combinedPathParent = path->parent(); d->shapeStartSegmentIndex[path] = 0; } else { const int startSegmentIndex = d->combinedPath->combine(path); d->shapeStartSegmentIndex[path] = startSegmentIndex; } } } KoPathCombineCommand::~KoPathCombineCommand() { delete d; } void KoPathCombineCommand::redo() { KUndo2Command::redo(); if (d->paths.isEmpty()) return; d->isCombined = true; if (d->controller) { Q_FOREACH (KoPathShape* p, d->paths) { - d->controller->removeShape(p); p->setParent(0); } d->combinedPath->setParent(d->combinedPathParent); - d->controller->addShape(d->combinedPath); } } void KoPathCombineCommand::undo() { if (! d->paths.size()) return; d->isCombined = false; if (d->controller) { - d->controller->removeShape(d->combinedPath); d->combinedPath->setParent(0); auto parentIt = d->oldParents.begin(); Q_FOREACH (KoPathShape* p, d->paths) { p->setParent(*parentIt); - d->controller->addShape(p); ++parentIt; } } KUndo2Command::undo(); } KoPathShape *KoPathCombineCommand::combinedPath() const { return d->combinedPath; } KoPathPointData KoPathCombineCommand::originalToCombined(KoPathPointData pd) const { KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(d->shapeStartSegmentIndex.contains(pd.pathShape), pd); const int segmentOffet = d->shapeStartSegmentIndex[pd.pathShape]; KoPathPointIndex newIndex(segmentOffet + pd.pointIndex.first, pd.pointIndex.second); return KoPathPointData(d->combinedPath, newIndex); } diff --git a/libs/flake/commands/KoPathShapeMarkerCommand.cpp b/libs/flake/commands/KoPathShapeMarkerCommand.cpp index 8aeacef885..d7a545e428 100644 --- a/libs/flake/commands/KoPathShapeMarkerCommand.cpp +++ b/libs/flake/commands/KoPathShapeMarkerCommand.cpp @@ -1,104 +1,105 @@ /* This file is part of the KDE project * Copyright (C) 2010 Jeremy Lugagne * 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 "KoPathShapeMarkerCommand.h" #include "KoMarker.h" #include "KoPathShape.h" #include #include "kis_command_ids.h" #include struct Q_DECL_HIDDEN KoPathShapeMarkerCommand::Private { QList shapes; ///< the shapes to set marker for QList> oldMarkers; ///< the old markers, one for each shape QExplicitlySharedDataPointer marker; ///< the new marker to set KoFlake::MarkerPosition position; QList oldAutoFillMarkers; }; KoPathShapeMarkerCommand::KoPathShapeMarkerCommand(const QList &shapes, KoMarker *marker, KoFlake::MarkerPosition position, KUndo2Command *parent) : KUndo2Command(kundo2_i18n("Set marker"), parent), m_d(new Private) { m_d->shapes = shapes; m_d->marker = marker; m_d->position = position; // save old markers Q_FOREACH (KoPathShape *shape, m_d->shapes) { m_d->oldMarkers.append(QExplicitlySharedDataPointer(shape->marker(position))); m_d->oldAutoFillMarkers.append(shape->autoFillMarkers()); } } KoPathShapeMarkerCommand::~KoPathShapeMarkerCommand() { } void KoPathShapeMarkerCommand::redo() { KUndo2Command::redo(); Q_FOREACH (KoPathShape *shape, m_d->shapes) { - shape->update(); + const QRectF oldDirtyRect = shape->boundingRect(); shape->setMarker(m_d->marker.data(), m_d->position); // we have no GUI for selection auto-filling yet! So just enable it! shape->setAutoFillMarkers(true); - shape->update(); + shape->updateAbsolute(oldDirtyRect | shape->boundingRect()); } } void KoPathShapeMarkerCommand::undo() { KUndo2Command::undo(); auto markerIt = m_d->oldMarkers.begin(); auto autoFillIt = m_d->oldAutoFillMarkers.begin(); Q_FOREACH (KoPathShape *shape, m_d->shapes) { - shape->update(); + const QRectF oldDirtyRect = shape->boundingRect(); shape->setMarker((*markerIt).data(), m_d->position); shape->setAutoFillMarkers(*autoFillIt); - shape->update(); + shape->updateAbsolute(oldDirtyRect | shape->boundingRect()); ++markerIt; + ++autoFillIt; } } int KoPathShapeMarkerCommand::id() const { return KisCommandUtils::ChangeShapeMarkersId; } bool KoPathShapeMarkerCommand::mergeWith(const KUndo2Command *command) { const KoPathShapeMarkerCommand *other = dynamic_cast(command); if (!other || other->m_d->shapes != m_d->shapes || other->m_d->position != m_d->position) { return false; } m_d->marker = other->m_d->marker; return true; } diff --git a/libs/flake/commands/KoShapeClipCommand.cpp b/libs/flake/commands/KoShapeClipCommand.cpp index 7acfdddc51..f753a71c33 100644 --- a/libs/flake/commands/KoShapeClipCommand.cpp +++ b/libs/flake/commands/KoShapeClipCommand.cpp @@ -1,132 +1,131 @@ /* This file is part of the KDE project * Copyright (C) 2011 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 "KoShapeClipCommand.h" #include "KoClipPath.h" #include "KoShape.h" #include "KoShapeContainer.h" #include "KoPathShape.h" #include "KoShapeControllerBase.h" #include #include "kis_pointer_utils.h" class Q_DECL_HIDDEN KoShapeClipCommand::Private { public: Private(KoShapeControllerBase *c) : controller(c), executed(false) { } ~Private() { if (executed) { qDeleteAll(oldClipPaths); } else { qDeleteAll(newClipPaths); } } QList shapesToClip; QList oldClipPaths; QList clipPathShapes; QList newClipPaths; QList oldParents; KoShapeControllerBase *controller; bool executed; }; KoShapeClipCommand::KoShapeClipCommand(KoShapeControllerBase *controller, const QList &shapes, const QList &clipPathShapes, KUndo2Command *parent) : KUndo2Command(parent), d(new Private(controller)) { d->shapesToClip = shapes; d->clipPathShapes = clipPathShapes; Q_FOREACH (KoShape *shape, d->shapesToClip) { d->oldClipPaths.append(shape->clipPath()); d->newClipPaths.append(new KoClipPath(implicitCastList(clipPathShapes), KoFlake::UserSpaceOnUse)); } Q_FOREACH (KoPathShape *path, clipPathShapes) { d->oldParents.append(path->parent()); } setText(kundo2_i18n("Clip Shape")); } KoShapeClipCommand::KoShapeClipCommand(KoShapeControllerBase *controller, KoShape *shape, const QList &clipPathShapes, KUndo2Command *parent) : KUndo2Command(parent), d(new Private(controller)) { d->shapesToClip.append(shape); d->clipPathShapes = clipPathShapes; d->oldClipPaths.append(shape->clipPath()); d->newClipPaths.append(new KoClipPath(implicitCastList(clipPathShapes), KoFlake::UserSpaceOnUse)); Q_FOREACH (KoPathShape *path, clipPathShapes) { d->oldParents.append(path->parent()); } setText(kundo2_i18n("Clip Shape")); } KoShapeClipCommand::~KoShapeClipCommand() { delete d; } void KoShapeClipCommand::redo() { const uint shapeCount = d->shapesToClip.count(); for (uint i = 0; i < shapeCount; ++i) { d->shapesToClip[i]->setClipPath(d->newClipPaths[i]); d->shapesToClip[i]->update(); } const uint clipPathCount = d->clipPathShapes.count(); for (uint i = 0; i < clipPathCount; ++i) { - d->controller->removeShape(d->clipPathShapes[i]); - if (d->oldParents.at(i)) + if (d->oldParents.at(i)) { d->oldParents.at(i)->removeShape(d->clipPathShapes[i]); + } } d->executed = true; KUndo2Command::redo(); } void KoShapeClipCommand::undo() { KUndo2Command::undo(); const uint shapeCount = d->shapesToClip.count(); for (uint i = 0; i < shapeCount; ++i) { d->shapesToClip[i]->setClipPath(d->oldClipPaths[i]); d->shapesToClip[i]->update(); } const uint clipPathCount = d->clipPathShapes.count(); for (uint i = 0; i < clipPathCount; ++i) { - if (d->oldParents.at(i)) + if (d->oldParents.at(i)) { d->oldParents.at(i)->addShape(d->clipPathShapes[i]); - // the parent has to be there when it is added to the KoShapeControllerBase - d->controller->addShape(d->clipPathShapes[i]); + } } d->executed = false; } diff --git a/libs/flake/commands/KoShapeCreateCommand.cpp b/libs/flake/commands/KoShapeCreateCommand.cpp index bada674723..72b566e4d6 100644 --- a/libs/flake/commands/KoShapeCreateCommand.cpp +++ b/libs/flake/commands/KoShapeCreateCommand.cpp @@ -1,129 +1,106 @@ /* This file is part of the KDE project * Copyright (C) 2006 Thomas Zander * Copyright (C) 2006 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 "KoShapeCreateCommand.h" #include "KoShape.h" #include "KoShapeContainer.h" #include "KoShapeControllerBase.h" #include #include "kis_assert.h" #include #include #include #include +#include +#include + class Q_DECL_HIDDEN KoShapeCreateCommand::Private { public: Private(KoShapeControllerBase *_document, const QList &_shapes, KoShapeContainer *_parentShape) : shapesDocument(_document), shapes(_shapes), - explicitParentShape(_parentShape), - deleteShapes(true) + explicitParentShape(_parentShape) { } - ~Private() { - if (deleteShapes) { - qDeleteAll(shapes); - } - } - KoShapeControllerBase *shapesDocument; QList shapes; KoShapeContainer *explicitParentShape; - bool deleteShapes; - std::vector> reorderingCommands; + KisSurrogateUndoStore undoStore; + bool firstRedo = true; + }; KoShapeCreateCommand::KoShapeCreateCommand(KoShapeControllerBase *controller, KoShape *shape, KoShapeContainer *parentShape, KUndo2Command *parent) : KoShapeCreateCommand(controller, QList() << shape, parentShape, parent) { } KoShapeCreateCommand::KoShapeCreateCommand(KoShapeControllerBase *controller, const QList shapes, KoShapeContainer *parentShape, KUndo2Command *parent) : KoShapeCreateCommand(controller, shapes, parentShape, parent, kundo2_i18np("Create shape", "Create %1 shapes", shapes.size())) { } KoShapeCreateCommand::KoShapeCreateCommand(KoShapeControllerBase *controller, const QList shapes, KoShapeContainer *parentShape, KUndo2Command *parent, const KUndo2MagicString &undoString) : KUndo2Command(undoString, parent) , d(new Private(controller, shapes, parentShape)) { } KoShapeCreateCommand::~KoShapeCreateCommand() { delete d; } void KoShapeCreateCommand::redo() { KUndo2Command::redo(); - KIS_ASSERT(d->shapesDocument); - - d->deleteShapes = false; - d->reorderingCommands.clear(); - - Q_FOREACH(KoShape *shape, d->shapes) { - if (d->explicitParentShape) { - shape->setParent(d->explicitParentShape); - } - - d->shapesDocument->addShape(shape); + KIS_SAFE_ASSERT_RECOVER_RETURN(d->explicitParentShape); - KoShapeContainer *shapeParent = shape->parent(); + if (d->firstRedo) { + Q_FOREACH(KoShape *shape, d->shapes) { - KIS_SAFE_ASSERT_RECOVER_NOOP(shape->parent() || - dynamic_cast(shape)); + d->undoStore.addCommand(new KoAddShapeCommand(shape, d->explicitParentShape)); - if (shapeParent) { - KUndo2Command *cmd = KoShapeReorderCommand::mergeInShape(shapeParent->shapes(), shape); + KoShapeContainer *shapeParent = shape->parent(); + KIS_SAFE_ASSERT_RECOVER_NOOP(shape->parent() || + dynamic_cast(shape)); - if (cmd) { - cmd->redo(); - d->reorderingCommands.push_back( - std::unique_ptr(cmd)); + if (shapeParent) { + d->undoStore.addCommand(KoShapeReorderCommand::mergeInShape(shapeParent->shapes(), shape)); } } + d->firstRedo = false; + } else { + d->undoStore.redoAll(); } } void KoShapeCreateCommand::undo() { + d->undoStore.undoAll(); KUndo2Command::undo(); - KIS_ASSERT(d->shapesDocument); - - while (!d->reorderingCommands.empty()) { - std::unique_ptr cmd = std::move(d->reorderingCommands.back()); - cmd->undo(); - d->reorderingCommands.pop_back(); - } - - Q_FOREACH(KoShape *shape, d->shapes) { - d->shapesDocument->removeShape(shape); - } - - d->deleteShapes = true; } diff --git a/libs/flake/commands/KoShapeDeleteCommand.cpp b/libs/flake/commands/KoShapeDeleteCommand.cpp index 3d369bdfdd..9bc335e9d2 100644 --- a/libs/flake/commands/KoShapeDeleteCommand.cpp +++ b/libs/flake/commands/KoShapeDeleteCommand.cpp @@ -1,105 +1,103 @@ /* This file is part of the KDE project * Copyright (C) 2006 Thomas Zander * Copyright (C) 2006 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 "KoShapeDeleteCommand.h" #include "KoShapeContainer.h" #include "KoShapeControllerBase.h" #include class Q_DECL_HIDDEN KoShapeDeleteCommand::Private { public: Private(KoShapeControllerBase *c) : controller(c), deleteShapes(false) { } ~Private() { if (! deleteShapes) return; Q_FOREACH (KoShape *shape, shapes) delete shape; } KoShapeControllerBase *controller; ///< the shape controller to use for removing/readding QList shapes; ///< the list of shapes to delete QList oldParents; ///< the old parents of the shapes bool deleteShapes; ///< shows if shapes should be deleted when deleting the command }; KoShapeDeleteCommand::KoShapeDeleteCommand(KoShapeControllerBase *controller, KoShape *shape, KUndo2Command *parent) : KUndo2Command(parent), d(new Private(controller)) { d->shapes.append(shape); d->oldParents.append(shape->parent()); setText(kundo2_i18n("Delete shape")); } KoShapeDeleteCommand::KoShapeDeleteCommand(KoShapeControllerBase *controller, const QList &shapes, KUndo2Command *parent) : KUndo2Command(parent), d(new Private(controller)) { d->shapes = shapes; Q_FOREACH (KoShape *shape, d->shapes) { d->oldParents.append(shape->parent()); } setText(kundo2_i18np("Delete shape", "Delete shapes", shapes.count())); } KoShapeDeleteCommand::~KoShapeDeleteCommand() { delete d; } void KoShapeDeleteCommand::redo() { KUndo2Command::redo(); if (! d->controller) return; for (int i = 0; i < d->shapes.count(); i++) { - // the parent has to be there when it is removed from the KoShapeControllerBase - d->controller->removeShape(d->shapes[i]); - if (d->oldParents.at(i)) + if (d->oldParents.at(i)) { d->oldParents.at(i)->removeShape(d->shapes[i]); + } } d->deleteShapes = true; } void KoShapeDeleteCommand::undo() { KUndo2Command::undo(); if (! d->controller) return; for (int i = 0; i < d->shapes.count(); i++) { - if (d->oldParents.at(i)) + if (d->oldParents.at(i)) { d->oldParents.at(i)->addShape(d->shapes[i]); - // the parent has to be there when it is added to the KoShapeControllerBase - d->controller->addShape(d->shapes[i]); + } } d->deleteShapes = false; } diff --git a/libs/flake/commands/KoShapeUnclipCommand.cpp b/libs/flake/commands/KoShapeUnclipCommand.cpp index 7d365df77f..61c250946c 100644 --- a/libs/flake/commands/KoShapeUnclipCommand.cpp +++ b/libs/flake/commands/KoShapeUnclipCommand.cpp @@ -1,154 +1,153 @@ /* This file is part of the KDE project * Copyright (C) 2011 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 "KoShapeUnclipCommand.h" #include "KoClipPath.h" #include "KoShape.h" #include "KoShapeContainer.h" #include "KoPathShape.h" #include "KoShapeControllerBase.h" #include #include class KoShapeUnclipCommand::Private { public: Private(KoShapeControllerBase *c) : controller(c), executed(false) { } ~Private() { if (executed) { qDeleteAll(oldClipPaths); } else { qDeleteAll(clipPathShapes); } } void createClipPathShapes() { // check if we have already created the clip path shapes if (!clipPathShapes.isEmpty()) return; Q_FOREACH (KoShape *shape, shapesToUnclip) { KoClipPath *clipPath = shape->clipPath(); if (!clipPath) continue; Q_FOREACH (KoShape *clipShape, clipPath->clipPathShapes()) { KoShape *clone = clipShape->cloneShape(); KoPathShape *pathShape = dynamic_cast(clone); KIS_SAFE_ASSERT_RECOVER(pathShape) { delete clone; continue; } clipPathShapes.append(pathShape); } // apply transformations Q_FOREACH (KoPathShape *pathShape, clipPathShapes) { // apply transformation so that it matches the current clipped shapes clip path pathShape->applyAbsoluteTransformation(clipPath->clipDataTransformation(shape)); pathShape->setZIndex(shape->zIndex() + 1); clipPathParents.append(shape->parent()); } } } QList shapesToUnclip; QList oldClipPaths; QList clipPathShapes; QList clipPathParents; // TODO: damn! this is not a controller, this is a document! Needs refactoring! KoShapeControllerBase *controller; bool executed; }; KoShapeUnclipCommand::KoShapeUnclipCommand(KoShapeControllerBase *controller, const QList &shapes, KUndo2Command *parent) : KUndo2Command(parent), d(new Private(controller)) { d->shapesToUnclip = shapes; Q_FOREACH (KoShape *shape, d->shapesToUnclip) { d->oldClipPaths.append(shape->clipPath()); } setText(kundo2_i18n("Unclip Shape")); } KoShapeUnclipCommand::KoShapeUnclipCommand(KoShapeControllerBase *controller, KoShape *shape, KUndo2Command *parent) : KUndo2Command(parent), d(new Private(controller)) { d->shapesToUnclip.append(shape); d->oldClipPaths.append(shape->clipPath()); setText(kundo2_i18n("Unclip Shapes")); } KoShapeUnclipCommand::~KoShapeUnclipCommand() { delete d; } void KoShapeUnclipCommand::redo() { d->createClipPathShapes(); const uint shapeCount = d->shapesToUnclip.count(); for (uint i = 0; i < shapeCount; ++i) { d->shapesToUnclip[i]->setClipPath(0); d->shapesToUnclip[i]->update(); } const uint clipPathCount = d->clipPathShapes.count(); for (uint i = 0; i < clipPathCount; ++i) { - // the parent has to be there when it is added to the KoShapeControllerBase - if (d->clipPathParents.at(i)) + if (d->clipPathParents.at(i)) { d->clipPathParents.at(i)->addShape(d->clipPathShapes[i]); - d->controller->addShape(d->clipPathShapes[i]); + } } d->executed = true; KUndo2Command::redo(); } void KoShapeUnclipCommand::undo() { KUndo2Command::undo(); const uint shapeCount = d->shapesToUnclip.count(); for (uint i = 0; i < shapeCount; ++i) { d->shapesToUnclip[i]->setClipPath(d->oldClipPaths[i]); d->shapesToUnclip[i]->update(); } const uint clipPathCount = d->clipPathShapes.count(); for (uint i = 0; i < clipPathCount; ++i) { - d->controller->removeShape(d->clipPathShapes[i]); - if (d->clipPathParents.at(i)) + if (d->clipPathParents.at(i)) { d->clipPathParents.at(i)->removeShape(d->clipPathShapes[i]); + } } d->executed = false; } diff --git a/libs/flake/styles/markers.svg b/libs/flake/styles/markers.svg index c7cf439dd0..93a6ce1b35 100644 --- a/libs/flake/styles/markers.svg +++ b/libs/flake/styles/markers.svg @@ -1,149 +1,149 @@ diff --git a/libs/flake/tests/MockShapes.h b/libs/flake/tests/MockShapes.h index 376fa792b5..05514e7213 100644 --- a/libs/flake/tests/MockShapes.h +++ b/libs/flake/tests/MockShapes.h @@ -1,240 +1,240 @@ /* * This file is part of Calligra tests * * Copyright (C) 2006-2010 Thomas Zander * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public 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 MOCKSHAPES_H #define MOCKSHAPES_H #include #include #include #include #include #include #include "KoShapeManager.h" #include "FlakeDebug.h" #include "KoSnapData.h" #include "KoUnit.h" #include "kritaflake_export.h" +#include "kis_assert.h" class KRITAFLAKE_EXPORT MockShape : public KoShape { public: MockShape() : paintedCount(0) {} void paint(QPainter &painter, KoShapePaintingContext &) const override { Q_UNUSED(painter); //qDebug() << "Shape" << kBacktrace( 10 ); paintedCount++; } mutable int paintedCount; }; +#include + class KRITAFLAKE_EXPORT MockContainer : public KoShapeContainer { public: - MockContainer(KoShapeContainerModel *model = 0) : KoShapeContainer(model), paintedCount(0) {} + MockContainer(KoShapeContainerModel *model = new SimpleShapeContainerModel()) : KoShapeContainer(model), paintedCount(0) {} void paintComponent(QPainter &painter, KoShapePaintingContext &) const override { Q_UNUSED(painter); //qDebug() << "Container:" << kBacktrace( 10 ); paintedCount++; } mutable int paintedCount; + + bool contains(KoShape *shape) const { + return shapes().contains(shape); + } + + void setAssociatedRootShapeManager(KoShapeManager *shapeManager) + { + SimpleShapeContainerModel *model = dynamic_cast(this->model()); + KIS_SAFE_ASSERT_RECOVER_RETURN(model); + model->setAssociatedRootShapeManager(shapeManager); + } + + KoShapeManager* associatedRootShapeManager() const + { + SimpleShapeContainerModel *model = dynamic_cast(this->model()); + KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(model, 0); + return model->associatedRootShapeManager(); + } + }; class KRITAFLAKE_EXPORT MockGroup : public KoShapeGroup { void paintComponent(QPainter &painter, KoShapePaintingContext &) const override { Q_UNUSED(painter); + paintedCount++; } +public: + bool contains(KoShape *shape) const { + return shapes().contains(shape); + } + + mutable int paintedCount; }; class KoToolProxy; class KRITAFLAKE_EXPORT MockShapeController : public KoShapeControllerBase { public: - void addShapes(const QList shapes) override { - Q_FOREACH (KoShape *shape, shapes) { - m_shapes.insert(shape); - if (m_shapeManager) { - m_shapeManager->addShape(shape); - } - } - } - void removeShape(KoShape* shape) override { - m_shapes.remove(shape); - if (m_shapeManager) { - m_shapeManager->remove(shape); - } - } - bool contains(KoShape* shape) { - return m_shapes.contains(shape); - } - - void setShapeManager(KoShapeManager *shapeManager) { - m_shapeManager = shapeManager; - } - QRectF documentRectInPixels() const override { return QRectF(0,0,100,100); } qreal pixelsPerInch() const override { return 72.0; } - -private: - QSet m_shapes; - KoShapeManager *m_shapeManager = 0; }; class KRITAFLAKE_EXPORT MockCanvas : public KoCanvasBase { Q_OBJECT public: MockCanvas(KoShapeControllerBase *aKoShapeControllerBase =0)//made for TestSnapStrategy.cpp : KoCanvasBase(aKoShapeControllerBase), m_shapeManager(new KoShapeManager(this)), m_selectedShapesProxy(new KoSelectedShapesProxySimple(m_shapeManager.data())) { - if (MockShapeController *controller = dynamic_cast(aKoShapeControllerBase)) { - controller->setShapeManager(m_shapeManager.data()); - } } ~MockCanvas() override {} void setHorz(qreal pHorz){ m_horz = pHorz; } void setVert(qreal pVert){ m_vert = pVert; } void gridSize(QPointF *offset, QSizeF *spacing) const override { Q_UNUSED(offset); spacing->setWidth(m_horz); spacing->setHeight(m_vert); } bool snapToGrid() const override { return true; } void addCommand(KUndo2Command*) override { } KoShapeManager *shapeManager() const override { return m_shapeManager.data(); } KoSelectedShapesProxy *selectedShapesProxy() const override { return m_selectedShapesProxy.data(); } void updateCanvas(const QRectF&) override {} KoToolProxy * toolProxy() const override { return 0; } KoViewConverter *viewConverter() const override { return 0; } QWidget* canvasWidget() override { return 0; } const QWidget* canvasWidget() const override { return 0; } KoUnit unit() const override { return KoUnit(KoUnit::Millimeter); } void updateInputMethodInfo() override {} void setCursor(const QCursor &) override {} private: QScopedPointer m_shapeManager; QScopedPointer m_selectedShapesProxy; qreal m_horz; qreal m_vert; }; class KRITAFLAKE_EXPORT MockContainerModel : public KoShapeContainerModel { public: MockContainerModel() { resetCounts(); } /// reimplemented void add(KoShape *child) override { m_children.append(child); // note that we explicitly do not check for duplicates here! } /// reimplemented void remove(KoShape *child) override { m_children.removeAll(child); } /// reimplemented void setClipped(const KoShape *, bool) override { } // ignored /// reimplemented bool isClipped(const KoShape *) const override { return false; }// ignored /// reimplemented int count() const override { return m_children.count(); } /// reimplemented QList shapes() const override { return m_children; } /// reimplemented void containerChanged(KoShapeContainer *, KoShape::ChangeType) override { m_containerChangedCalled++; } /// reimplemented void proposeMove(KoShape *, QPointF &) override { m_proposeMoveCalled++; } /// reimplemented void childChanged(KoShape *, KoShape::ChangeType) override { m_childChangedCalled++; } void setInheritsTransform(const KoShape *, bool) override { } bool inheritsTransform(const KoShape *) const override { return false; } int containerChangedCalled() const { return m_containerChangedCalled; } int childChangedCalled() const { return m_childChangedCalled; } int proposeMoveCalled() const { return m_proposeMoveCalled; } void resetCounts() { m_containerChangedCalled = 0; m_childChangedCalled = 0; m_proposeMoveCalled = 0; } private: QList m_children; int m_containerChangedCalled, m_childChangedCalled, m_proposeMoveCalled; }; #endif diff --git a/libs/flake/tests/TestPointMergeCommand.cpp b/libs/flake/tests/TestPointMergeCommand.cpp index 5c6cf20cbe..54d60c9c01 100644 --- a/libs/flake/tests/TestPointMergeCommand.cpp +++ b/libs/flake/tests/TestPointMergeCommand.cpp @@ -1,578 +1,571 @@ /* This file is part of the KDE project * Copyright (C) 2009 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 "TestPointMergeCommand.h" #include "KoPathPointMergeCommand.h" #include "KoPathShape.h" #include "KoPathPoint.h" #include "KoPathPointData.h" #include #include #include #include void TestPointMergeCommand::closeSingleLinePath() { KoPathShape path1; path1.moveTo(QPointF(40, 0)); path1.lineTo(QPointF(60, 0)); path1.lineTo(QPointF(60, 30)); path1.lineTo(QPointF(0, 30)); path1.lineTo(QPointF(0, 0)); path1.lineTo(QPointF(20, 0)); KoPathPointIndex index1(0,0); KoPathPointIndex index2(0,5); KoPathPointData pd1(&path1, index1); KoPathPointData pd2(&path1, index2); KoPathPoint * p1 = path1.pointByIndex(index1); KoPathPoint * p2 = path1.pointByIndex(index2); QVERIFY(!path1.isClosedSubpath(0)); QCOMPARE(path1.subpathPointCount(0), 6); QCOMPARE(p1->point(), QPointF(40,0)); QCOMPARE(p2->point(), QPointF(20,0)); KoPathPointMergeCommand cmd1(pd1,pd2); cmd1.redo(); QVERIFY(path1.isClosedSubpath(0)); QCOMPARE(path1.subpathPointCount(0), 5); QCOMPARE(p2->point(), QPointF(20,0)); cmd1.undo(); QVERIFY(!path1.isClosedSubpath(0)); QCOMPARE(path1.subpathPointCount(0), 6); QCOMPARE(p1->point(), QPointF(40,0)); QCOMPARE(p2->point(), QPointF(20,0)); KoPathPointMergeCommand cmd2(pd2,pd1); cmd2.redo(); QVERIFY(path1.isClosedSubpath(0)); QCOMPARE(path1.subpathPointCount(0), 5); QCOMPARE(p2->point(), QPointF(20,0)); cmd2.undo(); QVERIFY(!path1.isClosedSubpath(0)); QCOMPARE(path1.subpathPointCount(0), 6); QCOMPARE(p1->point(), QPointF(40,0)); QCOMPARE(p2->point(), QPointF(20,0)); } void TestPointMergeCommand::closeSingleCurvePath() { KoPathShape path1; path1.moveTo(QPointF(40, 0)); path1.curveTo(QPointF(60, 0), QPointF(60,0), QPointF(60,60)); path1.lineTo(QPointF(0, 60)); path1.curveTo(QPointF(0, 0), QPointF(0,0), QPointF(20,0)); KoPathPointIndex index1(0,0); KoPathPointIndex index2(0,3); KoPathPointData pd1(&path1, index1); KoPathPointData pd2(&path1, index2); KoPathPoint * p1 = path1.pointByIndex(index1); KoPathPoint * p2 = path1.pointByIndex(index2); QVERIFY(!path1.isClosedSubpath(0)); QCOMPARE(path1.subpathPointCount(0), 4); QCOMPARE(p1->point(), QPointF(40,0)); QVERIFY(!p1->activeControlPoint1()); QCOMPARE(p2->point(), QPointF(20,0)); QVERIFY(!p2->activeControlPoint2()); KoPathPointMergeCommand cmd1(pd1,pd2); cmd1.redo(); QVERIFY(path1.isClosedSubpath(0)); QCOMPARE(path1.subpathPointCount(0), 3); QCOMPARE(p2->point(), QPointF(20,0)); QVERIFY(p2->activeControlPoint1()); QVERIFY(!p2->activeControlPoint2()); cmd1.undo(); QVERIFY(!path1.isClosedSubpath(0)); QCOMPARE(path1.subpathPointCount(0), 4); QCOMPARE(p1->point(), QPointF(40,0)); QVERIFY(!p1->activeControlPoint1()); QCOMPARE(p2->point(), QPointF(20,0)); QVERIFY(!p2->activeControlPoint2()); KoPathPointMergeCommand cmd2(pd2,pd1); cmd2.redo(); QVERIFY(path1.isClosedSubpath(0)); QCOMPARE(path1.subpathPointCount(0), 3); QCOMPARE(p2->point(), QPointF(20,0)); QVERIFY(p2->activeControlPoint1()); QVERIFY(!p2->activeControlPoint2()); cmd2.undo(); QVERIFY(!path1.isClosedSubpath(0)); QCOMPARE(path1.subpathPointCount(0), 4); QCOMPARE(p1->point(), QPointF(40,0)); QVERIFY(!p1->activeControlPoint1()); QCOMPARE(p2->point(), QPointF(20,0)); QVERIFY(!p2->activeControlPoint2()); } void TestPointMergeCommand::connectLineSubpaths() { KoPathShape path1; path1.moveTo(QPointF(0,0)); path1.lineTo(QPointF(10,0)); path1.moveTo(QPointF(20,0)); path1.lineTo(QPointF(30,0)); KoPathPointIndex index1(0,1); KoPathPointIndex index2(1,0); KoPathPointData pd1(&path1, index1); KoPathPointData pd2(&path1, index2); QCOMPARE(path1.subpathCount(), 2); QCOMPARE(path1.pointByIndex(index1)->point(), QPointF(10,0)); QCOMPARE(path1.pointByIndex(index2)->point(), QPointF(20,0)); KoPathPointMergeCommand cmd1(pd1, pd2); cmd1.redo(); QCOMPARE(path1.subpathCount(), 1); QCOMPARE(path1.pointByIndex(index1)->point(), QPointF(15,0)); cmd1.undo(); QCOMPARE(path1.subpathCount(), 2); QCOMPARE(path1.pointByIndex(index1)->point(), QPointF(10,0)); QCOMPARE(path1.pointByIndex(index2)->point(), QPointF(20,0)); KoPathPointMergeCommand cmd2(pd2, pd1); cmd2.redo(); QCOMPARE(path1.subpathCount(), 1); QCOMPARE(path1.pointByIndex(index1)->point(), QPointF(15,0)); cmd2.undo(); QCOMPARE(path1.subpathCount(), 2); QCOMPARE(path1.pointByIndex(index1)->point(), QPointF(10,0)); QCOMPARE(path1.pointByIndex(index2)->point(), QPointF(20,0)); } void TestPointMergeCommand::connectCurveSubpaths() { KoPathShape path1; path1.moveTo(QPointF(0,0)); path1.curveTo(QPointF(20,0),QPointF(0,20),QPointF(20,20)); path1.moveTo(QPointF(50,0)); path1.curveTo(QPointF(30,0), QPointF(50,20), QPointF(30,20)); KoPathPointIndex index1(0,1); KoPathPointIndex index2(1,1); KoPathPointData pd1(&path1, index1); KoPathPointData pd2(&path1, index2); QCOMPARE(path1.subpathCount(), 2); QCOMPARE(path1.pointByIndex(index1)->point(), QPointF(20,20)); QCOMPARE(path1.pointByIndex(index1)->controlPoint1(), QPointF(0,20)); QCOMPARE(path1.pointByIndex(index2)->point(), QPointF(30,20)); QCOMPARE(path1.pointByIndex(index2)->controlPoint1(), QPointF(50,20)); QVERIFY(path1.pointByIndex(index1)->activeControlPoint1()); QVERIFY(!path1.pointByIndex(index1)->activeControlPoint2()); KoPathPointMergeCommand cmd1(pd1, pd2); cmd1.redo(); QCOMPARE(path1.subpathCount(), 1); QCOMPARE(path1.pointByIndex(index1)->point(), QPointF(25,20)); QCOMPARE(path1.pointByIndex(index1)->controlPoint1(), QPointF(5,20)); QCOMPARE(path1.pointByIndex(index1)->controlPoint2(), QPointF(45,20)); QVERIFY(path1.pointByIndex(index1)->activeControlPoint1()); QVERIFY(path1.pointByIndex(index1)->activeControlPoint2()); cmd1.undo(); QCOMPARE(path1.subpathCount(), 2); QCOMPARE(path1.pointByIndex(index1)->point(), QPointF(20,20)); QCOMPARE(path1.pointByIndex(index1)->controlPoint1(), QPointF(0,20)); QCOMPARE(path1.pointByIndex(index2)->point(), QPointF(30,20)); QCOMPARE(path1.pointByIndex(index2)->controlPoint1(), QPointF(50,20)); QVERIFY(path1.pointByIndex(index1)->activeControlPoint1()); QVERIFY(!path1.pointByIndex(index1)->activeControlPoint2()); KoPathPointMergeCommand cmd2(pd2, pd1); cmd2.redo(); QCOMPARE(path1.subpathCount(), 1); QCOMPARE(path1.pointByIndex(index1)->point(), QPointF(25,20)); QCOMPARE(path1.pointByIndex(index1)->controlPoint1(), QPointF(5,20)); QCOMPARE(path1.pointByIndex(index1)->controlPoint2(), QPointF(45,20)); QVERIFY(path1.pointByIndex(index1)->activeControlPoint1()); QVERIFY(path1.pointByIndex(index1)->activeControlPoint2()); cmd2.undo(); QCOMPARE(path1.subpathCount(), 2); QCOMPARE(path1.pointByIndex(index1)->point(), QPointF(20,20)); QCOMPARE(path1.pointByIndex(index1)->controlPoint1(), QPointF(0,20)); QCOMPARE(path1.pointByIndex(index2)->point(), QPointF(30,20)); QCOMPARE(path1.pointByIndex(index2)->controlPoint1(), QPointF(50,20)); QVERIFY(path1.pointByIndex(index1)->activeControlPoint1()); QVERIFY(!path1.pointByIndex(index1)->activeControlPoint2()); } #include #include #include "kis_debug.h" void TestPointMergeCommand::testCombineShapes() { MockShapeController mockController; MockCanvas canvas(&mockController); + QScopedPointer rootContainer(new MockContainer()); + rootContainer->setAssociatedRootShapeManager(canvas.shapeManager()); QList shapesToCombine; for (int i = 0; i < 3; i++) { const QPointF step(15,15); const QRectF rect = QRectF(5,5,10,10).translated(step * i); QPainterPath p; p.addRect(rect); KoPathShape *shape = KoPathShape::createShapeFromPainterPath(p); QCOMPARE(shape->absoluteOutlineRect(), rect); shapesToCombine << shape; - mockController.addShape(shape); + rootContainer->addShape(shape); } KoPathCombineCommand cmd(&mockController, shapesToCombine); cmd.redo(); - QCOMPARE(canvas.shapeManager()->shapes().size(), 1); + QCOMPARE(rootContainer->shapes().size(), 1); - KoPathShape *combinedShape = dynamic_cast(canvas.shapeManager()->shapes().first()); + KoPathShape *combinedShape = dynamic_cast(rootContainer->shapes().first()); QCOMPARE(combinedShape, cmd.combinedPath()); QCOMPARE(combinedShape->subpathCount(), 3); QCOMPARE(combinedShape->absoluteOutlineRect(), QRectF(5,5,40,40)); QList tstPoints; QList expPoints; tstPoints << KoPathPointData(shapesToCombine[0], KoPathPointIndex(0,1)); expPoints << KoPathPointData(combinedShape, KoPathPointIndex(0,1)); tstPoints << KoPathPointData(shapesToCombine[1], KoPathPointIndex(0,2)); expPoints << KoPathPointData(combinedShape, KoPathPointIndex(1,2)); tstPoints << KoPathPointData(shapesToCombine[2], KoPathPointIndex(0,3)); expPoints << KoPathPointData(combinedShape, KoPathPointIndex(2,3)); for (int i = 0; i < tstPoints.size(); i++) { KoPathPointData convertedPoint = cmd.originalToCombined(tstPoints[i]); QCOMPARE(convertedPoint, expPoints[i]); } - Q_FOREACH (KoShape *shape, canvas.shapeManager()->shapes()) { - mockController.removeShape(shape); - shape->setParent(0); - delete shape; - } - + rootContainer.reset(); // 'shapesToCombine' will be deleted by KoPathCombineCommand } #include #include #include #include "kis_algebra_2d.h" inline QPointF fetchPoint(KoPathShape *shape, int subpath, int pointIndex) { return shape->absoluteTransformation().map( shape->pointByIndex(KoPathPointIndex(subpath, pointIndex))->point()); } void dumpShape(KoPathShape *shape, const QString &fileName) { QImage tmp(50,50, QImage::Format_ARGB32); tmp.fill(0); QPainter p(&tmp); p.drawPath(shape->absoluteTransformation().map(shape->outline())); tmp.save(fileName); } template void testMultipathMergeShapesImpl(const int srcPointIndex1, const int srcPointIndex2, const QList &expectedResultPoints, bool singleShape = false) { MockShapeController mockController; MockCanvas canvas(&mockController); + QScopedPointer rootContainer(new MockContainer()); + rootContainer->setAssociatedRootShapeManager(canvas.shapeManager()); QList shapes; for (int i = 0; i < 3; i++) { const QPointF step(15,15); const QRectF rect = QRectF(5,5,10,10).translated(step * i); QPainterPath p; p.moveTo(rect.topLeft()); p.lineTo(rect.bottomRight()); p.lineTo(rect.topRight()); KoPathShape *shape = KoPathShape::createShapeFromPainterPath(p); QCOMPARE(shape->absoluteOutlineRect(), rect); shapes << shape; - mockController.addShape(shape); + rootContainer->addShape(shape); } - { KoPathPointData pd1(shapes[0], KoPathPointIndex(0,srcPointIndex1)); KoPathPointData pd2(shapes[singleShape ? 0 : 1], KoPathPointIndex(0,srcPointIndex2)); MergeCommand cmd(pd1, pd2, &mockController, canvas.shapeManager()->selection()); cmd.redo(); const int expectedShapesCount = singleShape ? 3 : 2; - QCOMPARE(canvas.shapeManager()->shapes().size(), expectedShapesCount); + QCOMPARE(rootContainer->shapes().size(), expectedShapesCount); KoPathShape *combinedShape = 0; if (!singleShape) { - combinedShape = dynamic_cast(canvas.shapeManager()->shapes()[1]); + combinedShape = dynamic_cast(rootContainer->shapes()[1]); QCOMPARE(combinedShape, cmd.testingCombinedPath()); } else { - combinedShape = dynamic_cast(canvas.shapeManager()->shapes()[0]); + combinedShape = dynamic_cast(rootContainer->shapes()[0]); QCOMPARE(combinedShape, shapes[0]); } QCOMPARE(combinedShape->subpathCount(), 1); QRectF expectedOutlineRect; KisAlgebra2D::accumulateBounds(expectedResultPoints, &expectedOutlineRect); QVERIFY(KisAlgebra2D::fuzzyCompareRects(combinedShape->absoluteOutlineRect(), expectedOutlineRect, 0.01)); if (singleShape) { QCOMPARE(combinedShape->isClosedSubpath(0), true); } QCOMPARE(combinedShape->subpathPointCount(0), expectedResultPoints.size()); for (int i = 0; i < expectedResultPoints.size(); i++) { if (fetchPoint(combinedShape, 0, i) != expectedResultPoints[i]) { qDebug() << ppVar(i); qDebug() << ppVar(fetchPoint(combinedShape, 0, i)); qDebug() << ppVar(expectedResultPoints[i]); QFAIL("Resulting shape points are different!"); } } QList shapes = canvas.shapeManager()->selection()->selectedEditableShapes(); QCOMPARE(shapes.size(), 1); QCOMPARE(shapes.first(), combinedShape); //dumpShape(combinedShape, "tmp_0_seq.png"); cmd.undo(); - QCOMPARE(canvas.shapeManager()->shapes().size(), 3); - } - - Q_FOREACH (KoShape *shape, canvas.shapeManager()->shapes()) { - mockController.removeShape(shape); - shape->setParent(0); - delete shape; + QCOMPARE(rootContainer->shapes().size(), 3); } + rootContainer.reset(); // combined shapes will be deleted by the corresponding commands } void TestPointMergeCommand::testMultipathMergeShapesBothSequential() { // both sequential testMultipathMergeShapesImpl(2, 0, { QPointF(5,5), QPointF(15,15), QPointF(17.5,12.5), // merged by melding the points! QPointF(30,30), QPointF(30,20) }); } void TestPointMergeCommand::testMultipathMergeShapesFirstReversed() { // first reversed testMultipathMergeShapesImpl(0, 0, { QPointF(15,5), QPointF(15,15), QPointF(12.5,12.5), // merged by melding the points! QPointF(30,30), QPointF(30,20) }); } void TestPointMergeCommand::testMultipathMergeShapesSecondReversed() { // second reversed testMultipathMergeShapesImpl(2, 2, { QPointF(5,5), QPointF(15,15), QPointF(22.5,12.5), // merged by melding the points! QPointF(30,30), QPointF(20,20) }); } void TestPointMergeCommand::testMultipathMergeShapesBothReversed() { // both reversed testMultipathMergeShapesImpl(0, 2, { QPointF(15,5), QPointF(15,15), QPointF(17.5,12.5), // merged by melding the points! QPointF(30,30), QPointF(20,20) }); } void TestPointMergeCommand::testMultipathMergeShapesSingleShapeEndToStart() { // close end->start testMultipathMergeShapesImpl(2, 0, { QPointF(10,5), QPointF(15,15) }, true); } void TestPointMergeCommand::testMultipathMergeShapesSingleShapeStartToEnd() { // close start->end testMultipathMergeShapesImpl(0, 2, { QPointF(10,5), QPointF(15,15) }, true); } void TestPointMergeCommand::testMultipathJoinShapesBothSequential() { // both sequential testMultipathMergeShapesImpl (2, 0, { QPointF(5,5), QPointF(15,15), QPointF(15,5), QPointF(20,20), QPointF(30,30), QPointF(30,20) }); } void TestPointMergeCommand::testMultipathJoinShapesFirstReversed() { // first reversed testMultipathMergeShapesImpl (0, 0, { QPointF(15,5), QPointF(15,15), QPointF(5,5), QPointF(20,20), QPointF(30,30), QPointF(30,20) }); } void TestPointMergeCommand::testMultipathJoinShapesSecondReversed() { // second reversed testMultipathMergeShapesImpl (2, 2, { QPointF(5,5), QPointF(15,15), QPointF(15,5), QPointF(30,20), QPointF(30,30), QPointF(20,20) }); } void TestPointMergeCommand::testMultipathJoinShapesBothReversed() { // both reversed testMultipathMergeShapesImpl (0, 2, { QPointF(15,5), QPointF(15,15), QPointF(5,5), QPointF(30,20), QPointF(30,30), QPointF(20,20) }); } void TestPointMergeCommand::testMultipathJoinShapesSingleShapeEndToStart() { // close end->start testMultipathMergeShapesImpl (2, 0, { QPointF(5,5), QPointF(15,15), QPointF(15,5) }, true); } void TestPointMergeCommand::testMultipathJoinShapesSingleShapeStartToEnd() { // close start->end testMultipathMergeShapesImpl (0, 2, { QPointF(5,5), QPointF(15,15), QPointF(15,5) }, true); } KISTEST_MAIN(TestPointMergeCommand) diff --git a/libs/flake/tests/TestPointRemoveCommand.cpp b/libs/flake/tests/TestPointRemoveCommand.cpp index 0c3adcf0cf..207dfd3f0e 100644 --- a/libs/flake/tests/TestPointRemoveCommand.cpp +++ b/libs/flake/tests/TestPointRemoveCommand.cpp @@ -1,303 +1,304 @@ /* This file is part of the KDE project Copyright (C) 2007 Thorsten Zachmann This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "TestPointRemoveCommand.h" #include #include "KoPathShape.h" #include "KoPathPointRemoveCommand.h" #include "KoShapeController.h" #include #include #include void TestPointRemoveCommand::redoUndoPointRemove() { KoPathShape path1; path1.moveTo(QPointF(0, 0)); path1.lineTo(QPointF(0, 100)); KoPathPoint *point1 = path1.curveTo(QPointF(0, 50), QPointF(100, 50), QPointF(100, 100)); KoPathPoint *point2 = path1.lineTo(QPointF(200, 100)); path1.curveTo(QPointF(200, 50), QPointF(300, 50), QPointF(300, 100)); QPainterPath orig1(QPointF(0, 0)); orig1.lineTo(0, 100); orig1.cubicTo(0, 50, 100, 50, 100, 100); orig1.lineTo(200, 100); orig1.cubicTo(200, 50, 300, 50, 300, 100); QVERIFY(orig1 == path1.outline()); KoPathShape path2; path2.moveTo(QPointF(0, 0)); KoPathPoint *point3 = path2.curveTo(QPointF(50, 0), QPointF(100, 50), QPointF(100, 100)); path2.curveTo(QPointF(50, 100), QPointF(0, 50), QPointF(0, 0)); path2.closeMerge(); QList pd; pd.append(KoPathPointData(&path1, path1.pathPointIndex(point1))); pd.append(KoPathPointData(&path1, path1.pathPointIndex(point2))); pd.append(KoPathPointData(&path2, path2.pathPointIndex(point3))); QPainterPath ppath1Org = path1.outline(); QPainterPath ppath2Org = path2.outline(); MockShapeController mockController; KoShapeController shapeController(0, &mockController); KUndo2Command *cmd = KoPathPointRemoveCommand::createCommand(pd, &shapeController); cmd->redo(); QPainterPath ppath1(QPointF(0, 0)); ppath1.lineTo(0, 100); ppath1.cubicTo(0, 50, 300, 50, 300, 100); QPainterPath ppath2(QPointF(0, 0)); ppath2.cubicTo(50, 0, 0, 50, 0, 0); ppath2.closeSubpath(); QVERIFY(ppath1 == path1.outline()); QVERIFY(ppath2 == path2.outline()); cmd->undo(); QVERIFY(ppath1Org == path1.outline()); QVERIFY(ppath2Org == path2.outline()); delete cmd; } void TestPointRemoveCommand::redoUndoSubpathRemove() { KoPathShape path1; KoPathPoint *point11 = path1.moveTo(QPointF(0, 0)); KoPathPoint *point12 = path1.lineTo(QPointF(0, 100)); KoPathPoint *point13 = path1.curveTo(QPointF(0, 50), QPointF(100, 50), QPointF(100, 100)); KoPathPoint *point14 = path1.lineTo(QPointF(200, 100)); KoPathPoint *point15 = path1.curveTo(QPointF(200, 50), QPointF(300, 50), QPointF(300, 100)); KoPathPoint *point21 = path1.moveTo(QPointF(0, 0)); KoPathPoint *point22 = path1.curveTo(QPointF(50, 0), QPointF(100, 50), QPointF(100, 100)); path1.curveTo(QPointF(50, 100), QPointF(0, 50), QPointF(0, 0)); path1.closeMerge(); path1.moveTo(QPointF(100, 0)); path1.lineTo(QPointF(100, 100)); QList pd; pd.append(KoPathPointData(&path1, path1.pathPointIndex(point11))); pd.append(KoPathPointData(&path1, path1.pathPointIndex(point12))); pd.append(KoPathPointData(&path1, path1.pathPointIndex(point13))); pd.append(KoPathPointData(&path1, path1.pathPointIndex(point14))); pd.append(KoPathPointData(&path1, path1.pathPointIndex(point15))); QPainterPath ppath1Org = path1.outline(); MockShapeController mockController; KoShapeController shapeController(0, &mockController); KUndo2Command *cmd1 = KoPathPointRemoveCommand::createCommand(pd, &shapeController); cmd1->redo(); QPainterPath ppath1(QPointF(0, 0)); ppath1.cubicTo(50, 0, 100, 50, 100, 100); ppath1.cubicTo(50, 100, 0, 50, 0, 0); ppath1.closeSubpath(); ppath1.moveTo(100, 0); ppath1.lineTo(100, 100); QPainterPath ppath1mod = path1.outline(); QVERIFY(ppath1 == ppath1mod); QList pd2; pd2.append(KoPathPointData(&path1, path1.pathPointIndex(point21))); pd2.append(KoPathPointData(&path1, path1.pathPointIndex(point22))); KUndo2Command *cmd2 = KoPathPointRemoveCommand::createCommand(pd2, &shapeController); cmd2->redo(); QPainterPath ppath2(QPointF(0, 0)); ppath2.lineTo(0, 100); QVERIFY(ppath2 == path1.outline()); cmd2->undo(); QVERIFY(ppath1mod == path1.outline()); cmd1->undo(); QVERIFY(ppath1Org == path1.outline()); delete cmd2; delete cmd1; } void TestPointRemoveCommand::redoUndoShapeRemove() { KoPathShape *path1 = new KoPathShape(); KoPathPoint *point11 = path1->moveTo(QPointF(0, 0)); KoPathPoint *point12 = path1->lineTo(QPointF(0, 100)); KoPathPoint *point13 = path1->curveTo(QPointF(0, 50), QPointF(100, 50), QPointF(100, 100)); KoPathPoint *point14 = path1->lineTo(QPointF(200, 100)); KoPathPoint *point15 = path1->curveTo(QPointF(200, 50), QPointF(300, 50), QPointF(300, 100)); KoPathShape *path2 = new KoPathShape(); KoPathPoint *point21 = path2->moveTo(QPointF(0, 0)); KoPathPoint *point22 = path2->curveTo(QPointF(50, 0), QPointF(100, 50), QPointF(100, 100)); path2->curveTo(QPointF(50, 100), QPointF(0, 50), QPointF(0, 0)); path2->closeMerge(); QList pd; pd.append(KoPathPointData(path1, path1->pathPointIndex(point12))); pd.append(KoPathPointData(path1, path1->pathPointIndex(point11))); pd.append(KoPathPointData(path1, path1->pathPointIndex(point13))); pd.append(KoPathPointData(path1, path1->pathPointIndex(point15))); pd.append(KoPathPointData(path1, path1->pathPointIndex(point14))); pd.append(KoPathPointData(path2, path2->pathPointIndex(point22))); pd.append(KoPathPointData(path2, path2->pathPointIndex(point21))); QPainterPath ppath1Org = path1->outline(); QPainterPath ppath2Org = path2->outline(); MockShapeController mockController; - mockController.addShape(path1); - mockController.addShape(path2); KoShapeController shapeController(0, &mockController); + QScopedPointer rootContainer(new MockContainer()); + rootContainer->addShape(path1); + rootContainer->addShape(path2); KUndo2Command *cmd = KoPathPointRemoveCommand::createCommand(pd, &shapeController); cmd->redo(); - QVERIFY(!mockController.contains(path1)); - QVERIFY(!mockController.contains(path2)); + QVERIFY(!rootContainer->contains(path1)); + QVERIFY(!rootContainer->contains(path2)); cmd->undo(); - QVERIFY(mockController.contains(path1)); - QVERIFY(mockController.contains(path2)); + QVERIFY(rootContainer->contains(path1)); + QVERIFY(rootContainer->contains(path2)); QVERIFY(ppath1Org == path1->outline()); QVERIFY(ppath2Org == path2->outline()); delete cmd; - delete path1; - delete path2; + rootContainer.reset(); } void TestPointRemoveCommand::redoUndo() { KoPathShape *path1 = new KoPathShape(); KoPathPoint *point11 = path1->moveTo(QPointF(0, 0)); KoPathPoint *point12 = path1->lineTo(QPointF(0, 100)); KoPathPoint *point13 = path1->curveTo(QPointF(0, 50), QPointF(100, 50), QPointF(100, 100)); KoPathPoint *point14 = path1->lineTo(QPointF(200, 100)); KoPathPoint *point15 = path1->curveTo(QPointF(200, 50), QPointF(300, 50), QPointF(300, 100)); KoPathPoint *point16 = path1->moveTo(QPointF(100, 0)); KoPathPoint *point17 = path1->curveTo(QPointF(150, 0), QPointF(200, 50), QPointF(200, 100)); path1->curveTo(QPointF(150, 100), QPointF(100, 50), QPointF(100, 0)); path1->closeMerge(); KoPathPoint *point18 = path1->moveTo(QPointF(200, 0)); KoPathPoint *point19 = path1->lineTo(QPointF(200, 100)); KoPathShape *path2 = new KoPathShape(); KoPathPoint *point21 = path2->moveTo(QPointF(0, 0)); KoPathPoint *point22 = path2->curveTo(QPointF(50, 0), QPointF(100, 50), QPointF(100, 100)); path2->curveTo(QPointF(50, 100), QPointF(0, 50), QPointF(0, 0)); path2->closeMerge(); KoPathShape *path3 = new KoPathShape(); KoPathPoint *point31 = path3->moveTo(QPointF(0, 0)); KoPathPoint *point32 = path3->lineTo(QPointF(100, 100)); KoPathPoint *point33 = path3->lineTo(QPointF(200, 150)); MockShapeController mockController; - mockController.addShape(path1); - mockController.addShape(path2); - mockController.addShape(path3); + QScopedPointer rootContainer(new MockContainer()); + rootContainer->addShape(path1); + rootContainer->addShape(path2); + rootContainer->addShape(path3); QList pd; pd.append(KoPathPointData(path2, path2->pathPointIndex(point21))); pd.append(KoPathPointData(path1, path1->pathPointIndex(point13))); pd.append(KoPathPointData(path1, path1->pathPointIndex(point11))); pd.append(KoPathPointData(path3, path3->pathPointIndex(point31))); pd.append(KoPathPointData(path1, path1->pathPointIndex(point12))); pd.append(KoPathPointData(path1, path1->pathPointIndex(point15))); pd.append(KoPathPointData(path2, path2->pathPointIndex(point22))); pd.append(KoPathPointData(path1, path1->pathPointIndex(point14))); KoShapeController shapeController(0, &mockController); QPainterPath ppath1Org = path1->outline(); QPainterPath ppath2Org = path2->outline(); QPainterPath ppath3Org = path3->outline(); KUndo2Command *cmd1 = KoPathPointRemoveCommand::createCommand(pd, &shapeController); cmd1->redo(); - QVERIFY(mockController.contains(path1)); - QVERIFY(!mockController.contains(path2)); - QVERIFY(mockController.contains(path3)); + QVERIFY(rootContainer->contains(path1)); + QVERIFY(!rootContainer->contains(path2)); + QVERIFY(rootContainer->contains(path3)); QPainterPath ppath1(QPointF(0, 0)); ppath1.cubicTo(50, 0, 100, 50, 100, 100); ppath1.cubicTo(50, 100, 0, 50, 0, 0); ppath1.closeSubpath(); ppath1.moveTo(100, 0); ppath1.lineTo(100, 100); QPainterPath ppath1mod = path1->outline(); QVERIFY(ppath1 == ppath1mod); QPainterPath ppath3(QPointF(0, 0)); ppath3.lineTo(100, 50); QPainterPath ppath3mod = path3->outline(); QVERIFY(ppath3 == ppath3mod); QList pd2; pd2.append(KoPathPointData(path1, path1->pathPointIndex(point16))); pd2.append(KoPathPointData(path1, path1->pathPointIndex(point17))); pd2.append(KoPathPointData(path1, path1->pathPointIndex(point18))); pd2.append(KoPathPointData(path1, path1->pathPointIndex(point19))); pd2.append(KoPathPointData(path3, path3->pathPointIndex(point32))); pd2.append(KoPathPointData(path3, path3->pathPointIndex(point33))); KUndo2Command *cmd2 = KoPathPointRemoveCommand::createCommand(pd2, &shapeController); cmd2->redo(); - QVERIFY(!mockController.contains(path1)); - QVERIFY(!mockController.contains(path2)); - QVERIFY(!mockController.contains(path3)); + QVERIFY(!rootContainer->contains(path1)); + QVERIFY(!rootContainer->contains(path2)); + QVERIFY(!rootContainer->contains(path3)); cmd2->undo(); - QVERIFY(mockController.contains(path1)); - QVERIFY(!mockController.contains(path2)); - QVERIFY(mockController.contains(path3)); + QVERIFY(rootContainer->contains(path1)); + QVERIFY(!rootContainer->contains(path2)); + QVERIFY(rootContainer->contains(path3)); QVERIFY(ppath1 == ppath1mod); QVERIFY(ppath3 == ppath3mod); cmd1->undo(); QVERIFY(ppath1Org == path1->outline()); QVERIFY(ppath2Org == path2->outline()); QVERIFY(ppath3Org == path3->outline()); cmd1->redo(); cmd2->redo(); delete cmd2; delete cmd1; } KISTEST_MAIN(TestPointRemoveCommand) diff --git a/libs/flake/tests/TestShapePainting.cpp b/libs/flake/tests/TestShapePainting.cpp index e5bfb25157..284cc2a1d4 100644 --- a/libs/flake/tests/TestShapePainting.cpp +++ b/libs/flake/tests/TestShapePainting.cpp @@ -1,317 +1,315 @@ /* * This file is part of Calligra tests * * Copyright (C) 2006-2010 Thomas Zander * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public 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 "TestShapePainting.h" #include #include "KoShapeContainer.h" #include "KoShapeManager.h" #include "KoShapePaintingContext.h" #include #include #include void TestShapePainting::testPaintShape() { MockShape *shape1 = new MockShape(); MockShape *shape2 = new MockShape(); QScopedPointer container(new MockContainer()); container->addShape(shape1); container->addShape(shape2); QCOMPARE(shape1->parent(), container.data()); QCOMPARE(shape2->parent(), container.data()); container->setClipped(shape1, false); container->setClipped(shape2, false); QCOMPARE(container->isClipped(shape1), false); QCOMPARE(container->isClipped(shape2), false); MockCanvas canvas; KoShapeManager manager(&canvas); manager.addShape(container.data()); QCOMPARE(manager.shapes().count(), 3); QImage image(100, 100, QImage::Format_Mono); QPainter painter(&image); manager.paint(painter, false); // with the shape not being clipped, the shapeManager will paint it for us. QCOMPARE(shape1->paintedCount, 1); QCOMPARE(shape2->paintedCount, 1); QCOMPARE(container->paintedCount, 1); // the container should thus not paint the shape shape1->paintedCount = 0; shape2->paintedCount = 0; container->paintedCount = 0; KoShapePaintingContext paintContext; container->paint(painter, paintContext); QCOMPARE(shape1->paintedCount, 0); QCOMPARE(shape2->paintedCount, 0); QCOMPARE(container->paintedCount, 1); container->setClipped(shape1, false); container->setClipped(shape2, true); QCOMPARE(container->isClipped(shape1), false); QCOMPARE(container->isClipped(shape2), true); shape1->paintedCount = 0; shape2->paintedCount = 0; container->paintedCount = 0; manager.paint(painter, false); // with this shape not being clipped, the shapeManager will paint the container and this shape QCOMPARE(shape1->paintedCount, 1); // with this shape being clipped, the container will paint it for us. QCOMPARE(shape2->paintedCount, 1); QCOMPARE(container->paintedCount, 1); } void TestShapePainting::testPaintHiddenShape() { QScopedPointer top(new MockContainer()); MockShape *shape = new MockShape(); MockContainer *fourth = new MockContainer(); MockContainer *thirth = new MockContainer(); MockContainer *second = new MockContainer(); top->addShape(second); second->addShape(thirth); thirth->addShape(fourth); fourth->addShape(shape); second->setVisible(false); MockCanvas canvas; KoShapeManager manager(&canvas); manager.addShape(top.data()); QCOMPARE(manager.shapes().count(), 5); QImage image(100, 100, QImage::Format_Mono); QPainter painter(&image); manager.paint(painter, false); QCOMPARE(top->paintedCount, 1); QCOMPARE(second->paintedCount, 0); QCOMPARE(thirth->paintedCount, 0); QCOMPARE(fourth->paintedCount, 0); QCOMPARE(shape->paintedCount, 0); } void TestShapePainting::testPaintOrder() { // the stacking order determines the painting order so things on top // get their paint called last. // Each shape has a zIndex and within the children a container has // it determines the stacking order. Its important to realize that // the zIndex is thus local to a container, if you have layer1 and layer2 // with both various child shapes the stacking order of the layer shapes // is most important, then within this the child shape index is used. class OrderedMockShape : public MockShape { public: OrderedMockShape(QList *list) : order(list) {} void paint(QPainter &painter, KoShapePaintingContext &paintcontext) const override { order->append(this); MockShape::paint(painter, paintcontext); } mutable QList *order; }; QList order; { QScopedPointer top(new MockContainer()); top->setZIndex(2); OrderedMockShape *shape1 = new OrderedMockShape(&order); shape1->setZIndex(5); OrderedMockShape *shape2 = new OrderedMockShape(&order); shape2->setZIndex(0); top->addShape(shape1); top->addShape(shape2); QScopedPointer bottom(new MockContainer()); bottom->setZIndex(1); OrderedMockShape *shape3 = new OrderedMockShape(&order); shape3->setZIndex(-1); OrderedMockShape *shape4 = new OrderedMockShape(&order); shape4->setZIndex(9); bottom->addShape(shape3); bottom->addShape(shape4); MockCanvas canvas; KoShapeManager manager(&canvas); manager.addShape(top.data()); manager.addShape(bottom.data()); QCOMPARE(manager.shapes().count(), 6); QImage image(100, 100, QImage::Format_Mono); QPainter painter(&image); manager.paint(painter, false); QCOMPARE(top->paintedCount, 1); QCOMPARE(bottom->paintedCount, 1); QCOMPARE(shape1->paintedCount, 1); QCOMPARE(shape2->paintedCount, 1); QCOMPARE(shape3->paintedCount, 1); QCOMPARE(shape4->paintedCount, 1); QCOMPARE(order.count(), 4); QVERIFY(order[0] == shape3); // lowest first QVERIFY(order[1] == shape4); QVERIFY(order[2] == shape2); QVERIFY(order[3] == shape1); // again, with clipping. order.clear(); painter.setClipRect(0, 0, 100, 100); manager.paint(painter, false); QCOMPARE(top->paintedCount, 2); QCOMPARE(bottom->paintedCount, 2); QCOMPARE(shape1->paintedCount, 2); QCOMPARE(shape2->paintedCount, 2); QCOMPARE(shape3->paintedCount, 2); QCOMPARE(shape4->paintedCount, 2); QCOMPARE(order.count(), 4); QVERIFY(order[0] == shape3); // lowest first QVERIFY(order[1] == shape4); QVERIFY(order[2] == shape2); QVERIFY(order[3] == shape1); } order.clear(); { QScopedPointer root(new MockContainer()); root->setZIndex(0); MockContainer *branch1 = new MockContainer(); branch1->setZIndex(1); OrderedMockShape *child1_1 = new OrderedMockShape(&order); child1_1->setZIndex(1); OrderedMockShape *child1_2 = new OrderedMockShape(&order); child1_2->setZIndex(2); branch1->addShape(child1_1); branch1->addShape(child1_2); MockContainer *branch2 = new MockContainer(); branch2->setZIndex(2); OrderedMockShape *child2_1 = new OrderedMockShape(&order); child2_1->setZIndex(1); OrderedMockShape *child2_2 = new OrderedMockShape(&order); child2_2->setZIndex(2); branch2->addShape(child2_1); branch2->addShape(child2_2); root->addShape(branch1); root->addShape(branch2); QList sortedShapes; sortedShapes.append(root.data()); sortedShapes.append(branch1); sortedShapes.append(branch2); sortedShapes.append(branch1->shapes()); sortedShapes.append(branch2->shapes()); std::sort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex); QCOMPARE(sortedShapes.count(), 7); QVERIFY(sortedShapes[0] == root.data()); QVERIFY(sortedShapes[1] == branch1); QVERIFY(sortedShapes[2] == child1_1); QVERIFY(sortedShapes[3] == child1_2); QVERIFY(sortedShapes[4] == branch2); QVERIFY(sortedShapes[5] == child2_1); QVERIFY(sortedShapes[6] == child2_2); } } #include #include #include #include #include "kis_debug.h" void TestShapePainting::testGroupUngroup() { - QScopedPointer shapesFakeLayer(new MockContainer); + MockShapeController controller; + MockCanvas canvas(&controller); + + KoShapeManager *manager = canvas.shapeManager(); + + QScopedPointer shapesFakeLayer(new MockContainer()); + shapesFakeLayer->setAssociatedRootShapeManager(manager); + MockShape *shape1(new MockShape()); MockShape *shape2(new MockShape()); shape1->setName("shape1"); shape2->setName("shape2"); shape1->setParent(shapesFakeLayer.data()); shape2->setParent(shapesFakeLayer.data()); QList groupedShapes = {shape1, shape2}; - - MockShapeController controller; - MockCanvas canvas(&controller); - KoShapeManager *manager = canvas.shapeManager(); - - controller.addShape(shape1); - controller.addShape(shape2); - QImage image(100, 100, QImage::Format_Mono); QPainter painter(&image); painter.setClipRect(image.rect()); for (int i = 0; i < 3; i++) { KoShapeGroup *group = new KoShapeGroup(); - group->setParent(shapesFakeLayer.data()); { group->setName("group"); KUndo2Command groupingCommand; - canvas.shapeController()->addShapeDirect(group, 0, &groupingCommand); + canvas.shapeController()->addShapeDirect(group, shapesFakeLayer.data(), &groupingCommand); new KoShapeGroupCommand(group, groupedShapes, true, &groupingCommand); groupingCommand.redo(); manager->paint(painter, false); QCOMPARE(shape1->paintedCount, 2 * i + 1); QCOMPARE(shape2->paintedCount, 2 * i + 1); QCOMPARE(manager->shapes().size(), 3); } { KUndo2Command ungroupingCommand; new KoShapeUngroupCommand(group, group->shapes(), QList(), &ungroupingCommand); canvas.shapeController()->removeShape(group, &ungroupingCommand); // NOTE: group will be deleted in ungroupingCommand's d-tor ungroupingCommand.redo(); manager->paint(painter, false); QCOMPARE(shape1->paintedCount, 2 * i + 2); QCOMPARE(shape2->paintedCount, 2 * i + 2); QCOMPARE(manager->shapes().size(), 2); } } } KISTEST_MAIN(TestShapePainting) diff --git a/libs/flake/text/KoSvgTextShape.cpp b/libs/flake/text/KoSvgTextShape.cpp index b04fe02717..cdda1199ac 100644 --- a/libs/flake/text/KoSvgTextShape.cpp +++ b/libs/flake/text/KoSvgTextShape.cpp @@ -1,641 +1,641 @@ /* * Copyright (c) 2017 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KoSvgTextShape.h" #include #include #include "KoSvgText.h" #include "KoSvgTextProperties.h" #include #include #include #include #include #include "kis_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class KoSvgTextShape::Private { public: // NOTE: the cache data is shared between all the instances of // the shape, though it will be reset locally if the // accessing thread changes std::vector> cachedLayouts; std::vector cachedLayoutsOffsets; QThread *cachedLayoutsWorkingThread = 0; void clearAssociatedOutlines(const KoShape *rootShape); }; KoSvgTextShape::KoSvgTextShape() : KoSvgTextChunkShape() , d(new Private) { setShapeId(KoSvgTextShape_SHAPEID); } KoSvgTextShape::KoSvgTextShape(const KoSvgTextShape &rhs) : KoSvgTextChunkShape(rhs) , d(new Private) { setShapeId(KoSvgTextShape_SHAPEID); // QTextLayout has no copy-ctor, so just relayout everything! relayout(); } KoSvgTextShape::~KoSvgTextShape() { } KoShape *KoSvgTextShape::cloneShape() const { return new KoSvgTextShape(*this); } void KoSvgTextShape::shapeChanged(ChangeType type, KoShape *shape) { KoSvgTextChunkShape::shapeChanged(type, shape); if (type == StrokeChanged || type == BackgroundChanged || type == ContentChanged) { relayout(); } } void KoSvgTextShape::paintComponent(QPainter &painter, KoShapePaintingContext &paintContext) const { Q_UNUSED(paintContext); /** * HACK ALERT: * QTextLayout should only be accessed from the thread it has been created in. * If the cached layout has been created in a different thread, we should just * recreate the layouts in the current thread to be able to render them. */ if (QThread::currentThread() != d->cachedLayoutsWorkingThread) { relayout(); } for (int i = 0; i < (int)d->cachedLayouts.size(); i++) { d->cachedLayouts[i]->draw(&painter, d->cachedLayoutsOffsets[i]); } /** * HACK ALERT: * The layouts of non-gui threads must be destroyed in the same thread * they have been created. Because the thread might be restarted in the * meantime or just destroyed, meaning that the per-thread freetype data * will not be available. */ if (QThread::currentThread() != qApp->thread()) { d->cachedLayouts.clear(); d->cachedLayoutsOffsets.clear(); d->cachedLayoutsWorkingThread = 0; } } void KoSvgTextShape::paintStroke(QPainter &painter, KoShapePaintingContext &paintContext) const { Q_UNUSED(painter); Q_UNUSED(paintContext); // do nothing! everything is painted in paintComponent() } QPainterPath KoSvgTextShape::textOutline() { QPainterPath result; result.setFillRule(Qt::WindingFill); for (int i = 0; i < (int)d->cachedLayouts.size(); i++) { const QPointF layoutOffset = d->cachedLayoutsOffsets[i]; const QTextLayout *layout = d->cachedLayouts[i].get(); for (int j = 0; j < layout->lineCount(); j++) { QTextLine line = layout->lineAt(j); Q_FOREACH (const QGlyphRun &run, line.glyphRuns()) { const QVector indexes = run.glyphIndexes(); const QVector positions = run.positions(); const QRawFont font = run.rawFont(); KIS_SAFE_ASSERT_RECOVER(indexes.size() == positions.size()) { continue; } for (int k = 0; k < indexes.size(); k++) { QPainterPath glyph = font.pathForGlyph(indexes[k]); glyph.translate(positions[k] + layoutOffset); result += glyph; } const qreal thickness = font.lineThickness(); const QRectF runBounds = run.boundingRect(); if (run.overline()) { // the offset is calculated to be consistent with the way how Qt renders the text const qreal y = line.y(); QRectF overlineBlob(runBounds.x(), y, runBounds.width(), thickness); overlineBlob.translate(layoutOffset); QPainterPath path; path.addRect(overlineBlob); // don't use direct addRect, because it doesn't care about Qt::WindingFill result += path; } if (run.strikeOut()) { // the offset is calculated to be consistent with the way how Qt renders the text const qreal y = line.y() + 0.5 * line.height(); QRectF strikeThroughBlob(runBounds.x(), y, runBounds.width(), thickness); strikeThroughBlob.translate(layoutOffset); QPainterPath path; path.addRect(strikeThroughBlob); // don't use direct addRect, because it doesn't care about Qt::WindingFill result += path; } if (run.underline()) { const qreal y = line.y() + line.ascent() + font.underlinePosition(); QRectF underlineBlob(runBounds.x(), y, runBounds.width(), thickness); underlineBlob.translate(layoutOffset); QPainterPath path; path.addRect(underlineBlob); // don't use direct addRect, because it doesn't care about Qt::WindingFill result += path; } } } } return result; } void KoSvgTextShape::resetTextShape() { KoSvgTextChunkShape::resetTextShape(); relayout(); } struct TextChunk { QString text; QVector formats; Qt::LayoutDirection direction = Qt::LeftToRight; Qt::Alignment alignment = Qt::AlignLeading; struct SubChunkOffset { QPointF offset; int start = 0; }; QVector offsets; boost::optional xStartPos; boost::optional yStartPos; QPointF applyStartPosOverride(const QPointF &pos) const { QPointF result = pos; if (xStartPos) { result.rx() = *xStartPos; } if (yStartPos) { result.ry() = *yStartPos; } return result; } }; QVector mergeIntoChunks(const QVector &subChunks) { QVector chunks; for (auto it = subChunks.begin(); it != subChunks.end(); ++it) { if (it->transformation.startsNewChunk() || it == subChunks.begin()) { TextChunk newChunk = TextChunk(); newChunk.direction = it->format.layoutDirection(); newChunk.alignment = it->format.calculateAlignment(); newChunk.xStartPos = it->transformation.xPos; newChunk.yStartPos = it->transformation.yPos; chunks.append(newChunk); } TextChunk ¤tChunk = chunks.last(); if (it->transformation.hasRelativeOffset()) { TextChunk::SubChunkOffset o; o.start = currentChunk.text.size(); o.offset = it->transformation.relativeOffset(); KIS_SAFE_ASSERT_RECOVER_NOOP(!o.offset.isNull()); currentChunk.offsets.append(o); } QTextLayout::FormatRange formatRange; formatRange.start = currentChunk.text.size(); formatRange.length = it->text.size(); formatRange.format = it->format; currentChunk.formats.append(formatRange); currentChunk.text += it->text; } return chunks; } /** * Qt's QTextLayout has a weird trait, it doesn't count space characters as * distinct characters in QTextLayout::setNumColumns(), that is, if we want to * position a block of text that starts with a space character in a specific * position, QTextLayout will drop this space and will move the text to the left. * * That is why we have a special wrapper object that ensures that no spaces are * dropped and their horizontal advance parameter is taken into account. */ struct LayoutChunkWrapper { LayoutChunkWrapper(QTextLayout *layout) : m_layout(layout) { } QPointF addTextChunk(int startPos, int length, const QPointF &textChunkStartPos) { QPointF currentTextPos = textChunkStartPos; const int lastPos = startPos + length - 1; KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(startPos == m_addedChars, currentTextPos); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(lastPos < m_layout->text().size(), currentTextPos); // qDebug() << m_layout->text(); QTextLine line; std::swap(line, m_danglingLine); if (!line.isValid()) { line = m_layout->createLine(); } // skip all the space characters that were not included into the Qt's text line const int currentLineStart = line.isValid() ? line.textStart() : startPos + length; while (startPos < currentLineStart && startPos <= lastPos) { currentTextPos.rx() += skipSpaceCharacter(startPos); startPos++; } if (startPos <= lastPos) { // defines the number of columns to look for glyphs const int numChars = lastPos - startPos + 1; // Tabs break the normal column flow // grow to avoid missing glyphs int charOffset = 0; int noChangeCount = 0; while (line.textLength() < numChars) { int tl = line.textLength(); line.setNumColumns(numChars + charOffset); if (tl == line.textLength()) { noChangeCount++; // 5 columns max are needed to discover tab char. Set to 10 to be safe. if (noChangeCount > 10) break; } else { noChangeCount = 0; } charOffset++; } line.setPosition(currentTextPos - QPointF(0, line.ascent())); currentTextPos.rx() += line.horizontalAdvance(); // skip all the space characters that were not included into the Qt's text line for (int i = line.textStart() + line.textLength(); i < lastPos; i++) { currentTextPos.rx() += skipSpaceCharacter(i); } } else { // keep the created but unused line for future use std::swap(line, m_danglingLine); } m_addedChars += length; return currentTextPos; } private: qreal skipSpaceCharacter(int pos) { const QTextCharFormat format = formatForPos(pos, m_layout->formats()); const QChar skippedChar = m_layout->text()[pos]; KIS_SAFE_ASSERT_RECOVER_NOOP(skippedChar.isSpace() || !skippedChar.isPrint()); QFontMetrics metrics(format.font()); #if QT_VERSION >= QT_VERSION_CHECK(5,11,0) return metrics.horizontalAdvance(skippedChar); #else return metrics.width(skippedChar); #endif } static QTextCharFormat formatForPos(int pos, const QVector &formats) { Q_FOREACH (const QTextLayout::FormatRange &range, formats) { if (pos >= range.start && pos < range.start + range.length) { return range.format; } } KIS_SAFE_ASSERT_RECOVER_NOOP(0 && "pos should be within the bounds of the layouted text"); return QTextCharFormat(); } private: int m_addedChars = 0; QTextLayout *m_layout; QTextLine m_danglingLine; }; void KoSvgTextShape::relayout() const { d->cachedLayouts.clear(); d->cachedLayoutsOffsets.clear(); d->cachedLayoutsWorkingThread = QThread::currentThread(); QPointF currentTextPos; QVector textChunks = mergeIntoChunks(layoutInterface()->collectSubChunks()); Q_FOREACH (const TextChunk &chunk, textChunks) { std::shared_ptr layout(new QTextLayout()); QTextOption option; // WARNING: never activate this option! It breaks the RTL text layout! //option.setFlags(QTextOption::ShowTabsAndSpaces); option.setWrapMode(QTextOption::WrapAnywhere); option.setUseDesignMetrics(true); // TODO: investigate if it is needed? option.setTextDirection(chunk.direction); layout->setText(chunk.text); layout->setTextOption(option); layout->setFormats(chunk.formats); layout->setCacheEnabled(true); layout->beginLayout(); currentTextPos = chunk.applyStartPosOverride(currentTextPos); const QPointF anchorPointPos = currentTextPos; int lastSubChunkStart = 0; QPointF lastSubChunkOffset; LayoutChunkWrapper wrapper(layout.get()); for (int i = 0; i <= chunk.offsets.size(); i++) { const bool isFinalPass = i == chunk.offsets.size(); const int length = !isFinalPass ? chunk.offsets[i].start - lastSubChunkStart : chunk.text.size() - lastSubChunkStart; if (length > 0) { currentTextPos += lastSubChunkOffset; currentTextPos = wrapper.addTextChunk(lastSubChunkStart, length, currentTextPos); } if (!isFinalPass) { lastSubChunkOffset = chunk.offsets[i].offset; lastSubChunkStart = chunk.offsets[i].start; } } layout->endLayout(); QPointF diff; if (chunk.alignment & Qt::AlignTrailing || chunk.alignment & Qt::AlignHCenter) { if (chunk.alignment & Qt::AlignTrailing) { diff = currentTextPos - anchorPointPos; } else if (chunk.alignment & Qt::AlignHCenter) { diff = 0.5 * (currentTextPos - anchorPointPos); } // TODO: fix after t2b text implemented diff.ry() = 0; } d->cachedLayouts.push_back(layout); d->cachedLayoutsOffsets.push_back(-diff); } d->clearAssociatedOutlines(this); for (int i = 0; i < int(d->cachedLayouts.size()); i++) { const QTextLayout &layout = *d->cachedLayouts[i]; const QPointF layoutOffset = d->cachedLayoutsOffsets[i]; using namespace KoSvgText; Q_FOREACH (const QTextLayout::FormatRange &range, layout.formats()) { const KoSvgCharChunkFormat &format = static_cast(range.format); AssociatedShapeWrapper wrapper = format.associatedShapeWrapper(); const int rangeStart = range.start; const int safeRangeLength = range.length > 0 ? range.length : layout.text().size() - rangeStart; if (safeRangeLength <= 0) continue; const int rangeEnd = range.start + safeRangeLength - 1; const int firstLineIndex = layout.lineForTextPosition(rangeStart).lineNumber(); const int lastLineIndex = layout.lineForTextPosition(rangeEnd).lineNumber(); for (int i = firstLineIndex; i <= lastLineIndex; i++) { const QTextLine line = layout.lineAt(i); // It might happen that the range contains only one (or two) // symbol that is a whitespace symbol. In such a case we should // just skip this (invalid) line. if (!line.isValid()) continue; const int posStart = qMax(line.textStart(), rangeStart); const int posEnd = qMin(line.textStart() + line.textLength() - 1, rangeEnd); const QList glyphRuns = line.glyphRuns(posStart, posEnd - posStart + 1); Q_FOREACH (const QGlyphRun &run, glyphRuns) { const QPointF firstPosition = run.positions().first(); const quint32 firstGlyphIndex = run.glyphIndexes().first(); const QPointF lastPosition = run.positions().last(); const quint32 lastGlyphIndex = run.glyphIndexes().last(); const QRawFont rawFont = run.rawFont(); const QRectF firstGlyphRect = rawFont.boundingRect(firstGlyphIndex).translated(firstPosition); const QRectF lastGlyphRect = rawFont.boundingRect(lastGlyphIndex).translated(lastPosition); QRectF rect = run.boundingRect(); /** * HACK ALERT: there is a bug in a way how Qt calculates boundingRect() * of the glyph run. It doesn't care about left and right bearings * of the border chars in the run, therefore it becomes cropped. * * Here we just add a half-char width margin to both sides * of the glyph run to make sure the glyphs are fully painted. * * BUG: 389528 * BUG: 392068 */ rect.setLeft(qMin(rect.left(), lastGlyphRect.left()) - 0.5 * firstGlyphRect.width()); rect.setRight(qMax(rect.right(), lastGlyphRect.right()) + 0.5 * lastGlyphRect.width()); wrapper.addCharacterRect(rect.translated(layoutOffset)); } } } } } void KoSvgTextShape::Private::clearAssociatedOutlines(const KoShape *rootShape) { const KoSvgTextChunkShape *chunkShape = dynamic_cast(rootShape); KIS_SAFE_ASSERT_RECOVER_RETURN(chunkShape); chunkShape->layoutInterface()->clearAssociatedOutline(); Q_FOREACH (KoShape *child, chunkShape->shapes()) { clearAssociatedOutlines(child); } } bool KoSvgTextShape::isRootTextNode() const { return true; } KoSvgTextShapeFactory::KoSvgTextShapeFactory() : KoShapeFactoryBase(KoSvgTextShape_SHAPEID, i18n("Text")) { setToolTip(i18n("SVG Text Shape")); setIconName(koIconNameCStr("x-shape-text")); setLoadingPriority(5); setXmlElementNames(KoXmlNS::svg, QStringList("text")); KoShapeTemplate t; t.name = i18n("SVG Text"); t.iconName = koIconName("x-shape-text"); t.toolTip = i18n("SVG Text Shape"); addTemplate(t); } KoShape *KoSvgTextShapeFactory::createDefaultShape(KoDocumentResourceManager *documentResources) const { debugFlake << "Create default svg text shape"; KoSvgTextShape *shape = new KoSvgTextShape(); shape->setShapeId(KoSvgTextShape_SHAPEID); KoSvgTextShapeMarkupConverter converter(shape); converter.convertFromSvg(i18nc("Default text for the text shape", "Placeholder Text"), "", QRectF(0, 0, 200, 60), documentResources->documentResolution()); debugFlake << converter.errors() << converter.warnings(); return shape; } KoShape *KoSvgTextShapeFactory::createShape(const KoProperties *params, KoDocumentResourceManager *documentResources) const { KoSvgTextShape *shape = new KoSvgTextShape(); shape->setShapeId(KoSvgTextShape_SHAPEID); - QString svgText = params->stringProperty("svgText", "Lorem ipsum dolor sit amet, consectetur adipiscing elit."); + QString svgText = params->stringProperty("svgText", i18nc("Default text for the text shape", "Placeholder Text")); QString defs = params->stringProperty("defs" , ""); QRectF shapeRect = QRectF(0, 0, 200, 60); QVariant rect = params->property("shapeRect"); if (rect.type()==QVariant::RectF) { shapeRect = rect.toRectF(); } KoSvgTextShapeMarkupConverter converter(shape); converter.convertFromSvg(svgText, defs, shapeRect, documentResources->documentResolution()); shape->setPosition(shapeRect.topLeft()); return shape; } bool KoSvgTextShapeFactory::supports(const KoXmlElement &/*e*/, KoShapeLoadingContext &/*context*/) const { return false; } diff --git a/libs/global/KisUsageLogger.cpp b/libs/global/KisUsageLogger.cpp index 46ef05ce0d..4f6b6a5eea 100644 --- a/libs/global/KisUsageLogger.cpp +++ b/libs/global/KisUsageLogger.cpp @@ -1,253 +1,259 @@ /* * 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 #include #include +#include +#include Q_GLOBAL_STATIC(KisUsageLogger, s_instance) const QString KisUsageLogger::s_sectionHeader("================================================================================\n"); struct KisUsageLogger::Private { bool active {false}; QFile logFile; QFile sysInfoFile; }; KisUsageLogger::KisUsageLogger() : d(new Private) { d->logFile.setFileName(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/krita.log"); d->sysInfoFile.setFileName(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/krita-sysinfo.log"); rotateLog(); d->logFile.open(QFile::Append | QFile::Text); d->sysInfoFile.open(QFile::WriteOnly | QFile::Text); } KisUsageLogger::~KisUsageLogger() { if (d->active) { close(); } } void KisUsageLogger::initialize() { s_instance->d->active = true; QString systemInfo = basicSystemInfo(); s_instance->d->sysInfoFile.write(systemInfo.toUtf8()); } QString KisUsageLogger::basicSystemInfo() { 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()); #ifdef Q_OS_LINUX systemInfo.append("\n Desktop: ").append(qgetenv("XDG_CURRENT_DESKTOP")); #endif systemInfo.append("\n\n"); return systemInfo; } void KisUsageLogger::close() { log("CLOSING SESSION"); s_instance->d->active = false; s_instance->d->logFile.flush(); s_instance->d->logFile.close(); s_instance->d->sysInfoFile.flush(); s_instance->d->sysInfoFile.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::writeSysInfo(const QString &message) { if (!s_instance->d->active) return; if (!s_instance->d->sysInfoFile.isOpen()) return; s_instance->d->sysInfoFile.write(message.toUtf8()); s_instance->d->sysInfoFile.write("\n"); s_instance->d->sysInfoFile.flush(); } void KisUsageLogger::writeHeader() { Q_ASSERT(s_instance->d->sysInfoFile.isOpen()); s_instance->d->logFile.write(s_sectionHeader.toUtf8()); QString sessionHeader = QString("SESSION: %1. Executing %2\n\n") .arg(QDateTime::currentDateTime().toString(Qt::RFC2822Date)) .arg(qApp->arguments().join(' ')); s_instance->d->logFile.write(sessionHeader.toUtf8()); QString KritaAndQtVersion; KritaAndQtVersion.append("Krita Version: ").append(KritaVersionWrapper::versionString(true)) .append(", Qt version compiled: ").append(QT_VERSION_STR) .append(", loaded: ").append(qVersion()) .append(". Process ID: ") .append(QString::number(qApp->applicationPid())).append("\n"); KritaAndQtVersion.append("-- -- -- -- -- -- -- --\n"); s_instance->d->logFile.write(KritaAndQtVersion.toUtf8()); s_instance->d->logFile.flush(); + log(QString("Style: %1. Available styles: %2") + .arg(qApp->style()->objectName(), + QStyleFactory::keys().join(", "))); + } QString KisUsageLogger::screenInformation() { QList screens = qApp->screens(); QString info; info.append("Display Information"); info.append("\nNumber of screens: ").append(QString::number(screens.size())); for (int i = 0; i < screens.size(); ++i ) { QScreen *screen = screens[i]; info.append("\n\tScreen: ").append(QString::number(i)); info.append("\n\t\tName: ").append(screen->name()); info.append("\n\t\tDepth: ").append(QString::number(screen->depth())); info.append("\n\t\tScale: ").append(QString::number(screen->devicePixelRatio())); info.append("\n\t\tResolution in pixels: ").append(QString::number(screen->geometry().width())) .append("x") .append(QString::number(screen->geometry().height())); info.append("\n\t\tManufacturer: ").append(screen->manufacturer()); info.append("\n\t\tModel: ").append(screen->model()); info.append("\n\t\tRefresh Rate: ").append(QString::number(screen->refreshRate())); } info.append("\n"); return info; } void KisUsageLogger::rotateLog() { if (d->logFile.exists()) { { // Check for CLOSING SESSION d->logFile.open(QFile::ReadOnly); QString log = QString::fromUtf8(d->logFile.readAll()); if (!log.split(s_sectionHeader).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(); } { // 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_algebra_2d.cpp b/libs/global/kis_algebra_2d.cpp index ac6383bec0..2bfb88edda 100644 --- a/libs/global/kis_algebra_2d.cpp +++ b/libs/global/kis_algebra_2d.cpp @@ -1,681 +1,681 @@ /* * 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_algebra_2d.h" #include #include #include #include #include #include #include #include #include #include #include #define SANITY_CHECKS namespace KisAlgebra2D { void adjustIfOnPolygonBoundary(const QPolygonF &poly, int polygonDirection, QPointF *pt) { const int numPoints = poly.size(); for (int i = 0; i < numPoints; i++) { int nextI = i + 1; if (nextI >= numPoints) { nextI = 0; } const QPointF &p0 = poly[i]; const QPointF &p1 = poly[nextI]; QPointF edge = p1 - p0; qreal cross = crossProduct(edge, *pt - p0) / (0.5 * edge.manhattanLength()); if (cross < 1.0 && isInRange(pt->x(), p0.x(), p1.x()) && isInRange(pt->y(), p0.y(), p1.y())) { QPointF salt = 1.0e-3 * inwardUnitNormal(edge, polygonDirection); QPointF adjustedPoint = *pt + salt; // in case the polygon is self-intersecting, polygon direction // might not help if (kisDistanceToLine(adjustedPoint, QLineF(p0, p1)) < 1e-4) { adjustedPoint = *pt - salt; #ifdef SANITY_CHECKS if (kisDistanceToLine(adjustedPoint, QLineF(p0, p1)) < 1e-4) { dbgKrita << ppVar(*pt); dbgKrita << ppVar(adjustedPoint); dbgKrita << ppVar(QLineF(p0, p1)); dbgKrita << ppVar(salt); dbgKrita << ppVar(poly.containsPoint(*pt, Qt::OddEvenFill)); dbgKrita << ppVar(kisDistanceToLine(*pt, QLineF(p0, p1))); dbgKrita << ppVar(kisDistanceToLine(adjustedPoint, QLineF(p0, p1))); } *pt = adjustedPoint; KIS_ASSERT_RECOVER_NOOP(kisDistanceToLine(*pt, QLineF(p0, p1)) > 1e-4); #endif /* SANITY_CHECKS */ } } } } QPointF transformAsBase(const QPointF &pt, const QPointF &base1, const QPointF &base2) { qreal len1 = norm(base1); if (len1 < 1e-5) return pt; qreal sin1 = base1.y() / len1; qreal cos1 = base1.x() / len1; qreal len2 = norm(base2); if (len2 < 1e-5) return QPointF(); qreal sin2 = base2.y() / len2; qreal cos2 = base2.x() / len2; qreal sinD = sin2 * cos1 - cos2 * sin1; qreal cosD = cos1 * cos2 + sin1 * sin2; qreal scaleD = len2 / len1; QPointF result; result.rx() = scaleD * (pt.x() * cosD - pt.y() * sinD); result.ry() = scaleD * (pt.x() * sinD + pt.y() * cosD); return result; } qreal angleBetweenVectors(const QPointF &v1, const QPointF &v2) { qreal a1 = std::atan2(v1.y(), v1.x()); qreal a2 = std::atan2(v2.y(), v2.x()); return a2 - a1; } qreal directionBetweenPoints(const QPointF &p1, const QPointF &p2, qreal defaultAngle) { if (fuzzyPointCompare(p1, p2)) { return defaultAngle; } const QVector2D diff(p2 - p1); return std::atan2(diff.y(), diff.x()); } QPainterPath smallArrow() { QPainterPath p; p.moveTo(5, 2); p.lineTo(-3, 8); p.lineTo(-5, 5); p.lineTo( 2, 0); p.lineTo(-5,-5); p.lineTo(-3,-8); p.lineTo( 5,-2); p.arcTo(QRectF(3, -2, 4, 4), 90, -180); return p; } template inline Point ensureInRectImpl(Point pt, const Rect &bounds) { if (pt.x() > bounds.right()) { pt.rx() = bounds.right(); } else if (pt.x() < bounds.left()) { pt.rx() = bounds.left(); } if (pt.y() > bounds.bottom()) { pt.ry() = bounds.bottom(); } else if (pt.y() < bounds.top()) { pt.ry() = bounds.top(); } return pt; } QPoint ensureInRect(QPoint pt, const QRect &bounds) { return ensureInRectImpl(pt, bounds); } QPointF ensureInRect(QPointF pt, const QRectF &bounds) { return ensureInRectImpl(pt, bounds); } bool intersectLineRect(QLineF &line, const QRect rect) { QPointF pt1 = QPointF(), pt2 = QPointF(); QPointF tmp; if (line.intersect(QLineF(rect.topLeft(), rect.topRight()), &tmp) != QLineF::NoIntersection) { if (tmp.x() >= rect.left() && tmp.x() <= rect.right()) { pt1 = tmp; } } if (line.intersect(QLineF(rect.topRight(), rect.bottomRight()), &tmp) != QLineF::NoIntersection) { if (tmp.y() >= rect.top() && tmp.y() <= rect.bottom()) { if (pt1.isNull()) pt1 = tmp; else pt2 = tmp; } } if (line.intersect(QLineF(rect.bottomRight(), rect.bottomLeft()), &tmp) != QLineF::NoIntersection) { if (tmp.x() >= rect.left() && tmp.x() <= rect.right()) { if (pt1.isNull()) pt1 = tmp; else pt2 = tmp; } } if (line.intersect(QLineF(rect.bottomLeft(), rect.topLeft()), &tmp) != QLineF::NoIntersection) { if (tmp.y() >= rect.top() && tmp.y() <= rect.bottom()) { if (pt1.isNull()) pt1 = tmp; else pt2 = tmp; } } if (pt1.isNull() || pt2.isNull()) return false; // Attempt to retain polarity of end points if ((line.x1() < line.x2()) != (pt1.x() > pt2.x()) || (line.y1() < line.y2()) != (pt1.y() > pt2.y())) { tmp = pt1; pt1 = pt2; pt2 = tmp; } line.setP1(pt1); line.setP2(pt2); return true; } template QVector sampleRectWithPoints(const Rect &rect) { QVector points; Point m1 = 0.5 * (rect.topLeft() + rect.topRight()); Point m2 = 0.5 * (rect.bottomLeft() + rect.bottomRight()); points << rect.topLeft(); points << m1; points << rect.topRight(); points << 0.5 * (rect.topLeft() + rect.bottomLeft()); points << 0.5 * (m1 + m2); points << 0.5 * (rect.topRight() + rect.bottomRight()); points << rect.bottomLeft(); points << m2; points << rect.bottomRight(); return points; } QVector sampleRectWithPoints(const QRect &rect) { return sampleRectWithPoints(rect); } QVector sampleRectWithPoints(const QRectF &rect) { return sampleRectWithPoints(rect); } template Rect approximateRectFromPointsImpl(const QVector &points) { using namespace boost::accumulators; accumulator_set > accX; accumulator_set > accY; Q_FOREACH (const Point &pt, points) { accX(pt.x()); accY(pt.y()); } Rect resultRect; if (alignPixels) { resultRect.setCoords(std::floor(min(accX)), std::floor(min(accY)), std::ceil(max(accX)), std::ceil(max(accY))); } else { resultRect.setCoords(min(accX), min(accY), max(accX), max(accY)); } return resultRect; } QRect approximateRectFromPoints(const QVector &points) { return approximateRectFromPointsImpl(points); } QRectF approximateRectFromPoints(const QVector &points) { return approximateRectFromPointsImpl(points); } QRect approximateRectWithPointTransform(const QRect &rect, std::function func) { QVector points = sampleRectWithPoints(rect); using namespace boost::accumulators; accumulator_set > accX; accumulator_set > accY; Q_FOREACH (const QPoint &pt, points) { QPointF dstPt = func(pt); accX(dstPt.x()); accY(dstPt.y()); } QRect resultRect; resultRect.setCoords(std::floor(min(accX)), std::floor(min(accY)), std::ceil(max(accX)), std::ceil(max(accY))); return resultRect; } QRectF cutOffRect(const QRectF &rc, const KisAlgebra2D::RightHalfPlane &p) { QVector points; const QLineF cutLine = p.getLine(); points << rc.topLeft(); points << rc.topRight(); points << rc.bottomRight(); points << rc.bottomLeft(); QPointF p1 = points[3]; bool p1Valid = p.pos(p1) >= 0; QVector resultPoints; for (int i = 0; i < 4; i++) { const QPointF p2 = points[i]; const bool p2Valid = p.pos(p2) >= 0; if (p1Valid != p2Valid) { QPointF intersection; cutLine.intersect(QLineF(p1, p2), &intersection); resultPoints << intersection; } if (p2Valid) { resultPoints << p2; } p1 = p2; p1Valid = p2Valid; } return approximateRectFromPoints(resultPoints); } int quadraticEquation(qreal a, qreal b, qreal c, qreal *x1, qreal *x2) { int numSolutions = 0; const qreal D = pow2(b) - 4 * a * c; const qreal eps = 1e-14; if (qAbs(D) <= eps) { *x1 = -b / (2 * a); numSolutions = 1; } else if (D < 0) { return 0; } else { const qreal sqrt_D = std::sqrt(D); *x1 = (-b + sqrt_D) / (2 * a); *x2 = (-b - sqrt_D) / (2 * a); numSolutions = 2; } return numSolutions; } QVector intersectTwoCircles(const QPointF ¢er1, qreal r1, const QPointF ¢er2, qreal r2) { QVector points; const QPointF diff = (center2 - center1); const QPointF c1; const QPointF c2 = diff; const qreal centerDistance = norm(diff); if (centerDistance > r1 + r2) return points; if (centerDistance < qAbs(r1 - r2)) return points; if (centerDistance < qAbs(r1 - r2) + 0.001) { dbgKrita << "Skipping intersection" << ppVar(center1) << ppVar(center2) << ppVar(r1) << ppVar(r2) << ppVar(centerDistance) << ppVar(qAbs(r1-r2)); return points; } const qreal x_kp1 = diff.x(); const qreal y_kp1 = diff.y(); const qreal F2 = 0.5 * (pow2(x_kp1) + pow2(y_kp1) + pow2(r1) - pow2(r2)); const qreal eps = 1e-6; if (qAbs(diff.y()) < eps) { qreal x = F2 / diff.x(); qreal y1, y2; int result = KisAlgebra2D::quadraticEquation( 1, 0, pow2(x) - pow2(r2), &y1, &y2); KIS_SAFE_ASSERT_RECOVER(result > 0) { return points; } if (result == 1) { points << QPointF(x, y1); } else if (result == 2) { KisAlgebra2D::RightHalfPlane p(c1, c2); QPointF p1(x, y1); QPointF p2(x, y2); if (p.pos(p1) >= 0) { points << p1; points << p2; } else { points << p2; points << p1; } } } else { const qreal A = diff.x() / diff.y(); const qreal C = F2 / diff.y(); qreal x1, x2; int result = KisAlgebra2D::quadraticEquation( 1 + pow2(A), -2 * A * C, pow2(C) - pow2(r1), &x1, &x2); KIS_SAFE_ASSERT_RECOVER(result > 0) { return points; } if (result == 1) { points << QPointF(x1, C - x1 * A); } else if (result == 2) { KisAlgebra2D::RightHalfPlane p(c1, c2); QPointF p1(x1, C - x1 * A); QPointF p2(x2, C - x2 * A); if (p.pos(p1) >= 0) { points << p1; points << p2; } else { points << p2; points << p1; } } } for (int i = 0; i < points.size(); i++) { points[i] = center1 + points[i]; } return points; } QTransform mapToRect(const QRectF &rect) { return QTransform(rect.width(), 0, 0, rect.height(), rect.x(), rect.y()); } QTransform mapToRectInverse(const QRectF &rect) { return QTransform::fromTranslate(-rect.x(), -rect.y()) * - QTransform::fromScale(rect.width() > 0 ? 1.0 / rect.width() : 0.0, - rect.height() > 0 ? 1.0 / rect.height() : 0.0); + QTransform::fromScale(rect.width() != 0 ? 1.0 / rect.width() : 0.0, + rect.height() != 0 ? 1.0 / rect.height() : 0.0); } bool fuzzyMatrixCompare(const QTransform &t1, const QTransform &t2, qreal delta) { return qAbs(t1.m11() - t2.m11()) < delta && qAbs(t1.m12() - t2.m12()) < delta && qAbs(t1.m13() - t2.m13()) < delta && qAbs(t1.m21() - t2.m21()) < delta && qAbs(t1.m22() - t2.m22()) < delta && qAbs(t1.m23() - t2.m23()) < delta && qAbs(t1.m31() - t2.m31()) < delta && qAbs(t1.m32() - t2.m32()) < delta && qAbs(t1.m33() - t2.m33()) < delta; } bool fuzzyPointCompare(const QPointF &p1, const QPointF &p2) { return qFuzzyCompare(p1.x(), p2.x()) && qFuzzyCompare(p1.y(), p2.y()); } bool fuzzyPointCompare(const QPointF &p1, const QPointF &p2, qreal delta) { return qAbs(p1.x() - p2.x()) < delta && qAbs(p1.y() - p2.y()) < delta; } /********************************************************/ /* DecomposedMatix */ /********************************************************/ DecomposedMatix::DecomposedMatix() { } DecomposedMatix::DecomposedMatix(const QTransform &t0) { QTransform t(t0); QTransform projMatrix; if (t.m33() == 0.0 || t0.determinant() == 0.0) { qWarning() << "Cannot decompose matrix!" << t; valid = false; return; } if (t.type() == QTransform::TxProject) { QTransform affineTransform(t.toAffine()); projMatrix = affineTransform.inverted() * t; t = affineTransform; proj[0] = projMatrix.m13(); proj[1] = projMatrix.m23(); proj[2] = projMatrix.m33(); } std::array rows; rows[0] = QVector3D(t.m11(), t.m12(), t.m13()); rows[1] = QVector3D(t.m21(), t.m22(), t.m23()); rows[2] = QVector3D(t.m31(), t.m32(), t.m33()); if (!qFuzzyCompare(t.m33(), 1.0)) { const qreal invM33 = 1.0 / t.m33(); for (auto &row : rows) { row *= invM33; } } dx = rows[2].x(); dy = rows[2].y(); rows[2] = QVector3D(0,0,1); scaleX = rows[0].length(); rows[0] *= 1.0 / scaleX; shearXY = QVector3D::dotProduct(rows[0], rows[1]); rows[1] = rows[1] - shearXY * rows[0]; scaleY = rows[1].length(); rows[1] *= 1.0 / scaleY; shearXY *= 1.0 / scaleY; // If determinant is negative, one axis was flipped. qreal determinant = rows[0].x() * rows[1].y() - rows[0].y() * rows[1].x(); if (determinant < 0) { // Flip axis with minimum unit vector dot product. if (rows[0].x() < rows[1].y()) { scaleX = -scaleX; rows[0] = -rows[0]; } else { scaleY = -scaleY; rows[1] = -rows[1]; } shearXY = - shearXY; } angle = kisRadiansToDegrees(std::atan2(rows[0].y(), rows[0].x())); if (angle != 0.0) { // Rotate(-angle) = [cos(angle), sin(angle), -sin(angle), cos(angle)] // = [row0x, -row0y, row0y, row0x] // Thanks to the normalization above. qreal sn = -rows[0].y(); qreal cs = rows[0].x(); qreal m11 = rows[0].x(); qreal m12 = rows[0].y(); qreal m21 = rows[1].x(); qreal m22 = rows[1].y(); rows[0].setX(cs * m11 + sn * m21); rows[0].setY(cs * m12 + sn * m22); rows[1].setX(-sn * m11 + cs * m21); rows[1].setY(-sn * m12 + cs * m22); } QTransform leftOver( rows[0].x(), rows[0].y(), rows[0].z(), rows[1].x(), rows[1].y(), rows[1].z(), rows[2].x(), rows[2].y(), rows[2].z()); KIS_SAFE_ASSERT_RECOVER_NOOP(fuzzyMatrixCompare(leftOver, QTransform(), 1e-4)); } inline QTransform toQTransformStraight(const Eigen::Matrix3d &m) { return QTransform(m(0,0), m(0,1), m(0,2), m(1,0), m(1,1), m(1,2), m(2,0), m(2,1), m(2,2)); } inline Eigen::Matrix3d fromQTransformStraight(const QTransform &t) { Eigen::Matrix3d m; m << t.m11() , t.m12() , t.m13() ,t.m21() , t.m22() , t.m23() ,t.m31() , t.m32() , t.m33(); return m; } std::pair transformEllipse(const QPointF &axes, const QTransform &fullLocalToGlobal) { KisAlgebra2D::DecomposedMatix decomposed(fullLocalToGlobal); const QTransform localToGlobal = decomposed.scaleTransform() * decomposed.shearTransform() * decomposed.rotateTransform(); const QTransform localEllipse = QTransform(1.0 / pow2(axes.x()), 0.0, 0.0, 0.0, 1.0 / pow2(axes.y()), 0.0, 0.0, 0.0, 1.0); const QTransform globalToLocal = localToGlobal.inverted(); Eigen::Matrix3d eqM = fromQTransformStraight(globalToLocal * localEllipse * globalToLocal.transposed()); // std::cout << "eqM:" << std::endl << eqM << std::endl; Eigen::EigenSolver eigenSolver(eqM); const Eigen::Matrix3d T = eigenSolver.eigenvalues().real().asDiagonal(); const Eigen::Matrix3d U = eigenSolver.eigenvectors().real(); const Eigen::Matrix3d Ti = eigenSolver.eigenvalues().imag().asDiagonal(); const Eigen::Matrix3d Ui = eigenSolver.eigenvectors().imag(); KIS_SAFE_ASSERT_RECOVER_NOOP(Ti.isZero()); KIS_SAFE_ASSERT_RECOVER_NOOP(Ui.isZero()); KIS_SAFE_ASSERT_RECOVER_NOOP((U * U.transpose()).isIdentity()); // std::cout << "T:" << std::endl << T << std::endl; // std::cout << "U:" << std::endl << U << std::endl; // std::cout << "Ti:" << std::endl << Ti << std::endl; // std::cout << "Ui:" << std::endl << Ui << std::endl; // std::cout << "UTU':" << std::endl << U * T * U.transpose() << std::endl; const qreal newA = 1.0 / std::sqrt(T(0,0) * T(2,2)); const qreal newB = 1.0 / std::sqrt(T(1,1) * T(2,2)); const QTransform newGlobalToLocal = toQTransformStraight(U); const QTransform newLocalToGlobal = QTransform::fromScale(-1,-1) * newGlobalToLocal.inverted() * decomposed.translateTransform(); return std::make_pair(QPointF(newA, newB), newLocalToGlobal); } QPointF alignForZoom(const QPointF &pt, qreal zoom) { return QPointF((pt * zoom).toPoint()) / zoom; } } diff --git a/libs/image/KisSelectionUpdateCompressor.cpp b/libs/image/KisSelectionUpdateCompressor.cpp index 129e6d471b..91206c8f9d 100644 --- a/libs/image/KisSelectionUpdateCompressor.cpp +++ b/libs/image/KisSelectionUpdateCompressor.cpp @@ -1,75 +1,77 @@ /* * 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 "KisSelectionUpdateCompressor.h" #include "kis_image.h" #include "kis_selection.h" #include "kis_layer_utils.h" #include "kis_update_selection_job.h" KisSelectionUpdateCompressor::KisSelectionUpdateCompressor(KisSelection *selection) : m_parentSelection(selection) , m_updateSignalCompressor(new KisThreadSafeSignalCompressor(100, KisSignalCompressor::POSTPONE)) { connect(m_updateSignalCompressor, SIGNAL(timeout()), this, SLOT(startUpdateJob())); this->moveToThread(m_updateSignalCompressor->thread()); } KisSelectionUpdateCompressor::~KisSelectionUpdateCompressor() { m_updateSignalCompressor->deleteLater(); } void KisSelectionUpdateCompressor::requestUpdate(const QRect &updateRect) { m_fullUpdateRequested |= updateRect.isEmpty(); m_updateRect = !m_fullUpdateRequested ? m_updateRect | updateRect : QRect(); m_updateSignalCompressor->start(); } void KisSelectionUpdateCompressor::tryProcessStalledUpdate() { if (m_hasStalledUpdate) { m_updateSignalCompressor->start(); } } void KisSelectionUpdateCompressor::startUpdateJob() { KisNodeSP parentNode = m_parentSelection->parentNode(); if (!parentNode) { m_hasStalledUpdate = true; return; } + // FIXME: we cannot use parentNode->image() here because masks don't + // have the pointer initialized for some reason. KisImageSP image = KisLayerUtils::findImageByHierarchy(parentNode); if (!image) { m_hasStalledUpdate = true; return; } if (image) { image->addSpontaneousJob(new KisUpdateSelectionJob(m_parentSelection, m_updateRect)); } m_updateRect = QRect(); m_fullUpdateRequested = false; m_hasStalledUpdate = false; } diff --git a/libs/image/commands/kis_node_property_list_command.cpp b/libs/image/commands/kis_node_property_list_command.cpp index 85b163ee17..2b21f12566 100644 --- a/libs/image/commands/kis_node_property_list_command.cpp +++ b/libs/image/commands/kis_node_property_list_command.cpp @@ -1,228 +1,229 @@ /* * Copyright (c) 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 #include "kis_node.h" #include "kis_layer.h" #include "kis_image.h" #include "kis_selection_mask.h" #include "kis_paint_layer.h" #include "commands/kis_node_property_list_command.h" #include "kis_undo_adapter.h" #include "kis_layer_properties_icons.h" // HACK! please refactor out! #include "kis_simple_stroke_strategy.h" +#include "kis_abstract_projection_plane.h" KisNodePropertyListCommand::KisNodePropertyListCommand(KisNodeSP node, KisBaseNode::PropertyList newPropertyList) : KisNodeCommand(kundo2_i18n("Property Changes"), node), m_newPropertyList(newPropertyList), m_oldPropertyList(node->sectionModelProperties()) /** * TODO instead of "Property Changes" check which property * has been changed and display either lock/unlock, visible/hidden * or "Property Changes" (this require new strings) */ { } void KisNodePropertyListCommand::redo() { const KisBaseNode::PropertyList propsBefore = m_node->sectionModelProperties(); - const QRect oldExtent = m_node->extent(); + const QRect oldExtent = m_node->projectionPlane()->tightUserVisibleBounds(); m_node->setSectionModelProperties(m_newPropertyList); - doUpdate(propsBefore, m_node->sectionModelProperties(), oldExtent | m_node->extent()); + doUpdate(propsBefore, m_node->sectionModelProperties(), oldExtent | m_node->projectionPlane()->tightUserVisibleBounds()); } void KisNodePropertyListCommand::undo() { const KisBaseNode::PropertyList propsBefore = m_node->sectionModelProperties(); - const QRect oldExtent = m_node->extent(); + const QRect oldExtent = m_node->projectionPlane()->tightUserVisibleBounds(); m_node->setSectionModelProperties(m_oldPropertyList); - doUpdate(propsBefore, m_node->sectionModelProperties(), oldExtent | m_node->extent()); + doUpdate(propsBefore, m_node->sectionModelProperties(), oldExtent | m_node->projectionPlane()->tightUserVisibleBounds()); } bool checkOnionSkinChanged(const KisBaseNode::PropertyList &oldPropertyList, const KisBaseNode::PropertyList &newPropertyList) { if (oldPropertyList.size() != newPropertyList.size()) return false; bool oldOnionSkinsValue = false; bool newOnionSkinsValue = false; Q_FOREACH (const KisBaseNode::Property &prop, oldPropertyList) { if (prop.id == KisLayerPropertiesIcons::onionSkins.id()) { oldOnionSkinsValue = prop.state.toBool(); } } Q_FOREACH (const KisBaseNode::Property &prop, newPropertyList) { if (prop.id == KisLayerPropertiesIcons::onionSkins.id()) { newOnionSkinsValue = prop.state.toBool(); } } return oldOnionSkinsValue != newOnionSkinsValue; } void KisNodePropertyListCommand::doUpdate(const KisBaseNode::PropertyList &oldPropertyList, const KisBaseNode::PropertyList &newPropertyList, const QRect &totalUpdateExtent) { /** * Sometimes the node might refuse to change the property, e.g. needs-update for colorize * mask. In this case we should avoid issuing the update and set-modified call. */ if (oldPropertyList == newPropertyList) { return; } bool oldPassThroughValue = false; bool newPassThroughValue = false; bool oldVisibilityValue = false; bool newVisibilityValue = false; Q_FOREACH (const KisBaseNode::Property &prop, oldPropertyList) { if (prop.id == KisLayerPropertiesIcons::passThrough.id()) { oldPassThroughValue = prop.state.toBool(); } if (prop.id == KisLayerPropertiesIcons::visible.id()) { oldVisibilityValue = prop.state.toBool(); } } Q_FOREACH (const KisBaseNode::Property &prop, newPropertyList) { if (prop.id == KisLayerPropertiesIcons::passThrough.id()) { newPassThroughValue = prop.state.toBool(); } if (prop.id == KisLayerPropertiesIcons::visible.id()) { newVisibilityValue = prop.state.toBool(); } } if (oldPassThroughValue && !newPassThroughValue) { KisLayerSP layer(qobject_cast(m_node.data())); KisImageSP image = layer->image().toStrongRef(); if (image) { image->refreshGraphAsync(layer); } } else if ((m_node->parent() && !oldPassThroughValue && newPassThroughValue) || (oldPassThroughValue && newPassThroughValue && !oldVisibilityValue && newVisibilityValue)) { KisLayerSP layer(qobject_cast(m_node->parent().data())); KisImageSP image = layer->image().toStrongRef(); if (image) { image->refreshGraphAsync(layer); } } else if (checkOnionSkinChanged(oldPropertyList, newPropertyList)) { m_node->setDirtyDontResetAnimationCache(totalUpdateExtent); } else { m_node->setDirty(totalUpdateExtent); // TODO check if visibility was actually changed or not } } void KisNodePropertyListCommand::setNodePropertiesNoUndo(KisNodeSP node, KisImageSP image, PropertyList proplist) { QVector undo; Q_FOREACH (const KisBaseNode::Property &prop, proplist) { if (prop.isInStasis) undo << false; if (prop.name == i18n("Visible") && node->visible() != prop.state.toBool()) { undo << false; continue; } else if (prop.name == i18n("Locked") && node->userLocked() != prop.state.toBool()) { undo << false; continue; } else if (prop.name == i18n("Active")) { if (KisSelectionMask *m = dynamic_cast(node.data())) { if (m->active() != prop.state.toBool()) { undo << false; continue; } } } else if (prop.name == i18n("Alpha Locked")) { if (KisPaintLayer* l = dynamic_cast(node.data())) { if (l->alphaLocked() != prop.state.toBool()) { undo << false; continue; } } } // This property is known, but it hasn't got the same value, and it isn't one of // the previous properties, so we need to add the command to the undo list. Q_FOREACH(const KisBaseNode::Property &p2, node->sectionModelProperties()) { if (p2.name == prop.name && p2.state != prop.state) { undo << true; break; } } } QScopedPointer cmd(new KisNodePropertyListCommand(node, proplist)); image->setModified(); if (undo.contains(true)) { image->undoAdapter()->addCommand(cmd.take()); } else { /** * HACK ALERT! * * Here we start a fake legacy stroke, so that all the LoD planes would * be invalidated. Ideally, we should refactor this method and avoid * resetting LoD planes when node visibility changes, Instead there should * be two commands executes: LoD agnostic one (which sets the properties * themselves), and two LoD-specific update commands: one for lodN and * another one for lod0. */ struct SimpleLodResettingStroke : public KisSimpleStrokeStrategy { SimpleLodResettingStroke(KUndo2Command *cmd) : KisSimpleStrokeStrategy(QLatin1String("SimpleLodResettingStroke")), m_cmd(cmd) { setClearsRedoOnStart(false); this->enableJob(JOB_INIT, true); } void initStrokeCallback() override { m_cmd->redo(); } private: QScopedPointer m_cmd; }; KisStrokeId strokeId = image->startStroke(new SimpleLodResettingStroke(cmd.take())); image->endStroke(strokeId); } } diff --git a/libs/image/kis_asl_layer_style_serializer.cpp b/libs/image/kis_asl_layer_style_serializer.cpp index a02df3955a..1abcf70d98 100644 --- a/libs/image/kis_asl_layer_style_serializer.cpp +++ b/libs/image/kis_asl_layer_style_serializer.cpp @@ -1,1273 +1,1272 @@ /* * 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_asl_layer_style_serializer.h" #include #include #include #include #include #include #include "kis_dom_utils.h" #include "psd.h" #include "kis_global.h" #include "asl/kis_asl_reader.h" #include "asl/kis_asl_xml_parser.h" #include "asl/kis_asl_writer_utils.h" #include "asl/kis_asl_xml_writer.h" #include "asl/kis_asl_writer.h" #include using namespace std::placeholders; KisAslLayerStyleSerializer::KisAslLayerStyleSerializer() { } KisAslLayerStyleSerializer::~KisAslLayerStyleSerializer() { } QVector KisAslLayerStyleSerializer::styles() const { return m_stylesVector; } void KisAslLayerStyleSerializer::setStyles(const QVector &styles) { m_stylesVector = styles; Q_FOREACH(const KisPSDLayerStyleSP style, styles) { m_stylesHash.insert(style->psdUuid(), style); } m_initialized = true; } QHash KisAslLayerStyleSerializer::patterns() const { return m_patternsStore; } QHash KisAslLayerStyleSerializer::stylesHash() { if (m_stylesHash.count() == 0 && m_stylesVector.count() != 0) { // build the hash Q_FOREACH(KisPSDLayerStyleSP style, m_stylesVector) { m_stylesHash.insert(style->psdUuid(), style); } } return m_stylesHash; } QString compositeOpToBlendMode(const QString &compositeOp) { QString mode = "Nrml"; if (compositeOp == COMPOSITE_OVER) { mode = "Nrml"; } else if (compositeOp == COMPOSITE_DISSOLVE) { mode = "Dslv"; } else if (compositeOp == COMPOSITE_DARKEN) { mode = "Drkn"; } else if (compositeOp == COMPOSITE_MULT) { mode = "Mltp"; } else if (compositeOp == COMPOSITE_BURN) { mode = "CBrn"; } else if (compositeOp == COMPOSITE_LINEAR_BURN) { mode = "linearBurn"; } else if (compositeOp == COMPOSITE_DARKER_COLOR) { mode = "darkerColor"; } else if (compositeOp == COMPOSITE_LIGHTEN) { mode = "Lghn"; } else if (compositeOp == COMPOSITE_SCREEN) { mode = "Scrn"; } else if (compositeOp == COMPOSITE_DODGE) { mode = "CDdg"; } else if (compositeOp == COMPOSITE_LINEAR_DODGE) { mode = "linearDodge"; } else if (compositeOp == COMPOSITE_LIGHTER_COLOR) { mode = "lighterColor"; } else if (compositeOp == COMPOSITE_OVERLAY) { mode = "Ovrl"; } else if (compositeOp == COMPOSITE_SOFT_LIGHT_PHOTOSHOP) { mode = "SftL"; } else if (compositeOp == COMPOSITE_HARD_LIGHT) { mode = "HrdL"; } else if (compositeOp == COMPOSITE_VIVID_LIGHT) { mode = "vividLight"; } else if (compositeOp == COMPOSITE_LINEAR_LIGHT) { mode = "linearLight"; } else if (compositeOp == COMPOSITE_PIN_LIGHT) { mode = "pinLight"; } else if (compositeOp == COMPOSITE_HARD_MIX_PHOTOSHOP) { mode = "hardMix"; } else if (compositeOp == COMPOSITE_DIFF) { mode = "Dfrn"; } else if (compositeOp == COMPOSITE_EXCLUSION) { mode = "Xclu"; } else if (compositeOp == COMPOSITE_SUBTRACT) { mode = "Sbtr"; } else if (compositeOp == COMPOSITE_DIVIDE) { mode = "divide"; } else if (compositeOp == COMPOSITE_HUE) { mode = "H "; } else if (compositeOp == COMPOSITE_SATURATION) { mode = "Strt"; } else if (compositeOp == COMPOSITE_COLOR) { mode = "Clr "; } else if (compositeOp == COMPOSITE_LUMINIZE) { mode = "Lmns"; } else { dbgKrita << "Unknown composite op:" << mode << "Returning \"Nrml\"!"; } return mode; } QString techniqueToString(psd_technique_type technique, const QString &typeId) { QString result = "SfBL"; switch (technique) { case psd_technique_softer: result = "SfBL"; break; case psd_technique_precise: result = "PrBL"; break; case psd_technique_slope_limit: result = "Slmt"; break; } if (typeId == "BETE" && technique == psd_technique_slope_limit) { warnKrita << "WARNING: techniqueToString: invalid technique type!" << ppVar(technique) << ppVar(typeId); } return result; } QString bevelStyleToString(psd_bevel_style style) { QString result = "OtrB"; switch (style) { case psd_bevel_outer_bevel: result = "OtrB"; break; case psd_bevel_inner_bevel: result = "InrB"; break; case psd_bevel_emboss: result = "Embs"; break; case psd_bevel_pillow_emboss: result = "PlEb"; break; case psd_bevel_stroke_emboss: result = "strokeEmboss"; break; } return result; } QString gradientTypeToString(psd_gradient_style style) { QString result = "Lnr "; switch (style) { case psd_gradient_style_linear: result = "Lnr "; break; case psd_gradient_style_radial: result = "Rdl "; break; case psd_gradient_style_angle: result = "Angl"; break; case psd_gradient_style_reflected: result = "Rflc"; break; case psd_gradient_style_diamond: result = "Dmnd"; break; } return result; } QString strokePositionToString(psd_stroke_position position) { QString result = "OutF"; switch (position) { case psd_stroke_outside: result = "OutF"; break; case psd_stroke_inside: result = "InsF"; break; case psd_stroke_center: result = "CtrF"; break; } return result; } QString strokeFillTypeToString(psd_fill_type position) { QString result = "SClr"; switch (position) { case psd_fill_solid_color: result = "SClr"; break; case psd_fill_gradient: result = "GrFl"; break; case psd_fill_pattern: result = "Ptrn"; break; } return result; } QVector KisAslLayerStyleSerializer::fetchAllPatterns(KisPSDLayerStyle *style) const { QVector allPatterns; if (style->patternOverlay()->effectEnabled()) { allPatterns << style->patternOverlay()->pattern(); } if (style->stroke()->effectEnabled() && style->stroke()->fillType() == psd_fill_pattern) { allPatterns << style->stroke()->pattern(); } if(style->bevelAndEmboss()->effectEnabled() && style->bevelAndEmboss()->textureEnabled()) { allPatterns << style->bevelAndEmboss()->texturePattern(); } return allPatterns; } QString fetchPatternUuidSafe(KoPatternSP pattern, QHash patternToUuid) { if (patternToUuid.contains(pattern)) { return patternToUuid[pattern]; } else { warnKrita << "WARNING: the pattern is not present in the Uuid map!"; return "invalid-uuid"; } } QDomDocument KisAslLayerStyleSerializer::formXmlDocument() const { KIS_ASSERT_RECOVER(!m_stylesVector.isEmpty()) { return QDomDocument(); } QVector allPatterns; Q_FOREACH (KisPSDLayerStyleSP style, m_stylesVector) { allPatterns += fetchAllPatterns(style.data()); } QHash patternToUuidMap; KisAslXmlWriter w; if (!allPatterns.isEmpty()) { w.enterList(ResourceType::Patterns); Q_FOREACH (KoPatternSP pattern, allPatterns) { if (pattern) { if (!patternToUuidMap.contains(pattern)) { QString uuid = w.writePattern("", pattern); patternToUuidMap.insert(pattern, uuid); } } else { warnKrita << "WARNING: KisAslLayerStyleSerializer::saveToDevice: saved pattern is null!"; } } w.leaveList(); } Q_FOREACH (KisPSDLayerStyleSP style, m_stylesVector) { w.enterDescriptor("", "", "null"); w.writeText("Nm ", style->name()); w.writeText("Idnt", style->psdUuid()); w.leaveDescriptor(); w.enterDescriptor("", "", "Styl"); w.enterDescriptor("documentMode", "", "documentMode"); w.leaveDescriptor(); w.enterDescriptor("Lefx", "", "Lefx"); w.writeUnitFloat("Scl ", "#Prc", 100); w.writeBoolean("masterFXSwitch", style->isEnabled()); // Drop Shadow const psd_layer_effects_drop_shadow *dropShadow = style->dropShadow(); if (dropShadow->effectEnabled()) { w.enterDescriptor("DrSh", "", "DrSh"); w.writeBoolean("enab", dropShadow->effectEnabled()); w.writeEnum("Md ", "BlnM", compositeOpToBlendMode(dropShadow->blendMode())); w.writeColor("Clr ", dropShadow->color()); w.writeUnitFloat("Opct", "#Prc", dropShadow->opacity()); w.writeBoolean("uglg", dropShadow->useGlobalLight()); w.writeUnitFloat("lagl", "#Ang", dropShadow->angle()); w.writeUnitFloat("Dstn", "#Pxl", dropShadow->distance()); w.writeUnitFloat("Ckmt", "#Pxl", dropShadow->spread()); w.writeUnitFloat("blur", "#Pxl", dropShadow->size()); w.writeUnitFloat("Nose", "#Prc", dropShadow->noise()); w.writeBoolean("AntA", dropShadow->antiAliased()); // FIXME: save curves w.writeCurve("TrnS", "Linear", QVector() << QPointF() << QPointF(255, 255)); w.writeBoolean("layerConceals", dropShadow->knocksOut()); w.leaveDescriptor(); } // Inner Shadow const psd_layer_effects_inner_shadow *innerShadow = style->innerShadow(); if (innerShadow->effectEnabled()) { w.enterDescriptor("IrSh", "", "IrSh"); w.writeBoolean("enab", innerShadow->effectEnabled()); w.writeEnum("Md ", "BlnM", compositeOpToBlendMode(innerShadow->blendMode())); w.writeColor("Clr ", innerShadow->color()); w.writeUnitFloat("Opct", "#Prc", innerShadow->opacity()); w.writeBoolean("uglg", innerShadow->useGlobalLight()); w.writeUnitFloat("lagl", "#Ang", innerShadow->angle()); w.writeUnitFloat("Dstn", "#Pxl", innerShadow->distance()); w.writeUnitFloat("Ckmt", "#Pxl", innerShadow->spread()); w.writeUnitFloat("blur", "#Pxl", innerShadow->size()); w.writeUnitFloat("Nose", "#Prc", innerShadow->noise()); w.writeBoolean("AntA", innerShadow->antiAliased()); // FIXME: save curves w.writeCurve("TrnS", "Linear", QVector() << QPointF() << QPointF(255, 255)); w.leaveDescriptor(); } // Outer Glow const psd_layer_effects_outer_glow *outerGlow = style->outerGlow(); if (outerGlow->effectEnabled()) { w.enterDescriptor("OrGl", "", "OrGl"); w.writeBoolean("enab", outerGlow->effectEnabled()); w.writeEnum("Md ", "BlnM", compositeOpToBlendMode(outerGlow->blendMode())); - if (outerGlow->fillType() == psd_fill_gradient && outerGlow->gradient()) { KoSegmentGradient *segmentGradient = dynamic_cast(outerGlow->gradient().data()); KoStopGradient *stopGradient = dynamic_cast(outerGlow->gradient().data()); - if (segmentGradient) { + if (segmentGradient && segmentGradient->valid()) { w.writeSegmentGradient("Grad", segmentGradient); - } else if (stopGradient) { + } else if (stopGradient && stopGradient->valid()) { w.writeStopGradient("Grad", stopGradient); } else { warnKrita << "WARNING: OG: Unknown gradient type!"; w.writeColor("Clr ", outerGlow->color()); } } else { w.writeColor("Clr ", outerGlow->color()); } w.writeUnitFloat("Opct", "#Prc", outerGlow->opacity()); w.writeEnum("GlwT", "BETE", techniqueToString(outerGlow->technique(), "BETE")); w.writeUnitFloat("Ckmt", "#Pxl", outerGlow->spread()); w.writeUnitFloat("blur", "#Pxl", outerGlow->size()); w.writeUnitFloat("Nose", "#Prc", outerGlow->noise()); w.writeUnitFloat("ShdN", "#Prc", outerGlow->jitter()); w.writeBoolean("AntA", outerGlow->antiAliased()); // FIXME: save curves w.writeCurve("TrnS", "Linear", QVector() << QPointF() << QPointF(255, 255)); w.writeUnitFloat("Inpr", "#Prc", outerGlow->range()); w.leaveDescriptor(); } // Inner Glow const psd_layer_effects_inner_glow *innerGlow = style->innerGlow(); if (innerGlow->effectEnabled()) { w.enterDescriptor("IrGl", "", "IrGl"); w.writeBoolean("enab", innerGlow->effectEnabled()); w.writeEnum("Md ", "BlnM", compositeOpToBlendMode(innerGlow->blendMode())); - if (innerGlow->fillType() == psd_fill_gradient && innerGlow->gradient()) { KoSegmentGradient *segmentGradient = dynamic_cast(innerGlow->gradient().data()); KoStopGradient *stopGradient = dynamic_cast(innerGlow->gradient().data()); - if (segmentGradient) { + if (segmentGradient && innerGlow->gradient()->valid()) { w.writeSegmentGradient("Grad", segmentGradient); - } else if (stopGradient) { + } else if (stopGradient && innerGlow->gradient()->valid()) { w.writeStopGradient("Grad", stopGradient); } else { warnKrita << "WARNING: IG: Unknown gradient type!"; w.writeColor("Clr ", innerGlow->color()); } } else { w.writeColor("Clr ", innerGlow->color()); } w.writeUnitFloat("Opct", "#Prc", innerGlow->opacity()); w.writeEnum("GlwT", "BETE", techniqueToString(innerGlow->technique(), "BETE")); w.writeUnitFloat("Ckmt", "#Pxl", innerGlow->spread()); w.writeUnitFloat("blur", "#Pxl", innerGlow->size()); // NOTE: order is swapped in ASL! w.writeUnitFloat("ShdN", "#Prc", innerGlow->jitter()); w.writeUnitFloat("Nose", "#Prc", innerGlow->noise()); w.writeBoolean("AntA", innerGlow->antiAliased()); w.writeEnum("glwS", "IGSr", innerGlow->source() == psd_glow_center ? "SrcC" : "SrcE"); // FIXME: save curves w.writeCurve("TrnS", "Linear", QVector() << QPointF() << QPointF(255, 255)); w.writeUnitFloat("Inpr", "#Prc", innerGlow->range()); w.leaveDescriptor(); } // Bevel and Emboss const psd_layer_effects_bevel_emboss *bevelAndEmboss = style->bevelAndEmboss(); if (bevelAndEmboss->effectEnabled()) { w.enterDescriptor("ebbl", "", "ebbl"); w.writeBoolean("enab", bevelAndEmboss->effectEnabled()); w.writeEnum("hglM", "BlnM", compositeOpToBlendMode(bevelAndEmboss->highlightBlendMode())); w.writeColor("hglC", bevelAndEmboss->highlightColor()); w.writeUnitFloat("hglO", "#Prc", bevelAndEmboss->highlightOpacity()); w.writeEnum("sdwM", "BlnM", compositeOpToBlendMode(bevelAndEmboss->shadowBlendMode())); w.writeColor("sdwC", bevelAndEmboss->shadowColor()); w.writeUnitFloat("sdwO", "#Prc", bevelAndEmboss->shadowOpacity()); w.writeEnum("bvlT", "bvlT", techniqueToString(bevelAndEmboss->technique(), "bvlT")); w.writeEnum("bvlS", "BESl", bevelStyleToString(bevelAndEmboss->style())); w.writeBoolean("uglg", bevelAndEmboss->useGlobalLight()); w.writeUnitFloat("lagl", "#Ang", bevelAndEmboss->angle()); w.writeUnitFloat("Lald", "#Ang", bevelAndEmboss->altitude()); w.writeUnitFloat("srgR", "#Prc", bevelAndEmboss->depth()); w.writeUnitFloat("blur", "#Pxl", bevelAndEmboss->size()); w.writeEnum("bvlD", "BESs", bevelAndEmboss->direction() == psd_direction_up ? "In " : "Out "); // FIXME: save curves w.writeCurve("TrnS", "Linear", QVector() << QPointF() << QPointF(255, 255)); w.writeBoolean("antialiasGloss", bevelAndEmboss->glossAntiAliased()); w.writeUnitFloat("Sftn", "#Pxl", bevelAndEmboss->soften()); if (bevelAndEmboss->contourEnabled()) { w.writeBoolean("useShape", bevelAndEmboss->contourEnabled()); // FIXME: save curves w.writeCurve("MpgS", "Linear", QVector() << QPointF() << QPointF(255, 255)); w.writeBoolean("AntA", bevelAndEmboss->antiAliased()); w.writeUnitFloat("Inpr", "#Prc", bevelAndEmboss->contourRange()); } w.writeBoolean("useTexture", bevelAndEmboss->textureEnabled()); if (bevelAndEmboss->textureEnabled()) { w.writeBoolean("InvT", bevelAndEmboss->textureInvert()); w.writeBoolean("Algn", bevelAndEmboss->textureAlignWithLayer()); w.writeUnitFloat("Scl ", "#Prc", bevelAndEmboss->textureScale()); w.writeUnitFloat("textureDepth ", "#Prc", bevelAndEmboss->textureDepth()); w.writePatternRef("Ptrn", bevelAndEmboss->texturePattern(), fetchPatternUuidSafe(bevelAndEmboss->texturePattern(), patternToUuidMap)); w.writePhasePoint("phase", bevelAndEmboss->texturePhase()); } w.leaveDescriptor(); } // Satin const psd_layer_effects_satin *satin = style->satin(); if (satin->effectEnabled()) { w.enterDescriptor("ChFX", "", "ChFX"); w.writeBoolean("enab", satin->effectEnabled()); w.writeEnum("Md ", "BlnM", compositeOpToBlendMode(satin->blendMode())); w.writeColor("Clr ", satin->color()); w.writeBoolean("AntA", satin->antiAliased()); w.writeBoolean("Invr", satin->invert()); w.writeUnitFloat("Opct", "#Prc", satin->opacity()); w.writeUnitFloat("lagl", "#Ang", satin->angle()); w.writeUnitFloat("Dstn", "#Pxl", satin->distance()); w.writeUnitFloat("blur", "#Pxl", satin->size()); // FIXME: save curves w.writeCurve("MpgS", "Linear", QVector() << QPointF() << QPointF(255, 255)); w.leaveDescriptor(); } const psd_layer_effects_color_overlay *colorOverlay = style->colorOverlay(); if (colorOverlay->effectEnabled()) { w.enterDescriptor("SoFi", "", "SoFi"); w.writeBoolean("enab", colorOverlay->effectEnabled()); w.writeEnum("Md ", "BlnM", compositeOpToBlendMode(colorOverlay->blendMode())); w.writeUnitFloat("Opct", "#Prc", colorOverlay->opacity()); w.writeColor("Clr ", colorOverlay->color()); w.leaveDescriptor(); } // Gradient Overlay const psd_layer_effects_gradient_overlay *gradientOverlay = style->gradientOverlay(); KoSegmentGradient *segmentGradient = dynamic_cast(gradientOverlay->gradient().data()); KoStopGradient *stopGradient = dynamic_cast(gradientOverlay->gradient().data()); - if (gradientOverlay->effectEnabled() && (segmentGradient || stopGradient)) { + if (gradientOverlay->effectEnabled() && ((segmentGradient && segmentGradient->valid()) || (stopGradient && stopGradient->valid()))) { w.enterDescriptor("GrFl", "", "GrFl"); w.writeBoolean("enab", gradientOverlay->effectEnabled()); w.writeEnum("Md ", "BlnM", compositeOpToBlendMode(gradientOverlay->blendMode())); w.writeUnitFloat("Opct", "#Prc", gradientOverlay->opacity()); if (segmentGradient) { w.writeSegmentGradient("Grad", segmentGradient); } else if (stopGradient) { w.writeStopGradient("Grad", stopGradient); } w.writeUnitFloat("Angl", "#Ang", gradientOverlay->angle()); w.writeEnum("Type", "GrdT", gradientTypeToString(gradientOverlay->style())); w.writeBoolean("Rvrs", gradientOverlay->reverse()); w.writeBoolean("Algn", gradientOverlay->alignWithLayer()); w.writeUnitFloat("Scl ", "#Prc", gradientOverlay->scale()); w.writeOffsetPoint("Ofst", gradientOverlay->gradientOffset()); // FIXME: Krita doesn't support dithering w.writeBoolean("Dthr", true/*gradientOverlay->dither()*/); w.leaveDescriptor(); } // Pattern Overlay const psd_layer_effects_pattern_overlay *patternOverlay = style->patternOverlay(); if (patternOverlay->effectEnabled()) { w.enterDescriptor("patternFill", "", "patternFill"); w.writeBoolean("enab", patternOverlay->effectEnabled()); w.writeEnum("Md ", "BlnM", compositeOpToBlendMode(patternOverlay->blendMode())); w.writeUnitFloat("Opct", "#Prc", patternOverlay->opacity()); w.writePatternRef("Ptrn", patternOverlay->pattern(), fetchPatternUuidSafe(patternOverlay->pattern(), patternToUuidMap)); w.writeUnitFloat("Scl ", "#Prc", patternOverlay->scale()); w.writeBoolean("Algn", patternOverlay->alignWithLayer()); w.writePhasePoint("phase", patternOverlay->patternPhase()); w.leaveDescriptor(); } const psd_layer_effects_stroke *stroke = style->stroke(); if (stroke->effectEnabled()) { w.enterDescriptor("FrFX", "", "FrFX"); w.writeBoolean("enab", stroke->effectEnabled()); w.writeEnum("Styl", "FStl", strokePositionToString(stroke->position())); w.writeEnum("PntT", "FrFl", strokeFillTypeToString(stroke->fillType())); w.writeEnum("Md ", "BlnM", compositeOpToBlendMode(stroke->blendMode())); w.writeUnitFloat("Opct", "#Prc", stroke->opacity()); w.writeUnitFloat("Sz ", "#Pxl", stroke->size()); if (stroke->fillType() == psd_fill_solid_color) { w.writeColor("Clr ", stroke->color()); - } else if (stroke->fillType() == psd_fill_gradient) { + } + else if (stroke->fillType() == psd_fill_gradient) { KoSegmentGradient *segmentGradient = dynamic_cast(stroke->gradient().data()); KoStopGradient *stopGradient = dynamic_cast(stroke->gradient().data()); - if (segmentGradient) { + if (segmentGradient && segmentGradient->valid()) { w.writeSegmentGradient("Grad", segmentGradient); - } else if (stopGradient) { + } else if (stopGradient && stopGradient->valid()) { w.writeStopGradient("Grad", stopGradient); } else { warnKrita << "WARNING: Stroke: Unknown gradient type!"; w.writeColor("Clr ", stroke->color()); } w.writeUnitFloat("Angl", "#Ang", stroke->angle()); w.writeEnum("Type", "GrdT", gradientTypeToString(stroke->style())); w.writeBoolean("Rvrs", stroke->reverse()); w.writeUnitFloat("Scl ", "#Prc", stroke->scale()); w.writeBoolean("Algn", stroke->alignWithLayer()); w.writeOffsetPoint("Ofst", stroke->gradientOffset()); // FIXME: Krita doesn't support dithering w.writeBoolean("Dthr", true/*stroke->dither()*/); } else if (stroke->fillType() == psd_fill_pattern) { w.writePatternRef("Ptrn", stroke->pattern(), fetchPatternUuidSafe(stroke->pattern(), patternToUuidMap)); w.writeUnitFloat("Scl ", "#Prc", stroke->scale()); w.writeBoolean("Lnkd", stroke->alignWithLayer()); w.writePhasePoint("phase", stroke->patternPhase()); } w.leaveDescriptor(); } w.leaveDescriptor(); w.leaveDescriptor(); } return w.document(); } inline QDomNode findNodeByClassId(const QString &classId, QDomNode parent) { return KisDomUtils::findElementByAttibute(parent, "node", "classId", classId); } void replaceAllChildren(QDomNode src, QDomNode dst) { QDomNode node; do { node = dst.lastChild(); dst.removeChild(node); } while(!node.isNull()); node = src.firstChild(); while(!node.isNull()) { dst.appendChild(node); node = src.firstChild(); } src.parentNode().removeChild(src); } QDomDocument KisAslLayerStyleSerializer::formPsdXmlDocument() const { QDomDocument doc = formXmlDocument(); QDomNode nullNode = findNodeByClassId("null", doc.documentElement()); QDomNode stylNode = findNodeByClassId("Styl", doc.documentElement()); QDomNode lefxNode = findNodeByClassId("Lefx", stylNode); replaceAllChildren(lefxNode, nullNode); return doc; } void KisAslLayerStyleSerializer::saveToDevice(QIODevice *device) { QDomDocument doc = formXmlDocument(); if (doc.isNull()) return ; KisAslWriter writer; writer.writeFile(device, doc); } void convertAndSetBlendMode(const QString &mode, boost::function setBlendMode) { QString compositeOp = COMPOSITE_OVER; if (mode == "Nrml") { compositeOp = COMPOSITE_OVER; } else if (mode == "Dslv") { compositeOp = COMPOSITE_DISSOLVE; } else if (mode == "Drkn") { compositeOp = COMPOSITE_DARKEN; } else if (mode == "Mltp") { compositeOp = COMPOSITE_MULT; } else if (mode == "CBrn") { compositeOp = COMPOSITE_BURN; } else if (mode == "linearBurn") { compositeOp = COMPOSITE_LINEAR_BURN; } else if (mode == "darkerColor") { compositeOp = COMPOSITE_DARKER_COLOR; } else if (mode == "Lghn") { compositeOp = COMPOSITE_LIGHTEN; } else if (mode == "Scrn") { compositeOp = COMPOSITE_SCREEN; } else if (mode == "CDdg") { compositeOp = COMPOSITE_DODGE; } else if (mode == "linearDodge") { compositeOp = COMPOSITE_LINEAR_DODGE; } else if (mode == "lighterColor") { compositeOp = COMPOSITE_LIGHTER_COLOR; } else if (mode == "Ovrl") { compositeOp = COMPOSITE_OVERLAY; } else if (mode == "SftL") { compositeOp = COMPOSITE_SOFT_LIGHT_PHOTOSHOP; } else if (mode == "HrdL") { compositeOp = COMPOSITE_HARD_LIGHT; } else if (mode == "vividLight") { compositeOp = COMPOSITE_VIVID_LIGHT; } else if (mode == "linearLight") { compositeOp = COMPOSITE_LINEAR_LIGHT; } else if (mode == "pinLight") { compositeOp = COMPOSITE_PIN_LIGHT; } else if (mode == "hardMix") { compositeOp = COMPOSITE_HARD_MIX_PHOTOSHOP; } else if (mode == "Dfrn") { compositeOp = COMPOSITE_DIFF; } else if (mode == "Xclu") { compositeOp = COMPOSITE_EXCLUSION; } else if (mode == "Sbtr") { compositeOp = COMPOSITE_SUBTRACT; } else if (mode == "divide") { compositeOp = COMPOSITE_DIVIDE; } else if (mode == "H ") { compositeOp = COMPOSITE_HUE; } else if (mode == "Strt") { compositeOp = COMPOSITE_SATURATION; } else if (mode == "Clr ") { compositeOp = COMPOSITE_COLOR; } else if (mode == "Lmns") { compositeOp = COMPOSITE_LUMINIZE; } else { dbgKrita << "Unknown blending mode:" << mode << "Returning COMPOSITE_OVER!"; } setBlendMode(compositeOp); } void convertAndSetCurve(const QString &name, const QVector &points, boost::function setCurveLookupTable) { Q_UNUSED(name); Q_UNUSED(points); Q_UNUSED(setCurveLookupTable); warnKrita << "convertAndSetBlendMode:" << "Curve conversion is not implemented yet"; } template void convertAndSetEnum(const QString &value, const QMap map, boost::function setMappedValue) { setMappedValue(map[value]); } inline QString _prepaddr(const QString &pref, const QString &addr) { return pref + addr; } #define CONN_TEXT_RADDR(addr, method, object, type) m_catcher.subscribeText(addr, std::bind(&type::method, object, _1)) #define CONN_COLOR(addr, method, object, type, prefix) m_catcher.subscribeColor(_prepaddr(prefix, addr), std::bind(&type::method, object, _1)) #define CONN_UNITF(addr, unit, method, object, type, prefix) m_catcher.subscribeUnitFloat(_prepaddr(prefix, addr), unit, std::bind(&type::method, object, _1)) #define CONN_BOOL(addr, method, object, type, prefix) m_catcher.subscribeBoolean(_prepaddr(prefix, addr), std::bind(&type::method, object, _1)) #define CONN_POINT(addr, method, object, type, prefix) m_catcher.subscribePoint(_prepaddr(prefix, addr), std::bind(&type::method, object, _1)) #define CONN_COMPOSITE_OP(addr, method, object, type, prefix) \ { \ boost::function setter = \ std::bind(&type::method, object, _1); \ m_catcher.subscribeEnum(_prepaddr(prefix, addr), "BlnM", std::bind(convertAndSetBlendMode, _1, setter)); \ } #define CONN_CURVE(addr, method, object, type, prefix) \ { \ boost::function setter = \ std::bind(&type::method, object, _1); \ m_catcher.subscribeCurve(_prepaddr(prefix, addr), std::bind(convertAndSetCurve, _1, _2, setter)); \ } #define CONN_ENUM(addr, tag, method, map, mapped_type, object, type, prefix) \ { \ boost::function setter = \ std::bind(&type::method, object, _1); \ m_catcher.subscribeEnum(_prepaddr(prefix, addr), tag, std::bind(convertAndSetEnum, _1, map, setter)); \ } #define CONN_GRADIENT(addr, method, object, type, prefix) \ { \ m_catcher.subscribeGradient(_prepaddr(prefix, addr), std::bind(&type::method, object, _1)); \ } #define CONN_PATTERN(addr, method, object, type, prefix) \ { \ boost::function setter = \ std::bind(&type::method, object, _1); \ m_catcher.subscribePatternRef(_prepaddr(prefix, addr), std::bind(&KisAslLayerStyleSerializer::assignPatternObject, this, _1, _2, setter)); \ } void KisAslLayerStyleSerializer::registerPatternObject(const KoPatternSP pattern, const QString& patternUuid) { if (m_patternsStore.contains(patternUuid)) { warnKrita << "WARNING: ASL style contains a duplicated pattern!" << ppVar(pattern->name()) << ppVar(m_patternsStore[patternUuid]->name()); } else { pattern->setFilename(patternUuid + QString("_pattern")); m_patternsStore.insert(patternUuid, pattern); } } void KisAslLayerStyleSerializer::assignPatternObject(const QString &patternUuid, const QString &patternName, boost::function setPattern) { Q_UNUSED(patternName); KoPatternSP pattern = m_patternsStore[patternUuid]; if (!pattern) { warnKrita << "WARNING: ASL style contains non-existent pattern reference! Searching for uuid: " << patternUuid << " (name: " << patternName << ")"; QImage dumbImage(32, 32, QImage::Format_ARGB32); dumbImage.fill(Qt::red); KoPatternSP dumbPattern(new KoPattern(dumbImage, "invalid", "")); registerPatternObject(dumbPattern, patternUuid + QString("_invalid")); pattern = dumbPattern; m_isValid = false; } setPattern(pattern); } class FillStylesCorrector { public: static void correct(KisPSDLayerStyle *style) { correctWithoutPattern(style->outerGlow()); correctWithoutPattern(style->innerGlow()); correctWithPattern(style->stroke()); } private: template static void correctWithPattern(T *config) { if (config->pattern()) { config->setFillType(psd_fill_pattern); } else if (config->gradient()) { config->setFillType(psd_fill_gradient); } else { config->setFillType(psd_fill_solid_color); } } template static void correctWithoutPattern(T *config) { if (config->gradient()) { config->setFillType(psd_fill_gradient); } else { config->setFillType(psd_fill_solid_color); } } }; void KisAslLayerStyleSerializer::connectCatcherToStyle(KisPSDLayerStyle *style, const QString &prefix) { CONN_TEXT_RADDR("/null/Nm ", setName, style, KisPSDLayerStyle); CONN_TEXT_RADDR("/null/Idnt", setPsdUuid, style, KisPSDLayerStyle); CONN_BOOL("/masterFXSwitch", setEnabled, style, KisPSDLayerStyle, prefix); psd_layer_effects_drop_shadow *dropShadow = style->dropShadow(); CONN_COMPOSITE_OP("/DrSh/Md ", setBlendMode, dropShadow, psd_layer_effects_drop_shadow, prefix); CONN_COLOR("/DrSh/Clr ", setColor, dropShadow, psd_layer_effects_drop_shadow, prefix); CONN_UNITF("/DrSh/Opct", "#Prc", setOpacity, dropShadow, psd_layer_effects_drop_shadow, prefix); CONN_UNITF("/DrSh/lagl", "#Ang", setAngle, dropShadow, psd_layer_effects_drop_shadow, prefix); CONN_UNITF("/DrSh/Dstn", "#Pxl", setDistance, dropShadow, psd_layer_effects_drop_shadow, prefix); CONN_UNITF("/DrSh/Ckmt", "#Pxl", setSpread, dropShadow, psd_layer_effects_drop_shadow, prefix); CONN_UNITF("/DrSh/blur", "#Pxl", setSize, dropShadow, psd_layer_effects_drop_shadow, prefix); CONN_UNITF("/DrSh/Nose", "#Prc", setNoise, dropShadow, psd_layer_effects_drop_shadow, prefix); CONN_BOOL("/DrSh/enab", setEffectEnabled, dropShadow, psd_layer_effects_drop_shadow, prefix); CONN_BOOL("/DrSh/uglg", setUseGlobalLight, dropShadow, psd_layer_effects_drop_shadow, prefix); CONN_BOOL("/DrSh/AntA", setAntiAliased, dropShadow, psd_layer_effects_drop_shadow, prefix); CONN_BOOL("/DrSh/layerConceals", setKnocksOut, dropShadow, psd_layer_effects_drop_shadow, prefix); CONN_CURVE("/DrSh/TrnS", setContourLookupTable, dropShadow, psd_layer_effects_drop_shadow, prefix); psd_layer_effects_inner_shadow *innerShadow = style->innerShadow(); CONN_COMPOSITE_OP("/IrSh/Md ", setBlendMode, innerShadow, psd_layer_effects_inner_shadow, prefix); CONN_COLOR("/IrSh/Clr ", setColor, innerShadow, psd_layer_effects_inner_shadow, prefix); CONN_UNITF("/IrSh/Opct", "#Prc", setOpacity, innerShadow, psd_layer_effects_inner_shadow, prefix); CONN_UNITF("/IrSh/lagl", "#Ang", setAngle, innerShadow, psd_layer_effects_inner_shadow, prefix); CONN_UNITF("/IrSh/Dstn", "#Pxl", setDistance, innerShadow, psd_layer_effects_inner_shadow, prefix); CONN_UNITF("/IrSh/Ckmt", "#Pxl", setSpread, innerShadow, psd_layer_effects_inner_shadow, prefix); CONN_UNITF("/IrSh/blur", "#Pxl", setSize, innerShadow, psd_layer_effects_inner_shadow, prefix); CONN_UNITF("/IrSh/Nose", "#Prc", setNoise, innerShadow, psd_layer_effects_inner_shadow, prefix); CONN_BOOL("/IrSh/enab", setEffectEnabled, innerShadow, psd_layer_effects_inner_shadow, prefix); CONN_BOOL("/IrSh/uglg", setUseGlobalLight, innerShadow, psd_layer_effects_inner_shadow, prefix); CONN_BOOL("/IrSh/AntA", setAntiAliased, innerShadow, psd_layer_effects_inner_shadow, prefix); CONN_CURVE("/IrSh/TrnS", setContourLookupTable, innerShadow, psd_layer_effects_inner_shadow, prefix); psd_layer_effects_outer_glow *outerGlow = style->outerGlow(); CONN_COMPOSITE_OP("/OrGl/Md ", setBlendMode, outerGlow, psd_layer_effects_outer_glow, prefix); CONN_COLOR("/OrGl/Clr ", setColor, outerGlow, psd_layer_effects_outer_glow, prefix); CONN_UNITF("/OrGl/Opct", "#Prc", setOpacity, outerGlow, psd_layer_effects_outer_glow, prefix); CONN_UNITF("/OrGl/Ckmt", "#Pxl", setSpread, outerGlow, psd_layer_effects_outer_glow, prefix); CONN_UNITF("/OrGl/blur", "#Pxl", setSize, outerGlow, psd_layer_effects_outer_glow, prefix); CONN_UNITF("/OrGl/Nose", "#Prc", setNoise, outerGlow, psd_layer_effects_outer_glow, prefix); CONN_BOOL("/OrGl/enab", setEffectEnabled, outerGlow, psd_layer_effects_outer_glow, prefix); CONN_BOOL("/OrGl/AntA", setAntiAliased, outerGlow, psd_layer_effects_outer_glow, prefix); CONN_CURVE("/OrGl/TrnS", setContourLookupTable, outerGlow, psd_layer_effects_outer_glow, prefix); QMap fillTechniqueMap; fillTechniqueMap.insert("PrBL", psd_technique_precise); fillTechniqueMap.insert("SfBL", psd_technique_softer); CONN_ENUM("/OrGl/GlwT", "BETE", setTechnique, fillTechniqueMap, psd_technique_type, outerGlow, psd_layer_effects_outer_glow, prefix); CONN_GRADIENT("/OrGl/Grad", setGradient, outerGlow, psd_layer_effects_outer_glow, prefix); CONN_UNITF("/OrGl/Inpr", "#Prc", setRange, outerGlow, psd_layer_effects_outer_glow, prefix); CONN_UNITF("/OrGl/ShdN", "#Prc", setJitter, outerGlow, psd_layer_effects_outer_glow, prefix); psd_layer_effects_inner_glow *innerGlow = style->innerGlow(); CONN_COMPOSITE_OP("/IrGl/Md ", setBlendMode, innerGlow, psd_layer_effects_inner_glow, prefix); CONN_COLOR("/IrGl/Clr ", setColor, innerGlow, psd_layer_effects_inner_glow, prefix); CONN_UNITF("/IrGl/Opct", "#Prc", setOpacity, innerGlow, psd_layer_effects_inner_glow, prefix); CONN_UNITF("/IrGl/Ckmt", "#Pxl", setSpread, innerGlow, psd_layer_effects_inner_glow, prefix); CONN_UNITF("/IrGl/blur", "#Pxl", setSize, innerGlow, psd_layer_effects_inner_glow, prefix); CONN_UNITF("/IrGl/Nose", "#Prc", setNoise, innerGlow, psd_layer_effects_inner_glow, prefix); CONN_BOOL("/IrGl/enab", setEffectEnabled, innerGlow, psd_layer_effects_inner_glow, prefix); CONN_BOOL("/IrGl/AntA", setAntiAliased, innerGlow, psd_layer_effects_inner_glow, prefix); CONN_CURVE("/IrGl/TrnS", setContourLookupTable, innerGlow, psd_layer_effects_inner_glow, prefix); CONN_ENUM("/IrGl/GlwT", "BETE", setTechnique, fillTechniqueMap, psd_technique_type, innerGlow, psd_layer_effects_inner_glow, prefix); CONN_GRADIENT("/IrGl/Grad", setGradient, innerGlow, psd_layer_effects_inner_glow, prefix); CONN_UNITF("/IrGl/Inpr", "#Prc", setRange, innerGlow, psd_layer_effects_inner_glow, prefix); CONN_UNITF("/IrGl/ShdN", "#Prc", setJitter, innerGlow, psd_layer_effects_inner_glow, prefix); QMap glowSourceMap; glowSourceMap.insert("SrcC", psd_glow_center); glowSourceMap.insert("SrcE", psd_glow_edge); CONN_ENUM("/IrGl/glwS", "IGSr", setSource, glowSourceMap, psd_glow_source, innerGlow, psd_layer_effects_inner_glow, prefix); psd_layer_effects_satin *satin = style->satin(); CONN_COMPOSITE_OP("/ChFX/Md ", setBlendMode, satin, psd_layer_effects_satin, prefix); CONN_COLOR("/ChFX/Clr ", setColor, satin, psd_layer_effects_satin, prefix); CONN_UNITF("/ChFX/Opct", "#Prc", setOpacity, satin, psd_layer_effects_satin, prefix); CONN_UNITF("/ChFX/lagl", "#Ang", setAngle, satin, psd_layer_effects_satin, prefix); CONN_UNITF("/ChFX/Dstn", "#Pxl", setDistance, satin, psd_layer_effects_satin, prefix); CONN_UNITF("/ChFX/blur", "#Pxl", setSize, satin, psd_layer_effects_satin, prefix); CONN_BOOL("/ChFX/enab", setEffectEnabled, satin, psd_layer_effects_satin, prefix); CONN_BOOL("/ChFX/AntA", setAntiAliased, satin, psd_layer_effects_satin, prefix); CONN_BOOL("/ChFX/Invr", setInvert, satin, psd_layer_effects_satin, prefix); CONN_CURVE("/ChFX/MpgS", setContourLookupTable, satin, psd_layer_effects_satin, prefix); psd_layer_effects_color_overlay *colorOverlay = style->colorOverlay(); CONN_COMPOSITE_OP("/SoFi/Md ", setBlendMode, colorOverlay, psd_layer_effects_color_overlay, prefix); CONN_COLOR("/SoFi/Clr ", setColor, colorOverlay, psd_layer_effects_color_overlay, prefix); CONN_UNITF("/SoFi/Opct", "#Prc", setOpacity, colorOverlay, psd_layer_effects_color_overlay, prefix); CONN_BOOL("/SoFi/enab", setEffectEnabled, colorOverlay, psd_layer_effects_color_overlay, prefix); psd_layer_effects_gradient_overlay *gradientOverlay = style->gradientOverlay(); CONN_COMPOSITE_OP("/GrFl/Md ", setBlendMode, gradientOverlay, psd_layer_effects_gradient_overlay, prefix); CONN_UNITF("/GrFl/Opct", "#Prc", setOpacity, gradientOverlay, psd_layer_effects_gradient_overlay, prefix); CONN_UNITF("/GrFl/Scl ", "#Prc", setScale, gradientOverlay, psd_layer_effects_gradient_overlay, prefix); CONN_UNITF("/GrFl/Angl", "#Ang", setAngle, gradientOverlay, psd_layer_effects_gradient_overlay, prefix); CONN_BOOL("/GrFl/enab", setEffectEnabled, gradientOverlay, psd_layer_effects_gradient_overlay, prefix); // CONN_BOOL("/GrFl/Dthr", setDitherNotImplemented, gradientOverlay, psd_layer_effects_gradient_overlay, prefix); CONN_BOOL("/GrFl/Rvrs", setReverse, gradientOverlay, psd_layer_effects_gradient_overlay, prefix); CONN_BOOL("/GrFl/Algn", setAlignWithLayer, gradientOverlay, psd_layer_effects_gradient_overlay, prefix); CONN_POINT("/GrFl/Ofst", setGradientOffset, gradientOverlay, psd_layer_effects_gradient_overlay, prefix); CONN_GRADIENT("/GrFl/Grad", setGradient, gradientOverlay, psd_layer_effects_gradient_overlay, prefix); QMap gradientStyleMap; gradientStyleMap.insert("Lnr ", psd_gradient_style_linear); gradientStyleMap.insert("Rdl ", psd_gradient_style_radial); gradientStyleMap.insert("Angl", psd_gradient_style_angle); gradientStyleMap.insert("Rflc", psd_gradient_style_reflected); gradientStyleMap.insert("Dmnd", psd_gradient_style_diamond); CONN_ENUM("/GrFl/Type", "GrdT", setStyle, gradientStyleMap, psd_gradient_style, gradientOverlay, psd_layer_effects_gradient_overlay, prefix); psd_layer_effects_pattern_overlay *patternOverlay = style->patternOverlay(); CONN_BOOL("/patternFill/enab", setEffectEnabled, patternOverlay, psd_layer_effects_pattern_overlay, prefix); CONN_COMPOSITE_OP("/patternFill/Md ", setBlendMode, patternOverlay, psd_layer_effects_pattern_overlay, prefix); CONN_UNITF("/patternFill/Opct", "#Prc", setOpacity, patternOverlay, psd_layer_effects_pattern_overlay, prefix); CONN_PATTERN("/patternFill/Ptrn", setPattern, patternOverlay, psd_layer_effects_pattern_overlay, prefix); CONN_UNITF("/patternFill/Scl ", "#Prc", setScale, patternOverlay, psd_layer_effects_pattern_overlay, prefix); CONN_BOOL("/patternFill/Algn", setAlignWithLayer, patternOverlay, psd_layer_effects_pattern_overlay, prefix); CONN_POINT("/patternFill/phase", setPatternPhase, patternOverlay, psd_layer_effects_pattern_overlay, prefix); psd_layer_effects_stroke *stroke = style->stroke(); CONN_COMPOSITE_OP("/FrFX/Md ", setBlendMode, stroke, psd_layer_effects_stroke, prefix); CONN_BOOL("/FrFX/enab", setEffectEnabled, stroke, psd_layer_effects_stroke, prefix); CONN_UNITF("/FrFX/Opct", "#Prc", setOpacity, stroke, psd_layer_effects_stroke, prefix); CONN_UNITF("/FrFX/Sz ", "#Pxl", setSize, stroke, psd_layer_effects_stroke, prefix); QMap strokeStyleMap; strokeStyleMap.insert("OutF", psd_stroke_outside); strokeStyleMap.insert("InsF", psd_stroke_inside); strokeStyleMap.insert("CtrF", psd_stroke_center); CONN_ENUM("/FrFX/Styl", "FStl", setPosition, strokeStyleMap, psd_stroke_position, stroke, psd_layer_effects_stroke, prefix); QMap strokeFillType; strokeFillType.insert("SClr", psd_fill_solid_color); strokeFillType.insert("GrFl", psd_fill_gradient); strokeFillType.insert("Ptrn", psd_fill_pattern); CONN_ENUM("/FrFX/PntT", "FrFl", setFillType, strokeFillType, psd_fill_type, stroke, psd_layer_effects_stroke, prefix); // Color type CONN_COLOR("/FrFX/Clr ", setColor, stroke, psd_layer_effects_stroke, prefix); // Gradient Type CONN_GRADIENT("/FrFX/Grad", setGradient, stroke, psd_layer_effects_stroke, prefix); CONN_UNITF("/FrFX/Angl", "#Ang", setAngle, stroke, psd_layer_effects_stroke, prefix); CONN_UNITF("/FrFX/Scl ", "#Prc", setScale, stroke, psd_layer_effects_stroke, prefix); CONN_ENUM("/FrFX/Type", "GrdT", setStyle, gradientStyleMap, psd_gradient_style, stroke, psd_layer_effects_stroke, prefix); CONN_BOOL("/FrFX/Rvrs", setReverse, stroke, psd_layer_effects_stroke, prefix); CONN_BOOL("/FrFX/Algn", setAlignWithLayer, stroke, psd_layer_effects_stroke, prefix); CONN_POINT("/FrFX/Ofst", setGradientOffset, stroke, psd_layer_effects_stroke, prefix); // CONN_BOOL("/FrFX/Dthr", setDitherNotImplemented, stroke, psd_layer_effects_stroke, prefix); // Pattern type CONN_PATTERN("/FrFX/Ptrn", setPattern, stroke, psd_layer_effects_stroke, prefix); CONN_BOOL("/FrFX/Lnkd", setAlignWithLayer, stroke, psd_layer_effects_stroke, prefix); // yes, we share the params... CONN_POINT("/FrFX/phase", setPatternPhase, stroke, psd_layer_effects_stroke, prefix); psd_layer_effects_bevel_emboss *bevelAndEmboss = style->bevelAndEmboss(); CONN_BOOL("/ebbl/enab", setEffectEnabled, bevelAndEmboss, psd_layer_effects_bevel_emboss, prefix); CONN_COMPOSITE_OP("/ebbl/hglM", setHighlightBlendMode, bevelAndEmboss, psd_layer_effects_bevel_emboss, prefix); CONN_COLOR("/ebbl/hglC", setHighlightColor, bevelAndEmboss, psd_layer_effects_bevel_emboss, prefix); CONN_UNITF("/ebbl/hglO", "#Prc", setHighlightOpacity, bevelAndEmboss, psd_layer_effects_bevel_emboss, prefix); CONN_COMPOSITE_OP("/ebbl/sdwM", setShadowBlendMode, bevelAndEmboss, psd_layer_effects_bevel_emboss, prefix); CONN_COLOR("/ebbl/sdwC", setShadowColor, bevelAndEmboss, psd_layer_effects_bevel_emboss, prefix); CONN_UNITF("/ebbl/sdwO", "#Prc", setShadowOpacity, bevelAndEmboss, psd_layer_effects_bevel_emboss, prefix); QMap bevelTechniqueMap; bevelTechniqueMap.insert("PrBL", psd_technique_precise); bevelTechniqueMap.insert("SfBL", psd_technique_softer); bevelTechniqueMap.insert("Slmt", psd_technique_slope_limit); CONN_ENUM("/ebbl/bvlT", "bvlT", setTechnique, bevelTechniqueMap, psd_technique_type, bevelAndEmboss, psd_layer_effects_bevel_emboss, prefix); QMap bevelStyleMap; bevelStyleMap.insert("OtrB", psd_bevel_outer_bevel); bevelStyleMap.insert("InrB", psd_bevel_inner_bevel); bevelStyleMap.insert("Embs", psd_bevel_emboss); bevelStyleMap.insert("PlEb", psd_bevel_pillow_emboss); bevelStyleMap.insert("strokeEmboss", psd_bevel_stroke_emboss); CONN_ENUM("/ebbl/bvlS", "BESl", setStyle, bevelStyleMap, psd_bevel_style, bevelAndEmboss, psd_layer_effects_bevel_emboss, prefix); CONN_BOOL("/ebbl/uglg", setUseGlobalLight, bevelAndEmboss, psd_layer_effects_bevel_emboss, prefix); CONN_UNITF("/ebbl/lagl", "#Ang", setAngle, bevelAndEmboss, psd_layer_effects_bevel_emboss, prefix); CONN_UNITF("/ebbl/Lald", "#Ang", setAltitude, bevelAndEmboss, psd_layer_effects_bevel_emboss, prefix); CONN_UNITF("/ebbl/srgR", "#Prc", setDepth, bevelAndEmboss, psd_layer_effects_bevel_emboss, prefix); CONN_UNITF("/ebbl/blur", "#Pxl", setSize, bevelAndEmboss, psd_layer_effects_bevel_emboss, prefix); QMap bevelDirectionMap; bevelDirectionMap.insert("In ", psd_direction_up); bevelDirectionMap.insert("Out ", psd_direction_down); CONN_ENUM("/ebbl/bvlD", "BESs", setDirection, bevelDirectionMap, psd_direction, bevelAndEmboss, psd_layer_effects_bevel_emboss, prefix); CONN_CURVE("/ebbl/TrnS", setContourLookupTable, bevelAndEmboss, psd_layer_effects_bevel_emboss, prefix); CONN_BOOL("/ebbl/antialiasGloss", setGlossAntiAliased, bevelAndEmboss, psd_layer_effects_bevel_emboss, prefix); CONN_UNITF("/ebbl/Sftn", "#Pxl", setSoften, bevelAndEmboss, psd_layer_effects_bevel_emboss, prefix); // Use shape mode CONN_BOOL("/ebbl/useShape", setContourEnabled, bevelAndEmboss, psd_layer_effects_bevel_emboss, prefix); CONN_CURVE("/ebbl/MpgS", setGlossContourLookupTable, bevelAndEmboss, psd_layer_effects_bevel_emboss, prefix); CONN_BOOL("/ebbl/AntA", setAntiAliased, bevelAndEmboss, psd_layer_effects_bevel_emboss, prefix); CONN_UNITF("/ebbl/Inpr", "#Prc", setContourRange, bevelAndEmboss, psd_layer_effects_bevel_emboss, prefix); // Use texture mode CONN_BOOL("/ebbl/useTexture", setTextureEnabled, bevelAndEmboss, psd_layer_effects_bevel_emboss, prefix); CONN_BOOL("/ebbl/InvT", setTextureInvert, bevelAndEmboss, psd_layer_effects_bevel_emboss, prefix); CONN_BOOL("/ebbl/Algn", setTextureAlignWithLayer, bevelAndEmboss, psd_layer_effects_bevel_emboss, prefix); CONN_UNITF("/ebbl/Scl ", "#Prc", setTextureScale, bevelAndEmboss, psd_layer_effects_bevel_emboss, prefix); CONN_UNITF("/ebbl/textureDepth", "#Prc", setTextureDepth, bevelAndEmboss, psd_layer_effects_bevel_emboss, prefix); CONN_PATTERN("/ebbl/Ptrn", setTexturePattern, bevelAndEmboss, psd_layer_effects_bevel_emboss, prefix); CONN_POINT("/ebbl/phase", setTexturePhase, bevelAndEmboss, psd_layer_effects_bevel_emboss, prefix); } void KisAslLayerStyleSerializer::newStyleStarted(bool isPsdStructure) { m_stylesVector.append(toQShared(new KisPSDLayerStyle())); KisPSDLayerStyleSP currentStyleSP = m_stylesVector.last(); KisPSDLayerStyle *currentStyle = currentStyleSP.data(); psd_layer_effects_context *context = currentStyleSP->context(); context->keep_original = 0; QString prefix = isPsdStructure ? "/null" : "/Styl/Lefx"; connectCatcherToStyle(currentStyle, prefix); } bool KisAslLayerStyleSerializer::readFromFile(const QString& filename) { QFile file(filename); if (file.size() == 0) return false; if (!file.open(QIODevice::ReadOnly)) { dbgKrita << "Can't open file " << filename; return false; } readFromDevice(&file); m_initialized = true; file.close(); return true; } void KisAslLayerStyleSerializer::readFromDevice(QIODevice *device) { m_stylesVector.clear(); m_catcher.subscribePattern("/patterns/KisPattern", std::bind(&KisAslLayerStyleSerializer::registerPatternObject, this, _1, _2)); m_catcher.subscribePattern("/Patterns/KisPattern", std::bind(&KisAslLayerStyleSerializer::registerPatternObject, this, _1, _2)); m_catcher.subscribeNewStyleStarted(std::bind(&KisAslLayerStyleSerializer::newStyleStarted, this, false)); KisAslReader reader; QDomDocument doc = reader.readFile(device); //dbgKrita << ppVar(doc.toString()); //KisAslObjectCatcher c2; KisAslXmlParser parser; parser.parseXML(doc, m_catcher); // correct all the layer styles Q_FOREACH (KisPSDLayerStyleSP style, m_stylesVector) { FillStylesCorrector::correct(style.data()); style->setValid(!style->isEmpty()); style->setFilename(style->psdUuid() + QString("_style")); } m_initialized = true; } void KisAslLayerStyleSerializer::registerPSDPattern(const QDomDocument &doc) { KisAslCallbackObjectCatcher catcher; catcher.subscribePattern("/Patterns/KisPattern", std::bind(&KisAslLayerStyleSerializer::registerPatternObject, this, _1, _2)); catcher.subscribePattern("/patterns/KisPattern", std::bind(&KisAslLayerStyleSerializer::registerPatternObject, this, _1, _2)); //KisAslObjectCatcher c2; KisAslXmlParser parser; parser.parseXML(doc, catcher); } void KisAslLayerStyleSerializer::readFromPSDXML(const QDomDocument &doc) { // The caller prepares the document using th efollowing code // // KisAslReader reader; // QDomDocument doc = reader.readLfx2PsdSection(device); m_stylesVector.clear(); //m_catcher.subscribePattern("/Patterns/KisPattern", std::bind(&KisAslLayerStyleSerializer::registerPatternObject, this, _1)); m_catcher.subscribeNewStyleStarted(std::bind(&KisAslLayerStyleSerializer::newStyleStarted, this, true)); //KisAslObjectCatcher c2; KisAslXmlParser parser; parser.parseXML(doc, m_catcher); // correct all the layer styles Q_FOREACH (KisPSDLayerStyleSP style, m_stylesVector) { FillStylesCorrector::correct(style.data()); } } diff --git a/libs/image/kis_base_node.cpp b/libs/image/kis_base_node.cpp index ffd4f15ea3..4f96a61467 100644 --- a/libs/image/kis_base_node.cpp +++ b/libs/image/kis_base_node.cpp @@ -1,501 +1,504 @@ /* * 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_base_node.h" #include #include #include #include #include #include #include "kis_paint_device.h" #include "kis_layer_properties_icons.h" #include "kis_scalar_keyframe_channel.h" struct Q_DECL_HIDDEN KisBaseNode::Private { QString compositeOp; KoProperties properties; KisBaseNode::Property hack_visible; //HACK QUuid id; QMap keyframeChannels; QScopedPointer opacityChannel; bool systemLocked; bool collapsed; bool supportsLodMoves; bool animated; bool pinnedToTimeline; KisImageWSP image; Private(KisImageWSP image) : id(QUuid::createUuid()) , systemLocked(false) , collapsed(false) , supportsLodMoves(false) , animated(false) , pinnedToTimeline(false) , image(image) { } Private(const Private &rhs) : compositeOp(rhs.compositeOp), id(QUuid::createUuid()), systemLocked(false), collapsed(rhs.collapsed), supportsLodMoves(rhs.supportsLodMoves), animated(rhs.animated), pinnedToTimeline(rhs.pinnedToTimeline), image(rhs.image) { QMapIterator iter = rhs.properties.propertyIterator(); while (iter.hasNext()) { iter.next(); properties.setProperty(iter.key(), iter.value()); } } }; KisBaseNode::KisBaseNode(KisImageWSP image) : m_d(new Private(image)) { /** * Be cautious! These two calls are vital to warm-up KoProperties. * We use it and its QMap in a threaded environment. This is not * officially supported by Qt, but our environment guarantees, that * there will be the only writer and several readers. Whilst the * value of the QMap is boolean and there are no implicit-sharing * calls provocated, it is safe to work with it in such an * environment. */ setVisible(true, true); setUserLocked(false); setCollapsed(false); setSupportsLodMoves(true); m_d->compositeOp = COMPOSITE_OVER; } KisBaseNode::KisBaseNode(const KisBaseNode & rhs) : QObject() , KisShared() , m_d(new Private(*rhs.m_d)) { if (rhs.m_d->keyframeChannels.size() > 0) { Q_FOREACH(QString key, rhs.m_d->keyframeChannels.keys()) { KisKeyframeChannel* channel = rhs.m_d->keyframeChannels.value(key); if (!channel) { continue; } if (channel->inherits("KisScalarKeyframeChannel")) { KisScalarKeyframeChannel* pchannel = qobject_cast(channel); KIS_ASSERT_RECOVER(pchannel) { continue; } KisScalarKeyframeChannel* channelNew = new KisScalarKeyframeChannel(*pchannel, nullptr); KIS_ASSERT(channelNew); m_d->keyframeChannels.insert(channelNew->id(), channelNew); if (KoID(key) == KisKeyframeChannel::Opacity) { m_d->opacityChannel.reset(channelNew); } } } } } KisBaseNode::~KisBaseNode() { delete m_d; } KisPaintDeviceSP KisBaseNode::colorPickSourceDevice() const { return projection(); } quint8 KisBaseNode::opacity() const { if (m_d->opacityChannel) { qreal value = m_d->opacityChannel->currentValue(); if (!qIsNaN(value)) { return value; } } return nodeProperties().intProperty("opacity", OPACITY_OPAQUE_U8); } void KisBaseNode::setOpacity(quint8 val) { if (m_d->opacityChannel) { KisKeyframeSP activeKeyframe = m_d->opacityChannel->currentlyActiveKeyframe(); if (activeKeyframe) { m_d->opacityChannel->setScalarValue(activeKeyframe, val); } } if (opacity() == val) return; setNodeProperty("opacity", val); baseNodeInvalidateAllFramesCallback(); } quint8 KisBaseNode::percentOpacity() const { return int(float(opacity() * 100) / 255 + 0.5); } void KisBaseNode::setPercentOpacity(quint8 val) { setOpacity(int(float(val * 255) / 100 + 0.5)); } const QString& KisBaseNode::compositeOpId() const { return m_d->compositeOp; } void KisBaseNode::setCompositeOpId(const QString& compositeOp) { if (m_d->compositeOp == compositeOp) return; m_d->compositeOp = compositeOp; baseNodeChangedCallback(); baseNodeInvalidateAllFramesCallback(); } KisBaseNode::PropertyList KisBaseNode::sectionModelProperties() const { KisBaseNode::PropertyList l; l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::visible, visible(), m_d->hack_visible.isInStasis, m_d->hack_visible.stateInStasis); l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::locked, userLocked()); return l; } void KisBaseNode::setSectionModelProperties(const KisBaseNode::PropertyList &properties) { setVisible(properties.at(0).state.toBool()); m_d->hack_visible = properties.at(0); setUserLocked(properties.at(1).state.toBool()); } const KoProperties & KisBaseNode::nodeProperties() const { return m_d->properties; } void KisBaseNode::setNodeProperty(const QString & name, const QVariant & value) { m_d->properties.setProperty(name, value); baseNodeChangedCallback(); } void KisBaseNode::mergeNodeProperties(const KoProperties & properties) { QMapIterator iter = properties.propertyIterator(); while (iter.hasNext()) { iter.next(); m_d->properties.setProperty(iter.key(), iter.value()); } baseNodeChangedCallback(); baseNodeInvalidateAllFramesCallback(); } bool KisBaseNode::check(const KoProperties & properties) const { QMapIterator iter = properties.propertyIterator(); while (iter.hasNext()) { iter.next(); if (m_d->properties.contains(iter.key())) { if (m_d->properties.value(iter.key()) != iter.value()) return false; } } return true; } -QImage KisBaseNode::createThumbnail(qint32 w, qint32 h) +QImage KisBaseNode::createThumbnail(qint32 w, qint32 h, Qt::AspectRatioMode aspectRatioMode) { + Q_UNUSED(aspectRatioMode); + try { QImage image(w, h, QImage::Format_ARGB32); image.fill(0); return image; } catch (const std::bad_alloc&) { return QImage(); } } -QImage KisBaseNode::createThumbnailForFrame(qint32 w, qint32 h, int time) +QImage KisBaseNode::createThumbnailForFrame(qint32 w, qint32 h, int time, Qt::AspectRatioMode aspectRatioMode) { Q_UNUSED(time) + Q_UNUSED(aspectRatioMode); return createThumbnail(w, h); } bool KisBaseNode::visible(bool recursive) const { bool isVisible = m_d->properties.boolProperty(KisLayerPropertiesIcons::visible.id(), true); KisBaseNodeSP parentNode = parentCallback(); return recursive && isVisible && parentNode ? parentNode->visible(recursive) : isVisible; } void KisBaseNode::setVisible(bool visible, bool loading) { const bool isVisible = m_d->properties.boolProperty(KisLayerPropertiesIcons::visible.id(), true); if (!loading && isVisible == visible) return; m_d->properties.setProperty(KisLayerPropertiesIcons::visible.id(), visible); notifyParentVisibilityChanged(visible); if (!loading) { baseNodeChangedCallback(); baseNodeInvalidateAllFramesCallback(); } } bool KisBaseNode::userLocked() const { return m_d->properties.boolProperty(KisLayerPropertiesIcons::locked.id(), false); } bool KisBaseNode::belongsToIsolatedGroup() const { if (!m_d->image) { return false; } const KisBaseNode* element = this; while (element) { if (element->isIsolatedRoot()) { return true; } else { element = element->parentCallback().data(); } } return false; } bool KisBaseNode::isIsolatedRoot() const { if (!m_d->image) { return false; } const KisBaseNode* isolatedRoot = m_d->image->isolatedModeRoot().data(); return (this == isolatedRoot); } void KisBaseNode::setUserLocked(bool locked) { const bool isLocked = m_d->properties.boolProperty(KisLayerPropertiesIcons::locked.id(), true); if (isLocked == locked) return; m_d->properties.setProperty(KisLayerPropertiesIcons::locked.id(), locked); baseNodeChangedCallback(); } bool KisBaseNode::isEditable(bool checkVisibility) const { bool editable = true; if (checkVisibility) { editable = ((visible(false) || belongsToIsolatedGroup()) && !userLocked()); } else { editable = (!userLocked()); } if (editable) { KisBaseNodeSP parentNode = parentCallback(); if (parentNode && parentNode != this) { editable = parentNode->isEditable(checkVisibility); } } return editable; } bool KisBaseNode::hasEditablePaintDevice() const { return paintDevice() && isEditable(); } void KisBaseNode::setCollapsed(bool collapsed) { const bool oldCollapsed = m_d->collapsed; m_d->collapsed = collapsed; if (oldCollapsed != collapsed) { baseNodeCollapsedChangedCallback(); } } bool KisBaseNode::collapsed() const { return m_d->collapsed; } void KisBaseNode::setColorLabelIndex(int index) { const int currentLabel = colorLabelIndex(); if (currentLabel == index) return; m_d->properties.setProperty(KisLayerPropertiesIcons::colorLabelIndex.id(), index); baseNodeChangedCallback(); } int KisBaseNode::colorLabelIndex() const { return m_d->properties.intProperty(KisLayerPropertiesIcons::colorLabelIndex.id(), 0); } QUuid KisBaseNode::uuid() const { return m_d->id; } void KisBaseNode::setUuid(const QUuid& id) { m_d->id = id; baseNodeChangedCallback(); } bool KisBaseNode::supportsLodMoves() const { return m_d->supportsLodMoves; } void KisBaseNode::setImage(KisImageWSP image) { m_d->image = image; } KisImageWSP KisBaseNode::image() const { return m_d->image; } bool KisBaseNode::isFakeNode() const { return false; } void KisBaseNode::setSupportsLodMoves(bool value) { m_d->supportsLodMoves = value; } QMap KisBaseNode::keyframeChannels() const { return m_d->keyframeChannels; } KisKeyframeChannel * KisBaseNode::getKeyframeChannel(const QString &id) const { QMap::const_iterator i = m_d->keyframeChannels.constFind(id); if (i == m_d->keyframeChannels.constEnd()) { return 0; } return i.value(); } bool KisBaseNode::isPinnedToTimeline() const { return m_d->pinnedToTimeline; } void KisBaseNode::setPinnedToTimeline(bool pinned) { if (pinned == m_d->pinnedToTimeline) return; m_d->pinnedToTimeline = pinned; baseNodeChangedCallback(); } KisKeyframeChannel * KisBaseNode::getKeyframeChannel(const QString &id, bool create) { KisKeyframeChannel *channel = getKeyframeChannel(id); if (!channel && create) { channel = requestKeyframeChannel(id); if (channel) { addKeyframeChannel(channel); } } return channel; } bool KisBaseNode::isAnimated() const { return m_d->animated; } void KisBaseNode::enableAnimation() { m_d->animated = true; baseNodeChangedCallback(); } void KisBaseNode::addKeyframeChannel(KisKeyframeChannel *channel) { m_d->keyframeChannels.insert(channel->id(), channel); emit keyframeChannelAdded(channel); } KisKeyframeChannel *KisBaseNode::requestKeyframeChannel(const QString &id) { if (id == KisKeyframeChannel::Opacity.id()) { Q_ASSERT(m_d->opacityChannel.isNull()); KisPaintDeviceSP device = original(); if (device) { KisNode* node = dynamic_cast(this); KisScalarKeyframeChannel * channel = new KisScalarKeyframeChannel( KisKeyframeChannel::Opacity, 0, 255, KisNodeWSP( node ), KisKeyframe::Linear ); m_d->opacityChannel.reset(channel); return channel; } } return 0; } diff --git a/libs/image/kis_base_node.h b/libs/image/kis_base_node.h index 818804ade7..4213ef73d8 100644 --- a/libs/image/kis_base_node.h +++ b/libs/image/kis_base_node.h @@ -1,608 +1,608 @@ /* * 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_BASE_NODE_H #define _KIS_BASE_NODE_H #include #include #include #include #include #include "kis_shared.h" #include "kis_paint_device.h" #include "kis_processing_visitor.h" // included, not forward declared for msvc class KoProperties; class KoColorSpace; class KoCompositeOp; class KisNodeVisitor; class KisUndoAdapter; class KisKeyframeChannel; #include "kritaimage_export.h" /** * A KisBaseNode is the base class for all components of an image: * nodes, layers masks, selections. A node has a number of properties, * can be represented as a thumbnail and knows what to do when it gets * a certain paint device to process. A KisBaseNode does not know * anything about its peers. You should not directly inherit from a * KisBaseNode; inherit from KisNode instead. */ class KRITAIMAGE_EXPORT KisBaseNode : public QObject, public KisShared { Q_OBJECT public: /** * Describes a property of a document section. * * FIXME: using a QList instead of QMap and not having an untranslated identifier, * either enum or string, forces applications to rely on the order of properties * or to compare the translated strings. This makes it hard to robustly extend the * properties of document section items. */ struct Property { QString id; /** i18n-ed name, suitable for displaying */ QString name; /** Whether the property is a boolean (e.g. locked, visible) which can be toggled directly from the widget itself. */ bool isMutable; /** Provide these if the property isMutable. */ QIcon onIcon; QIcon offIcon; /** If the property isMutable, provide a boolean. Otherwise, a string suitable for displaying. */ QVariant state; /** If the property is mutable, specifies whether it can be put into stasis. When a property is in stasis, a new state is created, and the old one is stored in stateInStasis. When stasis ends, the old value is restored and the new one discarded */ bool canHaveStasis; /** If the property isMutable and canHaveStasis, indicate whether it is in stasis or not */ bool isInStasis; /** If the property isMutable and canHaveStasis, provide this value to store the property's state while in stasis */ bool stateInStasis; bool operator==(const Property &rhs) const { return rhs.name == name && rhs.state == state && isInStasis == rhs.isInStasis; } Property(): isMutable( false ), isInStasis(false) { } /// Constructor for a mutable property. Property( const KoID &n, const QIcon &on, const QIcon &off, bool isOn ) : id(n.id()), name( n.name() ), isMutable( true ), onIcon( on ), offIcon( off ), state( isOn ), canHaveStasis( false ), isInStasis(false) { } /** Constructor for a mutable property accepting stasis */ Property( const KoID &n, const QIcon &on, const QIcon &off, bool isOn, bool _isInStasis, bool _stateInStasis = false ) : id(n.id()), name(n.name()), isMutable( true ), onIcon( on ), offIcon( off ), state( isOn ), canHaveStasis( true ), isInStasis( _isInStasis ), stateInStasis( _stateInStasis ) { } /// Constructor for a nonmutable property. Property( const KoID &n, const QString &s ) : id(n.id()), name(n.name()), isMutable( false ), state( s ), isInStasis(false) { } }; /** Return this type for PropertiesRole. */ typedef QList PropertyList; public: /** * Create a new, empty base node. The node is unnamed, unlocked * visible and unlinked. */ KisBaseNode(KisImageWSP image); /** * Create a copy of this node. */ KisBaseNode(const KisBaseNode & rhs); /** * Delete this node */ ~KisBaseNode() override; /** * Return the paintdevice you can use to change pixels on. For a * paint layer these will be paint pixels, for an adjustment layer or a mask * the selection paint device. * * @return the paint device to paint on. Can be 0 if the actual * node type does not support painting. */ virtual KisPaintDeviceSP paintDevice() const = 0; /** * @return the rendered representation of a node * before the effect masks have had their go at it. Can be 0. */ virtual KisPaintDeviceSP original() const = 0; /** * @return the fully rendered representation of this layer: its * rendered original and its effect masks. Can be 0. */ virtual KisPaintDeviceSP projection() const = 0; /** * @return a special device from where the color picker tool should pick * color when in layer-only mode. For most of the nodes just shortcuts * to projection() device. TODO: can it be null? */ virtual KisPaintDeviceSP colorPickSourceDevice() const; virtual const KoColorSpace *colorSpace() const = 0; /** * Return the opacity of this layer, scaled to a range between 0 * and 255. * XXX: Allow true float opacity */ quint8 opacity() const; //0-255 /** * Set the opacity for this layer. The range is between 0 and 255. * The layer will be marked dirty. * * XXX: Allow true float opacity */ void setOpacity(quint8 val); //0-255 /** * return the 8-bit opacity of this layer scaled to the range * 0-100 * * XXX: Allow true float opacity */ quint8 percentOpacity() const; //0-100 /** * Set the opacity of this layer with a number between 0 and 100; * the number will be scaled to between 0 and 255. * XXX: Allow true float opacity */ void setPercentOpacity(quint8 val); //0-100 /** * Return the composite op associated with this layer. */ virtual const KoCompositeOp *compositeOp() const = 0; const QString& compositeOpId() const; /** * Set a new composite op for this layer. The layer will be marked * dirty. */ void setCompositeOpId(const QString& compositeOpId); /** * @return unique id, which is now used by clone layers. */ QUuid uuid() const; /** * Set the uuid of node. This should only be used when loading * existing node and in constructor. */ void setUuid(const QUuid& id); /** * return the name of this node. This is the same as the * QObject::objectName. */ QString name() const { return objectName(); } /** * set the QObject::objectName. This is also the user-visible name * of the layer. The reason for this is that we want to see the * layer name also when debugging. */ void setName(const QString& name) { setObjectName(name); baseNodeChangedCallback(); } /** * @return the icon used to represent the node type, for instance * in the layerbox and in the menu. */ virtual QIcon icon() const { return QIcon(); } /** * Return a the properties of this base node (locked, visible etc, * with the right icons for their representation and their state. * * Subclasses can extend this list with new properties, like * opacity for layers or visualized for masks. * * The order of properties is, unfortunately, for now, important, * so take care which properties superclasses of your class * define. * * KisBaseNode defines visible = 0, locked = 1 * KisLayer defines opacity = 2, compositeOp = 3 * KisMask defines active = 2 (KisMask does not inherit kislayer) */ virtual PropertyList sectionModelProperties() const; /** * Change the section model properties. */ virtual void setSectionModelProperties(const PropertyList &properties); /** * Return all the properties of this layer as a KoProperties-based * serializable key-value list. */ const KoProperties & nodeProperties() const; /** * Set a node property. * @param name name of the property to be set. * @param value value to set the property to. */ void setNodeProperty(const QString & name, const QVariant & value); /** * Merge the specified properties with the properties of this * layer. Wherever these properties overlap, the value of the * node properties is changed. No properties on the node are * deleted. If there are new properties in this list, they will be * added on the node. */ void mergeNodeProperties(const KoProperties & properties); /** * Compare the given properties list with the properties of this * node. * * @return false only if the same property exists in both lists * but with a different value. Properties that are not in both * lists are disregarded. */ bool check(const KoProperties & properties) const; /** * Accept the KisNodeVisitor (for the Visitor design pattern), * should call the correct function on the KisNodeVisitor for this * node type, so you need to override it for all leaf classes in * the node inheritance hierarchy. * * return false if the visitor could not successfully act on this * node instance. */ virtual bool accept(KisNodeVisitor &) { return false; } /** * Accept the KisNodeVisitor (for the Visitor design pattern), * should call the correct function on the KisProcessingVisitor * for this node type, so you need to override it for all leaf * classes in the node inheritance hierarchy. * * The processing visitor differs from node visitor in the way * that it accepts undo adapter, that allows the processing to * be multithreaded */ virtual void accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter) { Q_UNUSED(visitor); Q_UNUSED(undoAdapter); } /** * @return a thumbnail in requested size. The thumbnail is a rgba * QImage and may have transparent parts. Returns a fully * transparent QImage of the requested size if the current node * type cannot generate a thumbnail. If the requested size is too * big, return a null QImage. */ - virtual QImage createThumbnail(qint32 w, qint32 h); + virtual QImage createThumbnail(qint32 w, qint32 h, Qt::AspectRatioMode aspectRatioMode = Qt::IgnoreAspectRatio); /** * @return a thumbnail in requested size for the defined timestamp. * The thumbnail is a rgba Image and may have transparent parts. * Returns a fully transparent QImage of the requested size if the * current node type cannot generate a thumbnail. If the requested * size is too big, return a null QImage. */ - virtual QImage createThumbnailForFrame(qint32 w, qint32 h, int time); + virtual QImage createThumbnailForFrame(qint32 w, qint32 h, int time, Qt::AspectRatioMode aspectRatioMode = Qt::IgnoreAspectRatio); /** * Ask this node to re-read the pertinent settings from the krita * configuration. */ virtual void updateSettings() { } /** * @return true if this node is visible (i.e, active (except for * selection masks where visible and active properties are * different)) in the graph * * @param bool recursive if true, check whether all parents of * this node are visible as well. */ virtual bool visible(bool recursive = false) const; /** * Set the visible status of this node. Visible nodes are active * in the graph (except for selections masks which can be active * while hidden), that is to say, they are taken into account * when merging. Invisible nodes play no role in the final image *, but will be modified when modifying all layers, for instance * when cropping. * * Toggling the visibility of a node will not automatically lead * to recomposition. * * @param visible the new visibility state * @param isLoading if true, the property is set during loading. */ virtual void setVisible(bool visible, bool loading = false); /** * Return the locked status of this node. Locked nodes cannot be * edited. */ bool userLocked() const; /** * Return whether or not the given node is isolated. */ bool belongsToIsolatedGroup() const; /** * Return whether or not the given node is the root of * isolation. */ bool isIsolatedRoot() const; /** * Set the locked status of this node. Locked nodes cannot be * edited. */ virtual void setUserLocked(bool l); /** * @return true if the node can be edited: * * if checkVisibility is true, then the node is only editable if it is visible and not locked. * if checkVisibility is false, then the node is editable if it's not locked. */ bool isEditable(bool checkVisibility = true) const; /** * @return true if the node is editable and has a paintDevice() * which which can be used for accessing pixels. It is an * equivalent to (isEditable() && paintDevice()) */ bool hasEditablePaintDevice() const; /** * @return the x-offset of this layer in the image plane. */ virtual qint32 x() const { return 0; } /** * Set the x offset of this layer in the image place. * Re-implement this where it makes sense, by default it does * nothing. It should not move child nodes. */ virtual void setX(qint32) { } /** * @return the y-offset of this layer in the image plane. */ virtual qint32 y() const { return 0; } /** * Set the y offset of this layer in the image place. * Re-implement this where it makes sense, by default it does * nothing. It should not move child nodes. */ virtual void setY(qint32) { } /** * Returns an approximation of where the bounds on actual data are * in this node. */ virtual QRect extent() const { return QRect(); } /** * Returns the exact bounds of where the actual data resides in * this node. */ virtual QRect exactBounds() const { return QRect(); } /** * Sets the state of the node to the value of @param collapsed */ void setCollapsed(bool collapsed); /** * returns the collapsed state of this node */ bool collapsed() const; /** * Sets a color label index associated to the layer. The actual * color of the label and the number of available colors is * defined by Krita GUI configuration. */ void setColorLabelIndex(int index); /** * \see setColorLabelIndex */ int colorLabelIndex() const; /** * Returns true if the offset of the node can be changed in a LodN * stroke. Currently, all the nodes except shape layers support that. */ bool supportsLodMoves() const; /** * Return the keyframe channels associated with this node * @return list of keyframe channels */ QMap keyframeChannels() const; /** * Get the keyframe channel with given id. * If the channel does not yet exist and the node supports the requested * channel, it will be created if create is true. * @param id internal name for channel * @param create attempt to create the channel if it does not exist yet * @return keyframe channel with the id, or null if not found */ KisKeyframeChannel *getKeyframeChannel(const QString &id, bool create); KisKeyframeChannel *getKeyframeChannel(const QString &id) const; /** * @return If true, node will be visible on animation timeline even when inactive. */ bool isPinnedToTimeline() const; /** * Set whether node should be visible on animation timeline even when inactive. */ void setPinnedToTimeline(bool pinned); bool isAnimated() const; void enableAnimation(); virtual void setImage(KisImageWSP image); KisImageWSP image() const; /** * Fake node is not present in the layer stack and is not used * for normal projection rendering algorithms. */ virtual bool isFakeNode() const; protected: void setSupportsLodMoves(bool value); /** * FIXME: This method is a workaround for getting parent node * on a level of KisBaseNode. In fact, KisBaseNode should inherit * KisNode (in terms of current Krita) to be able to traverse * the node stack */ virtual KisBaseNodeSP parentCallback() const { return KisBaseNodeSP(); } virtual void notifyParentVisibilityChanged(bool value) { Q_UNUSED(value); } /** * This callback is called when some meta state of the base node * that can be interesting to the UI has changed. E.g. visibility, * lockness, opacity, compositeOp and etc. This signal is * forwarded by the KisNode and KisNodeGraphListener to the model * in KisLayerBox, so it can update its controls when information * changes. */ virtual void baseNodeChangedCallback() { } /** * This callback is called when collapsed state of the base node * has changed. This signal is forwarded by the KisNode and * KisNodeGraphListener to the model in KisLayerBox, so it can * update its controls when information changes. */ virtual void baseNodeCollapsedChangedCallback() { } virtual void baseNodeInvalidateAllFramesCallback() { } /** * Add a keyframe channel for this node. The channel will be added * to the common hash table which will be available to the UI. * * WARNING: the \p channel object *NOT* become owned by the node! * The caller must ensure manually that the lifetime of * the object coincide with the lifetime of the node. */ virtual void addKeyframeChannel(KisKeyframeChannel* channel); /** * Attempt to create the requested channel. Used internally by getKeyframeChannel. * Subclasses should implement this method to catch any new channel types they support. * @param id channel to create * @return newly created channel or null */ virtual KisKeyframeChannel * requestKeyframeChannel(const QString &id); Q_SIGNALS: void keyframeChannelAdded(KisKeyframeChannel *channel); private: struct Private; Private * const m_d; }; Q_DECLARE_METATYPE( KisBaseNode::PropertyList ) #endif diff --git a/libs/image/kis_convolution_painter.h b/libs/image/kis_convolution_painter.h index 2db38414a7..0911bec934 100644 --- a/libs/image/kis_convolution_painter.h +++ b/libs/image/kis_convolution_painter.h @@ -1,104 +1,109 @@ /* * 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) + * channels possible. + * + * WARNING: The painter will read **more** pixels than you pass in \p areaSize. + * The actual processing area will be: + * QRect(x - kernel.width() / 2, + * y - kernel.height() / 2, + * w + 2 * (kernel.width() / 2), + * h + 2 * (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 useFFTImplementation(const KisConvolutionKernelSP kernel) const; private: TestingEnginePreference m_enginePreference; }; #endif //KIS_CONVOLUTION_PAINTER_H_ diff --git a/libs/image/kis_default_bounds.cpp b/libs/image/kis_default_bounds.cpp index a84cdb9ee3..38b9a83cab 100644 --- a/libs/image/kis_default_bounds.cpp +++ b/libs/image/kis_default_bounds.cpp @@ -1,159 +1,211 @@ /* * Copyright (c) 2010 Boudewijn Rempt * Copyright (c) 2010 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_global.h" #include "kis_default_bounds.h" #include "kis_paint_device.h" #include "kis_image_animation_interface.h" #include "kis_image.h" const QRect KisDefaultBounds::infiniteRect = QRect(qint32_MIN/2, qint32_MIN/2, qint32_MAX, qint32_MAX); /******************************************************************/ /* KisDefaultBounds */ /******************************************************************/ struct Q_DECL_HIDDEN KisDefaultBounds::Private { KisImageWSP image; }; KisDefaultBounds::KisDefaultBounds(KisImageWSP image) : m_d(new Private()) { m_d->image = image; } KisDefaultBounds::~KisDefaultBounds() { delete m_d; } QRect KisDefaultBounds::bounds() const { /** * By default return infinite rect to cover everything */ return m_d->image ? m_d->image->effectiveLodBounds() : infiniteRect; } bool KisDefaultBounds::wrapAroundMode() const { return m_d->image ? m_d->image->wrapAroundModeActive() : false; } int KisDefaultBounds::currentLevelOfDetail() const { return m_d->image ? m_d->image->currentLevelOfDetail() : 0; } int KisDefaultBounds::currentTime() const { KisImageAnimationInterface *interface = m_d->image ? m_d->image->animationInterface() : 0; return interface ? interface->currentTime() : 0; } bool KisDefaultBounds::externalFrameActive() const { KisImageAnimationInterface *interface = m_d->image ? m_d->image->animationInterface() : 0; return interface ? interface->externalFrameActive() : false; } void *KisDefaultBounds::sourceCookie() const { return m_d->image.data(); } /******************************************************************/ /* KisSelectionDefaultBounds */ /******************************************************************/ struct Q_DECL_HIDDEN KisSelectionDefaultBounds::Private { KisPaintDeviceWSP parentDevice; }; KisSelectionDefaultBounds::KisSelectionDefaultBounds(KisPaintDeviceSP parentDevice) : m_d(new Private()) { m_d->parentDevice = parentDevice; } KisSelectionDefaultBounds::~KisSelectionDefaultBounds() { delete m_d; } QRect KisSelectionDefaultBounds::bounds() const { return m_d->parentDevice ? m_d->parentDevice->extent() | m_d->parentDevice->defaultBounds()->bounds() : QRect(); } bool KisSelectionDefaultBounds::wrapAroundMode() const { return m_d->parentDevice ? m_d->parentDevice->defaultBounds()->wrapAroundMode() : false; } int KisSelectionDefaultBounds::currentLevelOfDetail() const { return m_d->parentDevice ? m_d->parentDevice->defaultBounds()->currentLevelOfDetail() : 0; } int KisSelectionDefaultBounds::currentTime() const { return m_d->parentDevice ? m_d->parentDevice->defaultBounds()->currentTime() : 0; } bool KisSelectionDefaultBounds::externalFrameActive() const { return m_d->parentDevice ? m_d->parentDevice->defaultBounds()->externalFrameActive() : false; } void *KisSelectionDefaultBounds::sourceCookie() const { return m_d->parentDevice.data(); } /******************************************************************/ /* KisSelectionEmptyBounds */ /******************************************************************/ KisSelectionEmptyBounds::KisSelectionEmptyBounds(KisImageWSP image) : KisDefaultBounds(image) { } KisSelectionEmptyBounds::~KisSelectionEmptyBounds() { } QRect KisSelectionEmptyBounds::bounds() const { return QRect(0, 0, 0, 0); } + +/******************************************************************/ +/* KisWrapAroundBoundsWrapper */ +/******************************************************************/ + + +struct Q_DECL_HIDDEN KisWrapAroundBoundsWrapper::Private +{ + KisDefaultBoundsBaseSP base; + QRect bounds; +}; + +KisWrapAroundBoundsWrapper::KisWrapAroundBoundsWrapper(KisDefaultBoundsBaseSP base, QRect bounds) +: m_d(new Private()) +{ + m_d->base = base; + m_d->bounds = bounds; +} + +KisWrapAroundBoundsWrapper::~KisWrapAroundBoundsWrapper() +{ +} + +QRect KisWrapAroundBoundsWrapper::bounds() const +{ + return m_d->bounds; +} + +bool KisWrapAroundBoundsWrapper::wrapAroundMode() const +{ + return true; +} + +int KisWrapAroundBoundsWrapper::currentLevelOfDetail() const +{ + return m_d->base->currentLevelOfDetail(); +} + +int KisWrapAroundBoundsWrapper::currentTime() const +{ + return m_d->base->currentTime(); +} + +bool KisWrapAroundBoundsWrapper::externalFrameActive() const +{ + return m_d->base->externalFrameActive(); +} + +void *KisWrapAroundBoundsWrapper::sourceCookie() const +{ + return m_d->base->sourceCookie(); +} diff --git a/libs/image/kis_default_bounds.h b/libs/image/kis_default_bounds.h index c234633a2d..21dc96bc5e 100644 --- a/libs/image/kis_default_bounds.h +++ b/libs/image/kis_default_bounds.h @@ -1,85 +1,115 @@ /* * 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; +class KisWrapAroundBoundsWrapper; typedef KisSharedPtr KisDefaultBoundsSP; typedef KisSharedPtr KisSelectionDefaultBoundsSP; typedef KisSharedPtr KisSelectionEmptyBoundsSP; +typedef KisSharedPtr KisWrapAroundBoundsWrapperSP; class KRITAIMAGE_EXPORT KisDefaultBounds : public KisDefaultBoundsBase { public: KisDefaultBounds(KisImageWSP image = 0); ~KisDefaultBounds() override; QRect bounds() const override; bool wrapAroundMode() const override; int currentLevelOfDetail() const override; int currentTime() const override; bool externalFrameActive() const override; void * sourceCookie() const override; protected: friend class KisPaintDeviceTest; static const QRect infiniteRect; private: Q_DISABLE_COPY(KisDefaultBounds) struct Private; Private * const m_d; }; class KRITAIMAGE_EXPORT KisSelectionDefaultBounds : public KisDefaultBoundsBase { public: KisSelectionDefaultBounds(KisPaintDeviceSP parentPaintDevice); ~KisSelectionDefaultBounds() override; QRect bounds() const override; bool wrapAroundMode() const override; int currentLevelOfDetail() const override; int currentTime() const override; bool externalFrameActive() const override; void * sourceCookie() const override; private: Q_DISABLE_COPY(KisSelectionDefaultBounds) struct Private; Private * const m_d; }; class KRITAIMAGE_EXPORT KisSelectionEmptyBounds : public KisDefaultBounds { public: KisSelectionEmptyBounds(KisImageWSP image); ~KisSelectionEmptyBounds() override; QRect bounds() const override; }; +/** + * @brief The KisWrapAroundBoundsWrapper class + * wrapper around a KisDefaultBoundsBaseSP to enable + * wraparound. Used for patterns. + */ +class KRITAIMAGE_EXPORT KisWrapAroundBoundsWrapper : public KisDefaultBoundsBase +{ +public: + KisWrapAroundBoundsWrapper(KisDefaultBoundsBaseSP base, QRect bounds); + ~KisWrapAroundBoundsWrapper() override; + + QRect bounds() const override; + bool wrapAroundMode() const override; + int currentLevelOfDetail() const override; + int currentTime() const override; + bool externalFrameActive() const override; + void * sourceCookie() const override; + +protected: + friend class KisPaintDeviceTest; + +private: + Q_DISABLE_COPY(KisWrapAroundBoundsWrapper) + + struct Private; + const QScopedPointer m_d; +}; + #endif // KIS_DEFAULT_BOUNDS_H diff --git a/libs/image/kis_edge_detection_kernel.cpp b/libs/image/kis_edge_detection_kernel.cpp index 2596140852..15ed4fc938 100644 --- a/libs/image/kis_edge_detection_kernel.cpp +++ b/libs/image/kis_edge_detection_kernel.cpp @@ -1,428 +1,414 @@ /* * 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_kernel.h" #include "kis_global.h" #include "kis_convolution_kernel.h" #include #include #include #include #include #include KisEdgeDetectionKernel::KisEdgeDetectionKernel() { } /* * This code is very similar to the gaussian kernel code, except unlike the gaussian code, * edge-detection kernels DO use the diagonals. * Except for the simple mode. We implement the simple mode because it is an analog to * the old sobel filter. */ Eigen::Matrix KisEdgeDetectionKernel::createHorizontalMatrix(qreal radius, FilterType type, bool reverse) { int kernelSize = kernelSizeFromRadius(radius); Eigen::Matrix matrix(kernelSize, kernelSize); KIS_ASSERT_RECOVER_NOOP(kernelSize & 0x1); const int center = kernelSize / 2; if (type==Prewit) { for (int x = 0; x < kernelSize; x++) { for (int y=0; y KisEdgeDetectionKernel::createVerticalMatrix(qreal radius, FilterType type, bool reverse) { int kernelSize = kernelSizeFromRadius(radius); Eigen::Matrix matrix(kernelSize, kernelSize); KIS_ASSERT_RECOVER_NOOP(kernelSize & 0x1); const int center = kernelSize / 2; if (type==Prewit) { for (int y = 0; y < kernelSize; y++) { for (int x=0; x matrix = createHorizontalMatrix(radius, type, reverse); if (denormalize) { return KisConvolutionKernel::fromMatrix(matrix, 0.5, 1); } else { return KisConvolutionKernel::fromMatrix(matrix, 0, matrix.sum()); } } KisConvolutionKernelSP KisEdgeDetectionKernel::createVerticalKernel(qreal radius, KisEdgeDetectionKernel::FilterType type, bool denormalize, bool reverse) { Eigen::Matrix matrix = createVerticalMatrix(radius, type, reverse); if (denormalize) { return KisConvolutionKernel::fromMatrix(matrix, 0.5, 1); } else { return KisConvolutionKernel::fromMatrix(matrix, 0, matrix.sum()); } } int KisEdgeDetectionKernel::kernelSizeFromRadius(qreal radius) { return qMax((int)(2 * ceil(sigmaFromRadius(radius)) + 1), 3); } qreal KisEdgeDetectionKernel::sigmaFromRadius(qreal radius) { return 0.3 * radius + 0.3; } void KisEdgeDetectionKernel::applyEdgeDetection(KisPaintDeviceSP device, const QRect &rect, qreal xRadius, qreal yRadius, KisEdgeDetectionKernel::FilterType type, const QBitArray &channelFlags, KoUpdater *progressUpdater, FilterOutput output, bool writeToAlpha) { QPoint srcTopLeft = rect.topLeft(); KisPainter finalPainter(device); finalPainter.setChannelFlags(channelFlags); finalPainter.setProgress(progressUpdater); if (output == pythagorean || output == radian) { KisPaintDeviceSP x_denormalised = new KisPaintDevice(device->colorSpace()); KisPaintDeviceSP y_denormalised = new KisPaintDevice(device->colorSpace()); x_denormalised->prepareClone(device); y_denormalised->prepareClone(device); KisConvolutionKernelSP kernelHorizLeftRight = KisEdgeDetectionKernel::createHorizontalKernel(xRadius, type); KisConvolutionKernelSP kernelVerticalTopBottom = KisEdgeDetectionKernel::createVerticalKernel(yRadius, type); - qreal horizontalCenter = qreal(kernelHorizLeftRight->width()) / 2.0; - qreal verticalCenter = qreal(kernelVerticalTopBottom->height()) / 2.0; - KisConvolutionPainter horizPainterLR(x_denormalised); horizPainterLR.setChannelFlags(channelFlags); horizPainterLR.setProgress(progressUpdater); horizPainterLR.applyMatrix(kernelHorizLeftRight, device, - srcTopLeft - QPoint(0, ceil(horizontalCenter)), - srcTopLeft - QPoint(0, ceil(horizontalCenter)), - rect.size() + QSize(0, 2 * ceil(horizontalCenter)), BORDER_REPEAT); + srcTopLeft, + srcTopLeft, + rect.size(), BORDER_REPEAT); KisConvolutionPainter verticalPainterTB(y_denormalised); verticalPainterTB.setChannelFlags(channelFlags); verticalPainterTB.setProgress(progressUpdater); verticalPainterTB.applyMatrix(kernelVerticalTopBottom, device, - srcTopLeft - QPoint(0, ceil(verticalCenter)), - srcTopLeft - QPoint(0, ceil(verticalCenter)), - rect.size() + QSize(0, 2 * ceil(verticalCenter)), BORDER_REPEAT); + srcTopLeft, + srcTopLeft, + rect.size(), BORDER_REPEAT); KisSequentialIterator yItterator(y_denormalised, rect); KisSequentialIterator xItterator(x_denormalised, rect); KisSequentialIterator finalIt(device, rect); const int pixelSize = device->colorSpace()->pixelSize(); const int channels = device->colorSpace()->channelCount(); const int alphaPos = device->colorSpace()->alphaPos(); KIS_SAFE_ASSERT_RECOVER_RETURN(alphaPos >= 0); QVector yNormalised(channels); QVector xNormalised(channels); QVector finalNorm(channels); while(yItterator.nextPixel() && xItterator.nextPixel() && finalIt.nextPixel()) { device->colorSpace()->normalisedChannelsValue(yItterator.rawData(), yNormalised); device->colorSpace()->normalisedChannelsValue(xItterator.rawData(), xNormalised); device->colorSpace()->normalisedChannelsValue(finalIt.rawData(), finalNorm); if (output == pythagorean) { for (int c = 0; ccolorSpace()); qreal alpha = 0; for (int c = 0; c<(channels-1); c++) { alpha = alpha+finalNorm[c]; } alpha = qMin(alpha/(channels-1), col.opacityF()); col.setOpacity(alpha); memcpy(finalIt.rawData(), col.data(), pixelSize); } else { quint8* f = finalIt.rawData(); finalNorm[alphaPos] = 1.0; device->colorSpace()->fromNormalisedChannelsValue(f, finalNorm); memcpy(finalIt.rawData(), f, pixelSize); } } } else { KisConvolutionKernelSP kernel; - qreal center = 0; bool denormalize = !writeToAlpha; if (output == xGrowth) { kernel = KisEdgeDetectionKernel::createHorizontalKernel(xRadius, type, denormalize); - center = qreal(kernel->width()) / 2.0; } else if (output == xFall) { kernel = KisEdgeDetectionKernel::createHorizontalKernel(xRadius, type, denormalize, true); - center = qreal(kernel->width()) / 2.0; } else if (output == yGrowth) { kernel = KisEdgeDetectionKernel::createVerticalKernel(yRadius, type, denormalize); - center = qreal(kernel->height()) / 2.0; } else { //yFall kernel = KisEdgeDetectionKernel::createVerticalKernel(yRadius, type, denormalize, true); - center = qreal(kernel->height()) / 2.0; } if (writeToAlpha) { KisPaintDeviceSP denormalised = new KisPaintDevice(device->colorSpace()); denormalised->prepareClone(device); KisConvolutionPainter kernelP(denormalised); kernelP.setChannelFlags(channelFlags); kernelP.setProgress(progressUpdater); kernelP.applyMatrix(kernel, device, - srcTopLeft - QPoint(0, ceil(center)), - srcTopLeft - QPoint(0, ceil(center)), - rect.size() + QSize(0, 2 * ceil(center)), BORDER_REPEAT); + srcTopLeft, srcTopLeft, + rect.size(), BORDER_REPEAT); KisSequentialIterator iterator(denormalised, rect); KisSequentialIterator finalIt(device, rect); const int pixelSize = device->colorSpace()->pixelSize(); const int channels = device->colorSpace()->colorChannelCount(); QVector normalised(channels); while (iterator.nextPixel() && finalIt.nextPixel()) { device->colorSpace()->normalisedChannelsValue(iterator.rawData(), normalised); KoColor col(finalIt.rawData(), device->colorSpace()); qreal alpha = 0; for (int c = 0; ccolorSpace()->setOpacity(finalIt.rawData(), 1.0, numConseqPixels); } } } } void KisEdgeDetectionKernel::convertToNormalMap(KisPaintDeviceSP device, const QRect &rect, qreal xRadius, qreal yRadius, KisEdgeDetectionKernel::FilterType type, int channelToConvert, QVector channelOrder, QVector channelFlip, const QBitArray &channelFlags, KoUpdater *progressUpdater) { QPoint srcTopLeft = rect.topLeft(); KisPainter finalPainter(device); finalPainter.setChannelFlags(channelFlags); finalPainter.setProgress(progressUpdater); KisPaintDeviceSP x_denormalised = new KisPaintDevice(device->colorSpace()); KisPaintDeviceSP y_denormalised = new KisPaintDevice(device->colorSpace()); x_denormalised->prepareClone(device); y_denormalised->prepareClone(device); KisConvolutionKernelSP kernelHorizLeftRight = KisEdgeDetectionKernel::createHorizontalKernel(yRadius, type, true, !channelFlip[1]); KisConvolutionKernelSP kernelVerticalTopBottom = KisEdgeDetectionKernel::createVerticalKernel(xRadius, type, true, !channelFlip[0]); - qreal horizontalCenter = qreal(kernelHorizLeftRight->width()) / 2.0; - qreal verticalCenter = qreal(kernelVerticalTopBottom->height()) / 2.0; - KisConvolutionPainter horizPainterLR(y_denormalised); horizPainterLR.setChannelFlags(channelFlags); horizPainterLR.setProgress(progressUpdater); horizPainterLR.applyMatrix(kernelHorizLeftRight, device, - srcTopLeft - QPoint(ceil(horizontalCenter), 0), - srcTopLeft - QPoint(ceil(horizontalCenter), 0), - rect.size() + QSize(2 * ceil(horizontalCenter), 0), BORDER_REPEAT); + srcTopLeft, srcTopLeft, + rect.size(), BORDER_REPEAT); KisConvolutionPainter verticalPainterTB(x_denormalised); verticalPainterTB.setChannelFlags(channelFlags); verticalPainterTB.setProgress(progressUpdater); verticalPainterTB.applyMatrix(kernelVerticalTopBottom, device, - srcTopLeft - QPoint(0, ceil(verticalCenter)), - srcTopLeft - QPoint(0, ceil(verticalCenter)), - rect.size() + QSize(0, 2 * ceil(verticalCenter)), BORDER_REPEAT); + srcTopLeft, + srcTopLeft, + rect.size(), BORDER_REPEAT); KisSequentialIterator yItterator(y_denormalised, rect); KisSequentialIterator xItterator(x_denormalised, rect); KisSequentialIterator finalIt(device, rect); const int pixelSize = device->colorSpace()->pixelSize(); const int channels = device->colorSpace()->channelCount(); const int alphaPos = device->colorSpace()->alphaPos(); KIS_SAFE_ASSERT_RECOVER_RETURN(alphaPos >= 0); QVector yNormalised(channels); QVector xNormalised(channels); QVector finalNorm(channels); while(yItterator.nextPixel() && xItterator.nextPixel() && finalIt.nextPixel()) { device->colorSpace()->normalisedChannelsValue(yItterator.rawData(), yNormalised); device->colorSpace()->normalisedChannelsValue(xItterator.rawData(), xNormalised); qreal z = 1.0; if (channelFlip[2]==true){ z=-1.0; } QVector3D normal = QVector3D((xNormalised[channelToConvert]-0.5)*2, (yNormalised[channelToConvert]-0.5)*2, z); normal.normalize(); finalNorm.fill(1.0); for (int c = 0; c<3; c++) { finalNorm[device->colorSpace()->channels().at(channelOrder[c])->displayPosition()] = (normal[channelOrder[c]]/2)+0.5; } finalNorm[alphaPos]= 1.0; quint8* pixel = finalIt.rawData(); device->colorSpace()->fromNormalisedChannelsValue(pixel, finalNorm); memcpy(finalIt.rawData(), pixel, pixelSize); } } diff --git a/libs/image/kis_fill_painter.cc b/libs/image/kis_fill_painter.cc index 3e0d9a3569..7c461a97af 100644 --- a/libs/image/kis_fill_painter.cc +++ b/libs/image/kis_fill_painter.cc @@ -1,332 +1,358 @@ /* * Copyright (c) 2004 Adrian Page * Copyright (c) 2004 Bart Coppens * Copyright (c) 2010 Lukáš Tvrdý * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_fill_painter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "generator/kis_generator.h" #include "filter/kis_filter_configuration.h" #include "generator/kis_generator_registry.h" #include "kis_processing_information.h" #include "kis_debug.h" #include "kis_image.h" #include "kis_layer.h" #include "kis_paint_device.h" #include #include "KoColorSpace.h" #include "kis_transaction.h" #include "kis_pixel_selection.h" #include #include #include "kis_selection_filters.h" +#include KisFillPainter::KisFillPainter() : KisPainter() { initFillPainter(); } KisFillPainter::KisFillPainter(KisPaintDeviceSP device) : KisPainter(device) { initFillPainter(); } KisFillPainter::KisFillPainter(KisPaintDeviceSP device, KisSelectionSP selection) : KisPainter(device, selection) { initFillPainter(); } void KisFillPainter::initFillPainter() { m_width = m_height = -1; m_careForSelection = false; m_sizemod = 0; m_feather = 0; m_useCompositioning = false; m_threshold = 0; } void KisFillPainter::fillSelection(const QRect &rc, const KoColor &color) { KisPaintDeviceSP fillDevice = new KisPaintDevice(device()->colorSpace()); fillDevice->setDefaultPixel(color); bitBlt(rc.topLeft(), fillDevice, rc); } // 'regular' filling // XXX: This also needs renaming, since filling ought to keep the opacity and the composite op in mind, // this is more eraseToColor. void KisFillPainter::fillRect(qint32 x1, qint32 y1, qint32 w, qint32 h, const KoColor& kc, quint8 opacity) { if (w > 0 && h > 0) { // Make sure we're in the right colorspace KoColor kc2(kc); // get rid of const kc2.convertTo(device()->colorSpace()); quint8 * data = kc2.data(); device()->colorSpace()->setOpacity(data, opacity, 1); device()->fill(x1, y1, w, h, data); addDirtyRect(QRect(x1, y1, w, h)); } } void KisFillPainter::fillRect(const QRect &rc, const KoPatternSP pattern, const QPoint &offset) { fillRect(rc.x(), rc.y(), rc.width(), rc.height(), pattern, offset); } void KisFillPainter::fillRect(qint32 x1, qint32 y1, qint32 w, qint32 h, const KoPatternSP pattern, const QPoint &offset) { if (!pattern) return; if (!pattern->valid()) return; if (!device()) return; if (w < 1) return; if (h < 1) return; KisPaintDeviceSP patternLayer = new KisPaintDevice(device()->compositionSourceColorSpace(), pattern->name()); patternLayer->convertFromQImage(pattern->pattern(), 0); if (!offset.isNull()) { patternLayer->moveTo(offset); } fillRect(x1, y1, w, h, patternLayer, QRect(offset.x(), offset.y(), pattern->width(), pattern->height())); } +void KisFillPainter::fillRect(const QRect &rc, const KoPatternSP pattern, const QTransform transform) +{ + if (!pattern) return; + if (!pattern->valid()) return; + if (!device()) return; + if (rc.width() < 1) return; + if (rc.height() < 1) return; + + KisPaintDeviceSP patternLayer = new KisPaintDevice(device()->compositionSourceColorSpace(), pattern->name()); + patternLayer->convertFromQImage(pattern->pattern(), 0); + + fillRect(rc.x(), rc.y(), rc.width(), rc.height(), patternLayer, QRect(0, 0, pattern->width(), pattern->height()), transform); +} + +void KisFillPainter::fillRect(qint32 x1, qint32 y1, qint32 w, qint32 h, const KisPaintDeviceSP device, const QRect& deviceRect, const QTransform transform) +{ + KisPaintDeviceSP wrapped = device; + wrapped->setDefaultBounds(new KisWrapAroundBoundsWrapper(wrapped->defaultBounds(), deviceRect)); + + KisPerspectiveTransformWorker worker = KisPerspectiveTransformWorker(this->device(), transform, this->progressUpdater()); + worker.runPartialDst(device, this->device(), QRect(x1, y1, w, h)); + + addDirtyRect(QRect(x1, y1, w, h)); +} + void KisFillPainter::fillRect(qint32 x1, qint32 y1, qint32 w, qint32 h, const KisPaintDeviceSP device, const QRect& deviceRect) { const QRect &patternRect = deviceRect; const QRect fillRect(x1, y1, w, h); auto toPatternLocal = [](int value, int offset, int width) { const int normalizedValue = value - offset; return offset + (normalizedValue >= 0 ? normalizedValue % width : width - (-normalizedValue - 1) % width - 1); }; int dstY = fillRect.y(); while (dstY <= fillRect.bottom()) { const int dstRowsRemaining = fillRect.bottom() - dstY + 1; const int srcY = toPatternLocal(dstY, patternRect.y(), patternRect.height()); const int height = qMin(patternRect.height() - srcY + patternRect.y(), dstRowsRemaining); int dstX = fillRect.x(); while (dstX <= fillRect.right()) { const int dstColumnsRemaining = fillRect.right() - dstX + 1; const int srcX = toPatternLocal(dstX, patternRect.x(), patternRect.width()); const int width = qMin(patternRect.width() - srcX + patternRect.x(), dstColumnsRemaining); bitBlt(dstX, dstY, device, srcX, srcY, width, height); dstX += width; } dstY += height; } addDirtyRect(QRect(x1, y1, w, h)); } void KisFillPainter::fillRect(qint32 x1, qint32 y1, qint32 w, qint32 h, const KisFilterConfigurationSP generator) { if (!generator) return; KisGeneratorSP g = KisGeneratorRegistry::instance()->value(generator->name()); if (!device()) return; if (w < 1) return; if (h < 1) return; QRect tmpRc(x1, y1, w, h); KisProcessingInformation dstCfg(device(), tmpRc.topLeft(), 0); g->generate(dstCfg, tmpRc.size(), generator); addDirtyRect(tmpRc); } // flood filling void KisFillPainter::fillColor(int startX, int startY, KisPaintDeviceSP sourceDevice) { if (!m_useCompositioning) { if (m_sizemod || m_feather || compositeOp()->id() != COMPOSITE_OVER || opacity() != MAX_SELECTED || sourceDevice != device()) { warnKrita << "WARNING: Fast Flood Fill (no compositioning mode)" << "does not support compositeOps, opacity, " << "selection enhancements and separate source " << "devices"; } QRect fillBoundsRect(0, 0, m_width, m_height); QPoint startPoint(startX, startY); if (!fillBoundsRect.contains(startPoint)) return; KisScanlineFill gc(device(), startPoint, fillBoundsRect); gc.setThreshold(m_threshold); gc.fillColor(paintColor()); } else { genericFillStart(startX, startY, sourceDevice); // Now create a layer and fill it KisPaintDeviceSP filled = device()->createCompositionSourceDevice(); Q_CHECK_PTR(filled); KisFillPainter painter(filled); painter.fillRect(0, 0, m_width, m_height, paintColor()); painter.end(); genericFillEnd(filled); } } -void KisFillPainter::fillPattern(int startX, int startY, KisPaintDeviceSP sourceDevice) +void KisFillPainter::fillPattern(int startX, int startY, KisPaintDeviceSP sourceDevice, QTransform patternTransform) { genericFillStart(startX, startY, sourceDevice); // Now create a layer and fill it KisPaintDeviceSP filled = device()->createCompositionSourceDevice(); Q_CHECK_PTR(filled); KisFillPainter painter(filled); - painter.fillRect(0, 0, m_width, m_height, pattern()); + painter.fillRect(QRect(0, 0, m_width, m_height), pattern(), patternTransform); painter.end(); genericFillEnd(filled); } void KisFillPainter::genericFillStart(int startX, int startY, KisPaintDeviceSP sourceDevice) { Q_ASSERT(m_width > 0); Q_ASSERT(m_height > 0); // Create a selection from the surrounding area KisPixelSelectionSP pixelSelection = createFloodSelection(startX, startY, sourceDevice); KisSelectionSP newSelection = new KisSelection(pixelSelection->defaultBounds()); newSelection->pixelSelection()->applySelection(pixelSelection, SELECTION_REPLACE); m_fillSelection = newSelection; } void KisFillPainter::genericFillEnd(KisPaintDeviceSP filled) { if (progressUpdater() && progressUpdater()->interrupted()) { m_width = m_height = -1; return; } // TODO: filling using the correct bound of the selection would be better, *but* // the selection is limited to the exact bound of a layer, while in reality, we don't // want that, since we want a transparent layer to be completely filled // QRect rc = m_fillSelection->selectedExactRect(); /** * Apply the real selection to a filled one */ KisSelectionSP realSelection = selection(); if (realSelection) { m_fillSelection->pixelSelection()->applySelection( realSelection->projection(), SELECTION_INTERSECT); } setSelection(m_fillSelection); bitBlt(0, 0, filled, 0, 0, m_width, m_height); setSelection(realSelection); if (progressUpdater()) progressUpdater()->setProgress(100); m_width = m_height = -1; } KisPixelSelectionSP KisFillPainter::createFloodSelection(int startX, int startY, KisPaintDeviceSP sourceDevice) { KisPixelSelectionSP newSelection = new KisPixelSelection(new KisSelectionDefaultBounds(device())); return createFloodSelection(newSelection, startX, startY, sourceDevice); } KisPixelSelectionSP KisFillPainter::createFloodSelection(KisPixelSelectionSP pixelSelection, int startX, int startY, KisPaintDeviceSP sourceDevice) { if (m_width < 0 || m_height < 0) { if (selection() && m_careForSelection) { QRect rc = selection()->selectedExactRect(); m_width = rc.width() - (startX - rc.x()); m_height = rc.height() - (startY - rc.y()); } } dbgImage << "Width: " << m_width << " Height: " << m_height; // Otherwise the width and height should have been set Q_ASSERT(m_width > 0 && m_height > 0); QRect fillBoundsRect(0, 0, m_width, m_height); QPoint startPoint(startX, startY); if (!fillBoundsRect.contains(startPoint)) { return pixelSelection; } KisScanlineFill gc(sourceDevice, startPoint, fillBoundsRect); gc.setThreshold(m_threshold); gc.fillSelection(pixelSelection); if (m_sizemod > 0) { KisGrowSelectionFilter biggy(m_sizemod, m_sizemod); biggy.process(pixelSelection, pixelSelection->selectedRect().adjusted(-m_sizemod, -m_sizemod, m_sizemod, m_sizemod)); } else if (m_sizemod < 0) { KisShrinkSelectionFilter tiny(-m_sizemod, -m_sizemod, false); tiny.process(pixelSelection, pixelSelection->selectedRect()); } if (m_feather > 0) { KisFeatherSelectionFilter feathery(m_feather); feathery.process(pixelSelection, pixelSelection->selectedRect().adjusted(-m_feather, -m_feather, m_feather, m_feather)); } return pixelSelection; } diff --git a/libs/image/kis_fill_painter.h b/libs/image/kis_fill_painter.h index a988e2b696..4235e8283c 100644 --- a/libs/image/kis_fill_painter.h +++ b/libs/image/kis_fill_painter.h @@ -1,298 +1,319 @@ /* * Copyright (c) 2004 Adrian Page * Copyright (c) 2004 Bart Coppens * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_FILL_PAINTER_H_ #define KIS_FILL_PAINTER_H_ #include #include #include #include #include "kis_painter.h" #include "kis_types.h" #include "kis_selection.h" #include class KisFilterConfiguration; // XXX: Filling should set dirty rect. /** * This painter can be used to fill paint devices in different ways. This can also be used * for flood filling related operations. */ class KRITAIMAGE_EXPORT KisFillPainter : public KisPainter { public: /** * Construct an empty painter. Use the begin(KisPaintDeviceSP) method to attach * to a paint device */ KisFillPainter(); /** * Start painting on the specified paint device */ KisFillPainter(KisPaintDeviceSP device); KisFillPainter(KisPaintDeviceSP device, KisSelectionSP selection); private: void initFillPainter(); public: /** * Fill a rectangle with black transparent pixels (0, 0, 0, 0 for RGBA). */ void eraseRect(qint32 x1, qint32 y1, qint32 w, qint32 h); /** * Overloaded version of the above function. */ void eraseRect(const QRect& rc); /** * Fill current selection of KisPainter with a specified \p color. * * The filling rect is limited by \p rc to allow multithreaded * filling/processing. */ void fillSelection(const QRect &rc, const KoColor &color); /** * Fill a rectangle with a certain color. */ void fillRect(qint32 x, qint32 y, qint32 w, qint32 h, const KoColor& c); /** * Overloaded version of the above function. */ void fillRect(const QRect& rc, const KoColor& c); /** * Fill a rectangle with a certain color and opacity. */ void fillRect(qint32 x, qint32 y, qint32 w, qint32 h, const KoColor& c, quint8 opacity); /** * Overloaded version of the above function. */ void fillRect(const QRect& rc, const KoColor& c, quint8 opacity); /** * Fill a rectangle with a certain pattern. The pattern is repeated if it does not fit the * entire rectangle. */ void fillRect(qint32 x1, qint32 y1, qint32 w, qint32 h, const KoPatternSP pattern, const QPoint &offset = QPoint()); /** * Fill a rectangle with a certain pattern. The pattern is repeated if it does not fit the * entire rectangle. + * + * This one uses blitting and thus makes use of proper composition. */ void fillRect(qint32 x1, qint32 y1, qint32 w, qint32 h, const KisPaintDeviceSP device, const QRect& deviceRect); /** * Overloaded version of the above function. */ void fillRect(const QRect& rc, const KoPatternSP pattern, const QPoint &offset = QPoint()); + /** + * @brief fillRect + * Fill a rectangle with a certain pattern. The pattern is repeated if it does not fit the + * entire rectangle. Differs from other functions that it uses a transform, does not support + * composite ops in turn. + * @param rc rectangle to fill. + * @param pattern pattern to use. + * @param transform transformation to apply to the pattern. + */ + void fillRect(const QRect& rc, const KoPatternSP pattern, const QTransform transform); + /** + * Fill a rectangle with a certain pattern. The pattern is repeated if it does not fit the + * entire rectangle. + * + * This one supports transforms, but does not use blitting. + */ + void fillRect(qint32 x1, qint32 y1, qint32 w, qint32 h, const KisPaintDeviceSP device, const QRect& deviceRect, const QTransform transform); + /** * Fill the specified area with the output of the generator plugin that is configured * in the generator parameter */ void fillRect(qint32 x1, qint32 y1, qint32 w, qint32 h, const KisFilterConfigurationSP generator); /** * Fills the enclosed area around the point with the set color. If * there is a selection, the whole selection is filled. Note that * you must have set the width and height on the painter if you * don't have a selection. * * @param startX the X position where the floodfill starts * @param startY the Y position where the floodfill starts * @param sourceDevice the sourceDevice that determines the area that * is floodfilled if sampleMerged is on */ void fillColor(int startX, int startY, KisPaintDeviceSP sourceDevice); /** * Fills the enclosed area around the point with the set pattern. * If there is a selection, the whole selection is filled. Note * that you must have set the width and height on the painter if * you don't have a selection. * * @param startX the X position where the floodfill starts * @param startY the Y position where the floodfill starts * @param sourceDevice the sourceDevice that determines the area that * is floodfilled if sampleMerged is on + * @param patternTransform transform applied to the pattern; */ - void fillPattern(int startX, int startY, KisPaintDeviceSP sourceDevice); + void fillPattern(int startX, int startY, KisPaintDeviceSP sourceDevice, QTransform patternTransform = QTransform()); /** * Returns a selection mask for the floodfill starting at the specified position. * This variant basically creates a new selection object and passes it down * to the other variant of the function. * * @param startX the X position where the floodfill starts * @param startY the Y position where the floodfill starts * @param sourceDevice the sourceDevice that determines the area that * is floodfilled if sampleMerged is on */ KisPixelSelectionSP createFloodSelection(int startX, int startY, KisPaintDeviceSP sourceDevice); /** * Returns a selection mask for the floodfill starting at the specified position. * This variant requires an empty selection object. It is used in cases where the pointer * to the selection must be known beforehand, for example when the selection is filled * in a stroke and then the pointer to the pixel selection is needed later. * * @param selection empty new selection object * @param startX the X position where the floodfill starts * @param startY the Y position where the floodfill starts * @param sourceDevice the sourceDevice that determines the area that * is floodfilled if sampleMerged is on */ KisPixelSelectionSP createFloodSelection(KisPixelSelectionSP newSelection, int startX, int startY, KisPaintDeviceSP sourceDevice); /** * Set the threshold for floodfill. The range is 0-255: 0 means the fill will only * fill parts that are the exact same color, 255 means anything will be filled */ void setFillThreshold(int threshold); /** Returns the fill threshold, see setFillThreshold for details */ int fillThreshold() const { return m_threshold; } bool useCompositioning() const { return m_useCompositioning; } void setUseCompositioning(bool useCompositioning) { m_useCompositioning = useCompositioning; } /** Sets the width of the paint device */ void setWidth(int w) { m_width = w; } /** Sets the height of the paint device */ void setHeight(int h) { m_height = h; } /** If true, floodfill doesn't fill outside the selected area of a layer */ bool careForSelection() const { return m_careForSelection; } /** Set caring for selection. See careForSelection for details */ void setCareForSelection(bool set) { m_careForSelection = set; } /** Sets the auto growth/shrinking radius */ void setSizemod(int sizemod) { m_sizemod = sizemod; } /** Sets how much to auto-grow or shrink (if @p sizemod is negative) the selection flood before painting, this affects every fill operation except fillRect */ int sizemod() { return m_sizemod; } /** Sets feathering radius */ void setFeather(int feather) { m_feather = feather; } /** defines the feathering radius for selection flood operations, this affects every fill operation except fillRect */ uint feather() { return m_feather; } private: // for floodfill void genericFillStart(int startX, int startY, KisPaintDeviceSP sourceDevice); void genericFillEnd(KisPaintDeviceSP filled); KisSelectionSP m_fillSelection; int m_feather; int m_sizemod; int m_threshold; int m_width, m_height; QRect m_rect; bool m_careForSelection; bool m_useCompositioning; }; inline void KisFillPainter::fillRect(qint32 x, qint32 y, qint32 w, qint32 h, const KoColor& c) { fillRect(x, y, w, h, c, OPACITY_OPAQUE_U8); } inline void KisFillPainter::fillRect(const QRect& rc, const KoColor& c) { fillRect(rc.x(), rc.y(), rc.width(), rc.height(), c, OPACITY_OPAQUE_U8); } inline void KisFillPainter::eraseRect(qint32 x1, qint32 y1, qint32 w, qint32 h) { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KoColor c(Qt::black, cs); fillRect(x1, y1, w, h, c, OPACITY_TRANSPARENT_U8); } inline void KisFillPainter::eraseRect(const QRect& rc) { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KoColor c(Qt::black, cs); fillRect(rc.x(), rc.y(), rc.width(), rc.height(), c, OPACITY_TRANSPARENT_U8); } inline void KisFillPainter::fillRect(const QRect& rc, const KoColor& c, quint8 opacity) { fillRect(rc.x(), rc.y(), rc.width(), rc.height(), c, opacity); } inline void KisFillPainter::setFillThreshold(int threshold) { m_threshold = threshold; } #endif //KIS_FILL_PAINTER_H_ diff --git a/libs/image/kis_image.cc b/libs/image/kis_image.cc index 7b1d3b67f9..ec37e230be 100644 --- a/libs/image/kis_image.cc +++ b/libs/image/kis_image.cc @@ -1,2377 +1,2376 @@ /* * 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 "KoColorSpaceRegistry.h" #include "KoColor.h" #include "KoColorProfile.h" #include #include "KisProofingConfiguration.h" #include "kis_adjustment_layer.h" #include "kis_annotation.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_node.h" #include "kis_types.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 "processing/kis_convert_color_space_processing_visitor.h" #include "processing/kis_assign_profile_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_do_something_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" #include "KisBusyWaitBroker.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.setSuspendResumeUpdatesStrokeStrategyFactory( [=]() { KisSuspendProjectionUpdatesStrokeStrategy::SharedDataSP data = KisSuspendProjectionUpdatesStrokeStrategy::createSharedData(); KisSuspendResumePair suspend(new KisSuspendProjectionUpdatesStrokeStrategy(KisImageWSP(q), true, data), KisSuspendProjectionUpdatesStrokeStrategy::createSuspendJobsData(KisImageWSP(q))); KisSuspendResumePair resume(new KisSuspendProjectionUpdatesStrokeStrategy(KisImageWSP(q), false, data), KisSuspendProjectionUpdatesStrokeStrategy::createResumeJobsData(KisImageWSP(q))); return std::make_pair(suspend, resume); }); } 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; // filters are applied in a reversed way, from rbegin() to rend() QVector projectionUpdatesFilters; QStack disabledUpdatesCookies; 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); void convertImageColorSpaceImpl(const KoColorSpace *dstColorSpace, bool convertLayers, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags); struct SetImageProjectionColorSpace; }; 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() { /** * 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 || rhs.m_d->overlaySelectionMask) { 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; } if (rhs.m_d->overlaySelectionMask && KisNodeSP(rhs.m_d->overlaySelectionMask) == refNode) { m_d->targetOverlaySelectionMask = dynamic_cast(node.data()); m_d->overlaySelectionMask = m_d->targetOverlaySelectionMask; m_d->rootLayer->notifyChildMaskChanged(); } }); } 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->projectionUpdatesFilters.isEmpty()); KIS_ASSERT_RECOVER_NOOP(!rhs.m_d->disableUIUpdateSignals); KIS_ASSERT_RECOVER_NOOP(!rhs.m_d->disableDirtyRequests); m_d->blockLevelOfDetail = rhs.m_d->blockLevelOfDetail; #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(QLatin1String("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, i18n("Selection Mask")); 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(); KisBusyWaitBroker::instance()->notifyWaitOnImageStarted(this); m_d->scheduler.barrierLock(); KisBusyWaitBroker::instance()->notifyWaitOnImageEnded(this); 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(); KisBusyWaitBroker::instance()->notifyWaitOnImageStarted(this); m_d->scheduler.lock(); KisBusyWaitBroker::instance()->notifyWaitOnImageEnded(this); } 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::purgeUnusedData(bool isCancellable) { /** * WARNING: don't use this function unless you know what you are doing! * * It breaks undo on layers! Therefore, after calling it, KisImage is not * undo-capable anymore! */ struct PurgeUnusedDataStroke : public KisRunnableBasedStrokeStrategy { PurgeUnusedDataStroke(KisImageSP image, bool isCancellable) : KisRunnableBasedStrokeStrategy(QLatin1String("purge-unused-data"), kundo2_noi18n("purge-unused-data")), m_image(image) { this->enableJob(JOB_INIT, true, KisStrokeJobData::BARRIER, KisStrokeJobData::EXCLUSIVE); this->enableJob(JOB_DOSTROKE, true); setClearsRedoOnStart(false); setRequestsOtherStrokesToEnd(!isCancellable); setCanForgetAboutMe(isCancellable); } void initStrokeCallback() { KisPaintDeviceList deviceList; QVector jobsData; KisLayerUtils::recursiveApplyNodes(m_image->root(), [&deviceList](KisNodeSP node) { deviceList << node->getLodCapableDevices(); }); Q_FOREACH (KisPaintDeviceSP device, deviceList) { if (!device) continue; KritaUtils::addJobConcurrent(jobsData, [device] () { const_cast(device.data())->purgeDefaultPixels(); }); } addMutatedJobs(jobsData); } private: KisImageSP m_image; }; KisStrokeId id = startStroke(new PurgeUnusedDataStroke(this, isCancellable)); endStroke(id); } 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::convertLayerColorSpace(KisNodeSP node, const KoColorSpace *dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { if (!node->projectionLeaf()->isLayer()) return; const KoColorSpace *srcColorSpace = node->colorSpace(); if (!dstColorSpace || *srcColorSpace == *dstColorSpace) return; KUndo2MagicString actionName = kundo2_i18n("Convert Layer Color Space"); KisImageSignalVector emitSignals; emitSignals << ModifiedSignal; KisProcessingApplicator applicator(this, node, KisProcessingApplicator::RECURSIVE, emitSignals, actionName); applicator.applyVisitor( new KisConvertColorSpaceProcessingVisitor( srcColorSpace, dstColorSpace, renderingIntent, conversionFlags), KisStrokeJobData::CONCURRENT); applicator.end(); } struct KisImage::KisImagePrivate::SetImageProjectionColorSpace : public KisCommandUtils::FlipFlopCommand { SetImageProjectionColorSpace(const KoColorSpace *cs, KisImageWSP image, State initialState, KUndo2Command *parent = 0) : KisCommandUtils::FlipFlopCommand(initialState, parent), m_cs(cs), m_image(image) { } void partA() override { KisImageSP image = m_image; if (image) { image->setProjectionColorSpace(m_cs); } } private: const KoColorSpace *m_cs; KisImageWSP m_image; }; void KisImage::KisImagePrivate::convertImageColorSpaceImpl(const KoColorSpace *dstColorSpace, bool convertLayers, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { const KoColorSpace *srcColorSpace = this->colorSpace; if (!dstColorSpace || *srcColorSpace == *dstColorSpace) return; const KUndo2MagicString actionName = convertLayers ? kundo2_i18n("Convert Image Color Space") : kundo2_i18n("Convert Projection Color Space"); KisImageSignalVector emitSignals; emitSignals << ColorSpaceChangedSignal; emitSignals << ModifiedSignal; KisProcessingApplicator applicator(q, this->rootLayer, KisProcessingApplicator::RECURSIVE | KisProcessingApplicator::NO_UI_UPDATES, emitSignals, actionName); applicator.applyCommand( new KisImagePrivate::SetImageProjectionColorSpace(dstColorSpace, KisImageWSP(q), KisCommandUtils::FlipFlopCommand::INITIALIZING), KisStrokeJobData::BARRIER); if (convertLayers) { applicator.applyVisitor( new KisConvertColorSpaceProcessingVisitor( srcColorSpace, dstColorSpace, renderingIntent, conversionFlags), KisStrokeJobData::CONCURRENT); } else { applicator.applyCommand( new KisDoSomethingCommand< KisDoSomethingCommandOps::ResetOp, KisGroupLayerSP> (this->rootLayer, false)); applicator.applyCommand( new KisDoSomethingCommand< KisDoSomethingCommandOps::ResetOp, KisGroupLayerSP> (this->rootLayer, true)); } applicator.applyCommand( new KisImagePrivate::SetImageProjectionColorSpace(srcColorSpace, KisImageWSP(q), KisCommandUtils::FlipFlopCommand::FINALIZING), KisStrokeJobData::BARRIER); applicator.end(); } void KisImage::convertImageColorSpace(const KoColorSpace *dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { m_d->convertImageColorSpaceImpl(dstColorSpace, true, renderingIntent, conversionFlags); } void KisImage::convertImageProjectionColorSpace(const KoColorSpace *dstColorSpace) { m_d->convertImageColorSpaceImpl(dstColorSpace, false, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); } bool KisImage::assignLayerProfile(KisNodeSP node, const KoColorProfile *profile) { const KoColorSpace *srcColorSpace = node->colorSpace(); if (!node->projectionLeaf()->isLayer()) return false; if (!profile || *srcColorSpace->profile() == *profile) return false; KUndo2MagicString actionName = kundo2_i18n("Assign Profile to Layer"); KisImageSignalVector emitSignals; emitSignals << ModifiedSignal; const KoColorSpace *dstColorSpace = KoColorSpaceRegistry::instance()->colorSpace(colorSpace()->colorModelId().id(), colorSpace()->colorDepthId().id(), profile); if (!dstColorSpace) return false; KisProcessingApplicator applicator(this, node, KisProcessingApplicator::RECURSIVE | KisProcessingApplicator::NO_UI_UPDATES, emitSignals, actionName); applicator.applyVisitor( new KisAssignProfileProcessingVisitor( srcColorSpace, dstColorSpace), KisStrokeJobData::CONCURRENT); applicator.end(); return true; } bool KisImage::assignImageProfile(const KoColorProfile *profile, bool blockAllUpdates) { if (!profile) return false; const KoColorSpace *srcColorSpace = m_d->colorSpace; bool imageProfileIsSame = *srcColorSpace->profile() == *profile; imageProfileIsSame &= !KisLayerUtils::recursiveFindNode(m_d->rootLayer, [profile] (KisNodeSP node) { return *node->colorSpace()->profile() != *profile; }); if (imageProfileIsSame) { dbgImage << "Trying to set the same image profile again" << ppVar(srcColorSpace->profile()->name()) << ppVar(profile->name()); return true; } KUndo2MagicString actionName = kundo2_i18n("Assign Profile"); KisImageSignalVector emitSignals; emitSignals << ProfileChangedSignal; emitSignals << ModifiedSignal; const KoColorSpace *dstColorSpace = KoColorSpaceRegistry::instance()->colorSpace(colorSpace()->colorModelId().id(), colorSpace()->colorDepthId().id(), profile); if (!dstColorSpace) return false; KisProcessingApplicator applicator(this, m_d->rootLayer, KisProcessingApplicator::RECURSIVE | (!blockAllUpdates ? KisProcessingApplicator::NO_UI_UPDATES : KisProcessingApplicator::NO_IMAGE_UPDATES), emitSignals, actionName); applicator.applyCommand( new KisImagePrivate::SetImageProjectionColorSpace(dstColorSpace, KisImageWSP(this), KisCommandUtils::FlipFlopCommand::INITIALIZING), KisStrokeJobData::BARRIER); applicator.applyVisitor( new KisAssignProfileProcessingVisitor( srcColorSpace, dstColorSpace), KisStrokeJobData::CONCURRENT); applicator.applyCommand( new KisImagePrivate::SetImageProjectionColorSpace(srcColorSpace, KisImageWSP(this), KisCommandUtils::FlipFlopCommand::FINALIZING), KisStrokeJobData::BARRIER); applicator.end(); return true; } void KisImage::setProjectionColorSpace(const KoColorSpace * colorSpace) { m_d->colorSpace = colorSpace; } 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(); KisBusyWaitBroker::instance()->notifyWaitOnImageStarted(this); m_d->scheduler.waitForDone(); KisBusyWaitBroker::instance()->notifyWaitOnImageEnded(this); } 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(QLatin1String("start-isolated-mode"), kundo2_noi18n("start-isolated-mode")), m_node(node), m_image(image), m_needsFullRefresh(false) { this->enableJob(JOB_INIT, true, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); this->enableJob(JOB_DOSTROKE, true); this->enableJob(JOB_FINISH, true, KisStrokeJobData::BARRIER); setClearsRedoOnStart(false); } void initStrokeCallback() override { // pass-though node don't have any projection prepared, so we should // explicitly regenerate it before activating isolated mode. m_node->projectionLeaf()->explicitlyRegeneratePassThroughProjection(); const bool beforeVisibility = m_node->projectionLeaf()->visible(); m_image->m_d->isolatedRootNode = m_node; emit m_image->sigIsolatedModeChanged(); const bool afterVisibility = m_node->projectionLeaf()->visible(); m_needsFullRefresh = (beforeVisibility != afterVisibility); } void finishStrokeCallback() override { // the GUI uses our thread to do the color space conversion so we // need to emit this signal in multiple threads if (m_needsFullRefresh) { m_image->refreshGraphAsync(m_node); } else { 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; bool m_needsFullRefresh; }; 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(QLatin1String("stop-isolated-mode"), kundo2_noi18n("stop-isolated-mode")), m_image(image), m_oldRootNode(nullptr), m_oldNodeNeedsRefresh(false) { this->enableJob(JOB_INIT); this->enableJob(JOB_DOSTROKE, true); this->enableJob(JOB_FINISH, true, KisStrokeJobData::BARRIER); setClearsRedoOnStart(false); } void initStrokeCallback() { if (!m_image->m_d->isolatedRootNode) return; m_oldRootNode = m_image->m_d->isolatedRootNode; const bool beforeVisibility = m_oldRootNode->projectionLeaf()->visible(); m_image->m_d->isolatedRootNode = 0; emit m_image->sigIsolatedModeChanged(); const bool afterVisibility = m_oldRootNode->projectionLeaf()->visible(); m_oldNodeNeedsRefresh = (beforeVisibility != afterVisibility); } void finishStrokeCallback() override { m_image->invalidateAllFrames(); if (m_oldNodeNeedsRefresh){ m_oldRootNode->setDirty(m_image->bounds()); } else { // TODO: Substitute notifyProjectionUpdated() with this code // when update optimization is implemented // // QRect updateRect = bounds() | oldRootNode->extent(); //oldRootNode->setDirty(updateRect); QVector jobs; m_image->m_d->notifyProjectionUpdatedInPatches(m_image->bounds(), jobs); this->runnableJobsInterface()->addRunnableJobs(jobs); } } private: KisImageSP m_image; KisNodeSP m_oldRootNode; bool m_oldNodeNeedsRefresh; }; 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) { refreshGraphAsync(root, QVector({rc}), cropRect); } void KisImage::refreshGraphAsync(KisNodeSP root, const QVector &rects, const QRect &cropRect) { if (!root) root = m_d->rootLayer; /** * We iterate through the filters in a reversed way. It makes the most nested filters * to execute first. */ for (auto it = m_d->projectionUpdatesFilters.rbegin(); it != m_d->projectionUpdatesFilters.rend(); ++it) { KIS_SAFE_ASSERT_RECOVER(*it) { continue; } if ((*it)->filterRefreshGraph(this, root.data(), rects, cropRect)) { return; } } m_d->animationInterface->notifyNodeChanged(root.data(), rects, true); m_d->scheduler.fullRefreshAsync(root, rects, cropRect); } void KisImage::requestProjectionUpdateNoFilthy(KisNodeSP pseudoFilthy, const QRect &rc, const QRect &cropRect) { requestProjectionUpdateNoFilthy(pseudoFilthy, rc, cropRect, true); } void KisImage::requestProjectionUpdateNoFilthy(KisNodeSP pseudoFilthy, const QRect &rc, const QRect &cropRect, const bool resetAnimationCache) { KIS_ASSERT_RECOVER_RETURN(pseudoFilthy); if (resetAnimationCache) { 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(); } KisProjectionUpdatesFilterCookie KisImage::addProjectionUpdatesFilter(KisProjectionUpdatesFilterSP filter) { KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(filter, KisProjectionUpdatesFilterCookie()); m_d->projectionUpdatesFilters.append(filter); return KisProjectionUpdatesFilterCookie(filter.data()); } KisProjectionUpdatesFilterSP KisImage::removeProjectionUpdatesFilter(KisProjectionUpdatesFilterCookie cookie) { KIS_SAFE_ASSERT_RECOVER_NOOP(cookie); KIS_SAFE_ASSERT_RECOVER_NOOP(m_d->projectionUpdatesFilters.last() == cookie); auto it = std::find(m_d->projectionUpdatesFilters.begin(), m_d->projectionUpdatesFilters.end(), cookie); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(it != m_d->projectionUpdatesFilters.end(), KisProjectionUpdatesFilterSP()); KisProjectionUpdatesFilterSP filter = *it; m_d->projectionUpdatesFilters.erase(it); return filter; } KisProjectionUpdatesFilterCookie KisImage::currentProjectionUpdatesFilter() const { return !m_d->projectionUpdatesFilters.isEmpty() ? m_d->projectionUpdatesFilters.last().data() : KisProjectionUpdatesFilterCookie(); } void KisImage::disableDirtyRequests() { m_d->disabledUpdatesCookies.push( addProjectionUpdatesFilter(toQShared(new KisDropAllProjectionUpdatesFilter()))); } void KisImage::enableDirtyRequests() { KIS_SAFE_ASSERT_RECOVER_RETURN(!m_d->disabledUpdatesCookies.isEmpty()); removeProjectionUpdatesFilter(m_d->disabledUpdatesCookies.pop()); } 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 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) { /** * We iterate through the filters in a reversed way. It makes the most nested filters * to execute first. */ for (auto it = m_d->projectionUpdatesFilters.rbegin(); it != m_d->projectionUpdatesFilters.rend(); ++it) { KIS_SAFE_ASSERT_RECOVER(*it) { continue; } if ((*it)->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_indirect_painting_support.cpp b/libs/image/kis_indirect_painting_support.cpp index 2e01fee657..50cadbeab1 100644 --- a/libs/image/kis_indirect_painting_support.cpp +++ b/libs/image/kis_indirect_painting_support.cpp @@ -1,179 +1,182 @@ /* * Copyright (c) 2004 Bart Coppens * 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 //MSVC requires that Vc come first #include "kis_indirect_painting_support.h" #include #include #include #include #include "kis_layer.h" #include "kis_paint_layer.h" #include "kis_paint_device.h" #include "kis_selection.h" #include "kis_painter.h" struct Q_DECL_HIDDEN KisIndirectPaintingSupport::Private { // To simulate the indirect painting KisPaintDeviceSP temporaryTarget; QString compositeOp; quint8 compositeOpacity; QBitArray channelFlags; KisSelectionSP selection; QReadWriteLock lock; }; KisIndirectPaintingSupport::KisIndirectPaintingSupport() : d(new Private) { } KisIndirectPaintingSupport::~KisIndirectPaintingSupport() { delete d; } void KisIndirectPaintingSupport::setCurrentColor(const KoColor &color) { Q_UNUSED(color); } void KisIndirectPaintingSupport::setTemporaryTarget(KisPaintDeviceSP t) { d->temporaryTarget = t; } void KisIndirectPaintingSupport::setTemporaryCompositeOp(const QString &id) { d->compositeOp = id; } void KisIndirectPaintingSupport::setTemporaryOpacity(quint8 o) { d->compositeOpacity = o; } void KisIndirectPaintingSupport::setTemporaryChannelFlags(const QBitArray& channelFlags) { d->channelFlags = channelFlags; } void KisIndirectPaintingSupport::setTemporarySelection(KisSelectionSP selection) { d->selection = selection; } void KisIndirectPaintingSupport::lockTemporaryTarget() const { d->lock.lockForRead(); } void KisIndirectPaintingSupport::lockTemporaryTargetForWrite() const { d->lock.lockForWrite(); } void KisIndirectPaintingSupport::unlockTemporaryTarget() const { d->lock.unlock(); } KisPaintDeviceSP KisIndirectPaintingSupport::temporaryTarget() const { return d->temporaryTarget; } bool KisIndirectPaintingSupport::supportsNonIndirectPainting() const { return true; } QString KisIndirectPaintingSupport::temporaryCompositeOp() const { return d->compositeOp; } KisSelectionSP KisIndirectPaintingSupport::temporarySelection() const { return d->selection; } bool KisIndirectPaintingSupport::hasTemporaryTarget() const { return d->temporaryTarget; } void KisIndirectPaintingSupport::setupTemporaryPainter(KisPainter *painter) const { painter->setOpacity(d->compositeOpacity); painter->setCompositeOp(d->compositeOp); painter->setChannelFlags(d->channelFlags); painter->setSelection(d->selection); } void KisIndirectPaintingSupport::mergeToLayer(KisNodeSP layer, KisPostExecutionUndoAdapter *undoAdapter, const KUndo2MagicString &transactionText,int timedID) { QWriteLocker l(&d->lock); mergeToLayerImpl(layer->paintDevice(), undoAdapter, transactionText, timedID); } void KisIndirectPaintingSupport::mergeToLayerImpl(KisPaintDeviceSP dst, KisPostExecutionUndoAdapter *undoAdapter, const KUndo2MagicString &transactionText, int timedID, bool cleanResources) { /** - * We do not apply selection here, because it has already - * been taken into account in a tool code + * Brushes don't apply the selection, we apply that during the indirect + * painting merge operation. It is cheaper calculation-wise. */ KisPainter gc(dst); setupTemporaryPainter(&gc); /** * Scratchpad may not have an undo adapter */ if(undoAdapter) { gc.beginTransaction(transactionText,timedID); } writeMergeData(&gc, d->temporaryTarget); if (cleanResources) { releaseResources(); } if(undoAdapter) { gc.endTransaction(undoAdapter); } } void KisIndirectPaintingSupport::writeMergeData(KisPainter *painter, KisPaintDeviceSP src) { Q_FOREACH (const QRect &rc, src->region().rects()) { painter->bitBlt(rc.topLeft(), src, rc); } } void KisIndirectPaintingSupport::releaseResources() { d->temporaryTarget = 0; d->selection = 0; + d->compositeOp = COMPOSITE_OVER; + d->compositeOpacity = OPACITY_OPAQUE_U8; + d->channelFlags.clear(); } diff --git a/libs/image/kis_layer.cc b/libs/image/kis_layer.cc index 1c326b894b..d4a0766823 100644 --- a/libs/image/kis_layer.cc +++ b/libs/image/kis_layer.cc @@ -1,1006 +1,1006 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2005 C. Boemann * Copyright (c) 2009 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_layer.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_debug.h" #include "kis_image.h" #include "kis_painter.h" #include "kis_mask.h" #include "kis_effect_mask.h" #include "kis_selection_mask.h" #include "kis_meta_data_store.h" #include "kis_selection.h" #include "kis_paint_layer.h" #include "kis_raster_keyframe_channel.h" #include "kis_clone_layer.h" #include "kis_psd_layer_style.h" #include "kis_layer_projection_plane.h" #include "layerstyles/kis_layer_style_projection_plane.h" #include "krita_utils.h" #include "kis_layer_properties_icons.h" #include "kis_layer_utils.h" #include "kis_projection_leaf.h" #include "KisSafeNodeProjectionStore.h" class KisCloneLayersList { public: void addClone(KisCloneLayerWSP cloneLayer) { m_clonesList.append(cloneLayer); } void removeClone(KisCloneLayerWSP cloneLayer) { m_clonesList.removeOne(cloneLayer); } void setDirty(const QRect &rect) { Q_FOREACH (KisCloneLayerSP clone, m_clonesList) { if (clone) { clone->setDirtyOriginal(rect); } } } const QList registeredClones() const { return m_clonesList; } bool hasClones() const { return !m_clonesList.isEmpty(); } private: QList m_clonesList; }; class KisLayerMasksCache { public: KisLayerMasksCache(KisLayer *parent) : m_parent(parent) { } KisSelectionMaskSP selectionMask() { QReadLocker readLock(&m_lock); if (!m_isSelectionMaskValid) { readLock.unlock(); QWriteLocker writeLock(&m_lock); if (!m_isSelectionMaskValid) { KoProperties properties; properties.setProperty("active", true); properties.setProperty("visible", true); QList masks = m_parent->childNodes(QStringList("KisSelectionMask"), properties); // return the first visible mask Q_FOREACH (KisNodeSP mask, masks) { if (mask) { m_selectionMask = dynamic_cast(mask.data()); break; } } m_isSelectionMaskValid = true; } // return under write lock return m_selectionMask; } // return under read lock return m_selectionMask; } QList effectMasks() { QReadLocker readLock(&m_lock); if (!m_isEffectMasksValid) { readLock.unlock(); QWriteLocker writeLock(&m_lock); if (!m_isEffectMasksValid) { m_effectMasks = m_parent->searchEffectMasks(0); m_isEffectMasksValid = true; } // return under write lock return m_effectMasks; } // return under read lock return m_effectMasks; } void setDirty() { QWriteLocker l(&m_lock); m_isSelectionMaskValid = false; m_isEffectMasksValid = false; m_selectionMask = 0; m_effectMasks.clear(); } private: KisLayer *m_parent; QReadWriteLock m_lock; bool m_isSelectionMaskValid = false; bool m_isEffectMasksValid = false; KisSelectionMaskSP m_selectionMask; QList m_effectMasks; }; struct Q_DECL_HIDDEN KisLayer::Private { Private(KisLayer *q) : masksCache(q) { } QBitArray channelFlags; KisMetaData::Store* metaDataStore; KisCloneLayersList clonesList; KisPSDLayerStyleSP layerStyle; KisLayerStyleProjectionPlaneSP layerStyleProjectionPlane; KisLayerProjectionPlaneSP projectionPlane; KisSafeNodeProjectionStoreSP safeProjection; KisLayerMasksCache masksCache; }; KisLayer::KisLayer(KisImageWSP image, const QString &name, quint8 opacity) : KisNode(image) , m_d(new Private(this)) { setName(name); setOpacity(opacity); m_d->metaDataStore = new KisMetaData::Store(); m_d->projectionPlane = toQShared(new KisLayerProjectionPlane(this)); m_d->safeProjection = new KisSafeNodeProjectionStore(); m_d->safeProjection->setImage(image); } KisLayer::KisLayer(const KisLayer& rhs) : KisNode(rhs) , m_d(new Private(this)) { if (this != &rhs) { m_d->metaDataStore = new KisMetaData::Store(*rhs.m_d->metaDataStore); m_d->channelFlags = rhs.m_d->channelFlags; setName(rhs.name()); m_d->projectionPlane = toQShared(new KisLayerProjectionPlane(this)); m_d->safeProjection = new KisSafeNodeProjectionStore(*rhs.m_d->safeProjection); m_d->safeProjection->setImage(image()); if (rhs.m_d->layerStyle) { m_d->layerStyle = rhs.m_d->layerStyle->clone().dynamicCast(); if (rhs.m_d->layerStyleProjectionPlane) { m_d->layerStyleProjectionPlane = toQShared( new KisLayerStyleProjectionPlane(*rhs.m_d->layerStyleProjectionPlane, this, m_d->layerStyle)); } } } } KisLayer::~KisLayer() { delete m_d->metaDataStore; delete m_d; } const KoColorSpace * KisLayer::colorSpace() const { KisImageSP image = this->image(); if (!image) { return nullptr; } return image->colorSpace(); } const KoCompositeOp * KisLayer::compositeOp() const { /** * FIXME: This function duplicates the same function from * KisMask. We can't move it to KisBaseNode as it doesn't * know anything about parent() method of KisNode * Please think it over... */ KisNodeSP parentNode = parent(); if (!parentNode) return 0; if (!parentNode->colorSpace()) return 0; const KoCompositeOp* op = parentNode->colorSpace()->compositeOp(compositeOpId()); return op ? op : parentNode->colorSpace()->compositeOp(COMPOSITE_OVER); } KisPSDLayerStyleSP KisLayer::layerStyle() const { return m_d->layerStyle; } void KisLayer::setLayerStyle(KisPSDLayerStyleSP layerStyle) { if (layerStyle) { m_d->layerStyle = layerStyle; KisLayerStyleProjectionPlaneSP plane = !layerStyle->isEmpty() ? KisLayerStyleProjectionPlaneSP(new KisLayerStyleProjectionPlane(this)) : KisLayerStyleProjectionPlaneSP(0); m_d->layerStyleProjectionPlane = plane; } else { m_d->layerStyleProjectionPlane.clear(); m_d->layerStyle.clear(); } } KisBaseNode::PropertyList KisLayer::sectionModelProperties() const { KisBaseNode::PropertyList l = KisBaseNode::sectionModelProperties(); l << KisBaseNode::Property(KoID("opacity", i18n("Opacity")), i18n("%1%", percentOpacity())); const KoCompositeOp * compositeOp = this->compositeOp(); if (compositeOp) { l << KisBaseNode::Property(KoID("compositeop", i18n("Blending Mode")), compositeOp->description()); } if (m_d->layerStyle && !m_d->layerStyle->isEmpty()) { l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::layerStyle, m_d->layerStyle->isEnabled()); } l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::inheritAlpha, alphaChannelDisabled()); return l; } void KisLayer::setSectionModelProperties(const KisBaseNode::PropertyList &properties) { KisBaseNode::setSectionModelProperties(properties); Q_FOREACH (const KisBaseNode::Property &property, properties) { if (property.id == KisLayerPropertiesIcons::inheritAlpha.id()) { disableAlphaChannel(property.state.toBool()); } if (property.id == KisLayerPropertiesIcons::layerStyle.id()) { if (m_d->layerStyle && m_d->layerStyle->isEnabled() != property.state.toBool()) { m_d->layerStyle->setEnabled(property.state.toBool()); baseNodeChangedCallback(); baseNodeInvalidateAllFramesCallback(); } } } } void KisLayer::disableAlphaChannel(bool disable) { QBitArray newChannelFlags = m_d->channelFlags; if(newChannelFlags.isEmpty()) newChannelFlags = colorSpace()->channelFlags(true, true); if(disable) newChannelFlags &= colorSpace()->channelFlags(true, false); else newChannelFlags |= colorSpace()->channelFlags(false, true); setChannelFlags(newChannelFlags); } bool KisLayer::alphaChannelDisabled() const { QBitArray flags = colorSpace()->channelFlags(false, true) & m_d->channelFlags; return flags.count(true) == 0 && !m_d->channelFlags.isEmpty(); } void KisLayer::setChannelFlags(const QBitArray & channelFlags) { Q_ASSERT(channelFlags.isEmpty() ||((quint32)channelFlags.count() == colorSpace()->channelCount())); if (KritaUtils::compareChannelFlags(channelFlags, this->channelFlags())) { return; } if (!channelFlags.isEmpty() && channelFlags == QBitArray(channelFlags.size(), true)) { m_d->channelFlags.clear(); } else { m_d->channelFlags = channelFlags; } baseNodeChangedCallback(); baseNodeInvalidateAllFramesCallback(); } QBitArray & KisLayer::channelFlags() const { return m_d->channelFlags; } bool KisLayer::temporary() const { return nodeProperties().boolProperty("temporary", false); } void KisLayer::setTemporary(bool t) { setNodeProperty("temporary", t); } void KisLayer::setImage(KisImageWSP image) { // we own the projection device, so we should take care about it KisPaintDeviceSP projection = this->projection(); if (projection && projection != original()) { projection->setDefaultBounds(new KisDefaultBounds(image)); } m_d->safeProjection->setImage(image); KisNode::setImage(image); } bool KisLayer::canMergeAndKeepBlendOptions(KisLayerSP otherLayer) { return this->compositeOpId() == otherLayer->compositeOpId() && this->opacity() == otherLayer->opacity() && this->channelFlags() == otherLayer->channelFlags() && !this->layerStyle() && !otherLayer->layerStyle() && (this->colorSpace() == otherLayer->colorSpace() || *this->colorSpace() == *otherLayer->colorSpace()); } KisLayerSP KisLayer::createMergedLayerTemplate(KisLayerSP prevLayer) { const bool keepBlendingOptions = canMergeAndKeepBlendOptions(prevLayer); KisLayerSP newLayer = new KisPaintLayer(image(), prevLayer->name(), OPACITY_OPAQUE_U8); if (keepBlendingOptions) { newLayer->setCompositeOpId(compositeOpId()); newLayer->setOpacity(opacity()); newLayer->setChannelFlags(channelFlags()); } return newLayer; } void KisLayer::fillMergedLayerTemplate(KisLayerSP dstLayer, KisLayerSP prevLayer) { const bool keepBlendingOptions = canMergeAndKeepBlendOptions(prevLayer); QRect layerProjectionExtent = this->projection()->extent(); QRect prevLayerProjectionExtent = prevLayer->projection()->extent(); bool alphaDisabled = this->alphaChannelDisabled(); bool prevAlphaDisabled = prevLayer->alphaChannelDisabled(); KisPaintDeviceSP mergedDevice = dstLayer->paintDevice(); if (!keepBlendingOptions) { KisPainter gc(mergedDevice); KisImageSP imageSP = image().toStrongRef(); if (!imageSP) { return; } //Copy the pixels of previous layer with their actual alpha value prevLayer->disableAlphaChannel(false); prevLayer->projectionPlane()->apply(&gc, prevLayerProjectionExtent | imageSP->bounds()); //Restore the previous prevLayer disableAlpha status for correct undo/redo prevLayer->disableAlphaChannel(prevAlphaDisabled); //Paint the pixels of the current layer, using their actual alpha value if (alphaDisabled == prevAlphaDisabled) { this->disableAlphaChannel(false); } this->projectionPlane()->apply(&gc, layerProjectionExtent | imageSP->bounds()); //Restore the layer disableAlpha status for correct undo/redo this->disableAlphaChannel(alphaDisabled); } else { //Copy prevLayer KisPaintDeviceSP srcDev = prevLayer->projection(); mergedDevice->makeCloneFrom(srcDev, srcDev->extent()); //Paint layer on the copy KisPainter gc(mergedDevice); gc.bitBlt(layerProjectionExtent.topLeft(), this->projection(), layerProjectionExtent); } } void KisLayer::registerClone(KisCloneLayerWSP clone) { m_d->clonesList.addClone(clone); } void KisLayer::unregisterClone(KisCloneLayerWSP clone) { m_d->clonesList.removeClone(clone); } const QList KisLayer::registeredClones() const { return m_d->clonesList.registeredClones(); } bool KisLayer::hasClones() const { return m_d->clonesList.hasClones(); } void KisLayer::updateClones(const QRect &rect) { m_d->clonesList.setDirty(rect); } void KisLayer::notifyChildMaskChanged() { m_d->masksCache.setDirty(); } KisSelectionMaskSP KisLayer::selectionMask() const { return m_d->masksCache.selectionMask(); } KisSelectionSP KisLayer::selection() const { KisSelectionMaskSP mask = selectionMask(); if (mask) { return mask->selection(); } KisImageSP image = this->image(); if (image) { return image->globalSelection(); } return KisSelectionSP(); } /////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////// QList KisLayer::effectMasks() const { return m_d->masksCache.effectMasks(); } QList KisLayer::effectMasks(KisNodeSP lastNode) const { if (lastNode.isNull()) { return effectMasks(); } else { // happens rarely. return searchEffectMasks(lastNode); } } QList KisLayer::searchEffectMasks(KisNodeSP lastNode) const { QList masks; KIS_SAFE_ASSERT_RECOVER_NOOP(projectionLeaf()); KisProjectionLeafSP child = projectionLeaf()->firstChild(); while (child) { if (child->node() == lastNode) break; KIS_SAFE_ASSERT_RECOVER_NOOP(child); KIS_SAFE_ASSERT_RECOVER_NOOP(child->node()); if (child->visible()) { KisEffectMaskSP mask = dynamic_cast(const_cast(child->node().data())); if (mask) { masks.append(mask); } } child = child->nextSibling(); } return masks; } bool KisLayer::hasEffectMasks() const { return !m_d->masksCache.effectMasks().isEmpty(); } QRect KisLayer::masksChangeRect(const QList &masks, const QRect &requestedRect, bool &rectVariesFlag) const { rectVariesFlag = false; QRect prevChangeRect = requestedRect; /** * We set default value of the change rect for the case * when there is no mask at all */ QRect changeRect = requestedRect; Q_FOREACH (const KisEffectMaskSP& mask, masks) { changeRect = mask->changeRect(prevChangeRect); if (changeRect != prevChangeRect) rectVariesFlag = true; prevChangeRect = changeRect; } return changeRect; } QRect KisLayer::masksNeedRect(const QList &masks, const QRect &changeRect, QStack &applyRects, bool &rectVariesFlag) const { rectVariesFlag = false; QRect prevNeedRect = changeRect; QRect needRect; for (qint32 i = masks.size() - 1; i >= 0; i--) { applyRects.push(prevNeedRect); needRect = masks[i]->needRect(prevNeedRect); if (prevNeedRect != needRect) rectVariesFlag = true; prevNeedRect = needRect; } return needRect; } KisNode::PositionToFilthy calculatePositionToFilthy(KisNodeSP nodeInQuestion, KisNodeSP filthy, KisNodeSP parent) { if (parent == filthy || parent != filthy->parent()) { return KisNode::N_ABOVE_FILTHY; } if (nodeInQuestion == filthy) { return KisNode::N_FILTHY; } KisNodeSP node = nodeInQuestion->prevSibling(); while (node) { if (node == filthy) { return KisNode::N_ABOVE_FILTHY; } node = node->prevSibling(); } return KisNode::N_BELOW_FILTHY; } QRect KisLayer::applyMasks(const KisPaintDeviceSP source, KisPaintDeviceSP destination, const QRect &requestedRect, KisNodeSP filthyNode, KisNodeSP lastNode) const { Q_ASSERT(source); Q_ASSERT(destination); QList masks = effectMasks(lastNode); QRect changeRect; QRect needRect; if (masks.isEmpty()) { changeRect = requestedRect; if (source != destination) { copyOriginalToProjection(source, destination, requestedRect); } } else { QStack applyRects; bool changeRectVaries; bool needRectVaries; /** * FIXME: Assume that varying of the changeRect has already * been taken into account while preparing walkers */ changeRectVaries = false; changeRect = requestedRect; //changeRect = masksChangeRect(masks, requestedRect, // changeRectVaries); needRect = masksNeedRect(masks, changeRect, applyRects, needRectVaries); if (!changeRectVaries && !needRectVaries) { /** * A bit of optimization: * All filters will read/write exactly from/to the requested * rect so we needn't create temporary paint device, * just apply it onto destination */ Q_ASSERT(needRect == requestedRect); if (source != destination) { copyOriginalToProjection(source, destination, needRect); } Q_FOREACH (const KisEffectMaskSP& mask, masks) { const QRect maskApplyRect = applyRects.pop(); const QRect maskNeedRect = applyRects.isEmpty() ? needRect : applyRects.top(); PositionToFilthy maskPosition = calculatePositionToFilthy(mask, filthyNode, const_cast(this)); mask->apply(destination, maskApplyRect, maskNeedRect, maskPosition); } Q_ASSERT(applyRects.isEmpty()); } else { /** * We can't eliminate additional copy-op * as filters' behaviour may be quite insane here, * so let them work on their own paintDevice =) */ KisPaintDeviceSP tempDevice = new KisPaintDevice(colorSpace()); tempDevice->prepareClone(source); copyOriginalToProjection(source, tempDevice, needRect); QRect maskApplyRect = applyRects.pop(); QRect maskNeedRect = needRect; Q_FOREACH (const KisEffectMaskSP& mask, masks) { PositionToFilthy maskPosition = calculatePositionToFilthy(mask, filthyNode, const_cast(this)); mask->apply(tempDevice, maskApplyRect, maskNeedRect, maskPosition); if (!applyRects.isEmpty()) { maskNeedRect = maskApplyRect; maskApplyRect = applyRects.pop(); } } Q_ASSERT(applyRects.isEmpty()); KisPainter::copyAreaOptimized(changeRect.topLeft(), tempDevice, destination, changeRect); } } return changeRect; } QRect KisLayer::updateProjection(const QRect& rect, KisNodeSP filthyNode) { QRect updatedRect = rect; KisPaintDeviceSP originalDevice = original(); if (!rect.isValid() || (!visible() && !isIsolatedRoot() && !hasClones()) || !originalDevice) return QRect(); if (!needProjection() && !hasEffectMasks()) { m_d->safeProjection->releaseDevice(); } else { if (!updatedRect.isEmpty()) { KisPaintDeviceSP projection = m_d->safeProjection->getDeviceLazy(originalDevice); updatedRect = applyMasks(originalDevice, projection, updatedRect, filthyNode, 0); } } return updatedRect; } QRect KisLayer::partialChangeRect(KisNodeSP lastNode, const QRect& rect) { bool changeRectVaries = false; QRect changeRect = outgoingChangeRect(rect); changeRect = masksChangeRect(effectMasks(lastNode), changeRect, changeRectVaries); return changeRect; } /** * \p rect is a dirty rect in layer's original() coordinates! */ void KisLayer::buildProjectionUpToNode(KisPaintDeviceSP projection, KisNodeSP lastNode, const QRect& rect) { QRect changeRect = partialChangeRect(lastNode, rect); KisPaintDeviceSP originalDevice = original(); KIS_ASSERT_RECOVER_RETURN(needProjection() || hasEffectMasks()); if (!changeRect.isEmpty()) { applyMasks(originalDevice, projection, changeRect, this, lastNode); } } bool KisLayer::needProjection() const { return false; } void KisLayer::copyOriginalToProjection(const KisPaintDeviceSP original, KisPaintDeviceSP projection, const QRect& rect) const { KisPainter::copyAreaOptimized(rect.topLeft(), original, projection, rect); } KisAbstractProjectionPlaneSP KisLayer::projectionPlane() const { return m_d->layerStyleProjectionPlane ? KisAbstractProjectionPlaneSP(m_d->layerStyleProjectionPlane) : KisAbstractProjectionPlaneSP(m_d->projectionPlane); } KisLayerProjectionPlaneSP KisLayer::internalProjectionPlane() const { return m_d->projectionPlane; } KisPaintDeviceSP KisLayer::projection() const { KisPaintDeviceSP originalDevice = original(); return needProjection() || hasEffectMasks() ? m_d->safeProjection->getDeviceLazy(originalDevice) : originalDevice; } QRect KisLayer::tightUserVisibleBounds() const { QRect changeRect = exactBounds(); /// we do not use incomingChangeRect() here, because /// exactBounds() already takes it into account (it /// was used while preparing original()) bool changeRectVaries; changeRect = outgoingChangeRect(changeRect); changeRect = masksChangeRect(effectMasks(), changeRect, changeRectVaries); return changeRect; } QRect KisLayer::changeRect(const QRect &rect, PositionToFilthy pos) const { QRect changeRect = rect; changeRect = incomingChangeRect(changeRect); if(pos == KisNode::N_FILTHY) { QRect projectionToBeUpdated = projection()->exactBoundsAmortized() & changeRect; bool changeRectVaries; changeRect = outgoingChangeRect(changeRect); changeRect = masksChangeRect(effectMasks(), changeRect, changeRectVaries); /** * If the projection contains some dirty areas we should also * add them to the change rect, because they might have * changed. E.g. when a visibility of the mask has chnaged * while the parent layer was invinisble. */ if (!projectionToBeUpdated.isEmpty() && !changeRect.contains(projectionToBeUpdated)) { changeRect |= projectionToBeUpdated; } } // TODO: string comparizon: optimize! if (pos != KisNode::N_FILTHY && pos != KisNode::N_FILTHY_PROJECTION && compositeOpId() != COMPOSITE_COPY) { changeRect |= rect; } return changeRect; } void KisLayer::childNodeChanged(KisNodeSP changedChildNode) { if (dynamic_cast(changedChildNode.data())) { notifyChildMaskChanged(); } } QRect KisLayer::incomingChangeRect(const QRect &rect) const { return rect; } QRect KisLayer::outgoingChangeRect(const QRect &rect) const { return rect; } QRect KisLayer::needRectForOriginal(const QRect &rect) const { QRect needRect = rect; const QList masks = effectMasks(); if (!masks.isEmpty()) { QStack applyRects; bool needRectVaries; needRect = masksNeedRect(masks, rect, applyRects, needRectVaries); } return needRect; } -QImage KisLayer::createThumbnail(qint32 w, qint32 h) +QImage KisLayer::createThumbnail(qint32 w, qint32 h, Qt::AspectRatioMode aspectRatioMode) { if (w == 0 || h == 0) { return QImage(); } KisPaintDeviceSP originalDevice = original(); return originalDevice ? - originalDevice->createThumbnail(w, h, 1, + originalDevice->createThumbnail(w, h, aspectRatioMode, 1, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()) : QImage(); } -QImage KisLayer::createThumbnailForFrame(qint32 w, qint32 h, int time) +QImage KisLayer::createThumbnailForFrame(qint32 w, qint32 h, int time, Qt::AspectRatioMode aspectRatioMode) { 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, + return targetDevice->createThumbnail(w, h, aspectRatioMode, 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 0590aa6be5..7418f11ebe 100644 --- a/libs/image/kis_layer.h +++ b/libs/image/kis_layer.h @@ -1,426 +1,426 @@ /* * 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 "kritaimage_export.h" #include "kis_base_node.h" #include "kis_types.h" #include "kis_node.h" #include "kis_psd_layer_style.h" template class QStack; class QBitArray; class KisCloneLayer; class KisPSDLayerStyle; class KisAbstractProjectionPlane; class KisLayerProjectionPlane; typedef QSharedPointer KisLayerProjectionPlaneSP; namespace KisMetaData { class Store; } /** * Abstract class that represents the concept of a Layer in Krita. This is not related * to the paint devices: this is merely an abstraction of how layers can be stacked and * rendered differently. * Regarding the previous-, first-, next- and lastChild() calls, first means that it the layer * is at the top of the group in the layerlist, using next will iterate to the bottom to last, * whereas previous will go up to first again. * * * TODO: Add a layer mode whereby the projection of the layer is used * as a clipping path? **/ class KRITAIMAGE_EXPORT KisLayer : public KisNode { Q_OBJECT public: /** * @param image is the pointer of the image or null * @param opacity is a value between OPACITY_TRANSPARENT_U8 and OPACITY_OPAQUE_U8 **/ KisLayer(KisImageWSP image, const QString &name, quint8 opacity); KisLayer(const KisLayer& rhs); ~KisLayer() override; /// returns the image's colorSpace or null, if there is no image const KoColorSpace * colorSpace() const override; /// returns the layer's composite op for the colorspace of the layer's parent. const KoCompositeOp * compositeOp() const override; KisPSDLayerStyleSP layerStyle() const; void setLayerStyle(KisPSDLayerStyleSP layerStyle); /** * \see a comment in KisNode::projectionPlane() */ KisAbstractProjectionPlaneSP projectionPlane() const override; /** * The projection plane representing the layer itself without any * styles or anything else. It is used by the layer styles projection * plane to stack up the planes. */ virtual KisLayerProjectionPlaneSP internalProjectionPlane() const; QRect partialChangeRect(KisNodeSP lastNode, const QRect& rect); void buildProjectionUpToNode(KisPaintDeviceSP projection, KisNodeSP lastNode, const QRect& rect); virtual bool needProjection() const; /** * Return the fully rendered representation of this layer: its * data and its effect masks */ KisPaintDeviceSP projection() const override; /** * Return the layer data before the effect masks have had their go * at it. */ KisPaintDeviceSP original() const override = 0; /** * @return the selection associated with this layer, if there is * one. Otherwise, return 0; */ virtual KisSelectionMaskSP selectionMask() const; /** * @return the selection contained in the first KisSelectionMask associated * with this layer or the image, if either exists, otherwise, return 0. */ virtual KisSelectionSP selection() const; KisBaseNode::PropertyList sectionModelProperties() const override; void setSectionModelProperties(const KisBaseNode::PropertyList &properties) override; /** * set/unset the channel flag for the alpha channel of this layer */ void disableAlphaChannel(bool disable); /** * returns true if the channel flag for the alpha channel * of this layer is not set. * returns false otherwise. */ bool alphaChannelDisabled() const; /** * set the channelflags for this layer to the specified bit array. * The bit array must have exactly the same number of channels as * the colorspace this layer is in, or be empty, in which case all * channels are active. */ virtual void setChannelFlags(const QBitArray & channelFlags); /** * Return a bit array where each bit indicates whether a * particular channel is active or not. If the channelflags bit * array is empty, all channels are active. */ QBitArray & channelFlags() const; /** * Returns true if this layer is temporary: i.e., it should not * appear in the layerbox, even though it is temporarily in the * layer stack and taken into account on recomposition. */ bool temporary() const; /** * Set to true if this layer should not appear in the layerbox, * even though it is temporarily in the layer stack and taken into * account on recomposition. */ void setTemporary(bool t); /** * Set the image this layer belongs to. */ void setImage(KisImageWSP image) override; /** * Create and return a layer that is the result of merging * this with layer. * * This method is designed to be called only within KisImage::mergeLayerDown(). * * Decendands override this to create specific merged types when possible. * The KisLayer one creates a KisPaintLayerSP via a bitBlt, and can work on all layer types. * * Descendants that perform their own version do NOT call KisLayer::createMergedLayer */ virtual KisLayerSP createMergedLayerTemplate(KisLayerSP prevLayer); virtual void fillMergedLayerTemplate(KisLayerSP dstLayer, KisLayerSP prevLayer); /** * Clones should be informed about updates of the original * layer, so this is a way to register them */ void registerClone(KisCloneLayerWSP clone); /** * Deregisters the clone from the update list * * \see registerClone() */ void unregisterClone(KisCloneLayerWSP clone); /** * Return the list of the clones of this node. Be careful * with the list, because it is not thread safe. */ const QList registeredClones() const; /** * Returns whether we have a clone. * * Be careful with it. It is not thread safe to add/remove * clone while checking hasClones(). So there should be no updates. */ bool hasClones() const; /** * It is calles by the async merger after projection update is done */ void updateClones(const QRect &rect); /** * Informs this layers that its masks might have changed. */ void notifyChildMaskChanged(); public: qint32 x() const override; qint32 y() const override; void setX(qint32 x) override; void setY(qint32 y) override; /** * Returns an approximation of where the bounds * of actual data of this layer are */ QRect extent() const override; /** * Returns the exact bounds of where the actual data * of this layer resides */ QRect exactBounds() const override; - QImage createThumbnail(qint32 w, qint32 h) override; + QImage createThumbnail(qint32 w, qint32 h, Qt::AspectRatioMode aspectRatioMode = Qt::IgnoreAspectRatio) override; - QImage createThumbnailForFrame(qint32 w, qint32 h, int time) override; + QImage createThumbnailForFrame(qint32 w, qint32 h, int time, Qt::AspectRatioMode aspectRatioMode = Qt::IgnoreAspectRatio) override; /** * Return a tight rectange, where the contents of the layer * is placed from user's point of view. This rectangle includes * all the masks and effects the layer has (excluding layer * styles, they report their bounds via projection plane). */ QRect tightUserVisibleBounds() const; public: /** * Returns true if there are any effect masks present */ bool hasEffectMasks() const; /** * @return the list of effect masks */ QList effectMasks() const; /** * @return the list of effect masks up to a certain node */ QList effectMasks(KisNodeSP lastNode) const; /** * Get the group layer that contains this layer. */ KisLayerSP parentLayer() const; /** * @return the metadata object associated with this object. */ KisMetaData::Store* metaData(); protected: // override from KisNode QRect changeRect(const QRect &rect, PositionToFilthy pos = N_FILTHY) const override; void childNodeChanged(KisNodeSP changedChildNode) override; protected: /** * Ask the layer to assemble its data & apply all the effect masks * to it. */ QRect updateProjection(const QRect& rect, KisNodeSP filthyNode); /** * Layers can override this method to get some special behavior * when copying data from \p original to \p projection, e.g. blend * in indirect painting device. If you need to modify data * outside \p rect, please also override outgoingChangeRect() * method. */ virtual void copyOriginalToProjection(const KisPaintDeviceSP original, KisPaintDeviceSP projection, const QRect& rect) const; /** * For KisLayer classes change rect transformation consists of two * parts: incoming and outgoing. * * 1) incomingChangeRect(rect) chande rect transformation * performed by the transformations done basing on global * projection. It is performed in KisAsyncMerger + * KisUpdateOriginalVisitor classes. It happens before data * coming to KisLayer::original() therefore it is * 'incoming'. See KisAdjustmentLayer for example of usage. * * 2) outgoingChangeRect(rect) change rect transformation that * happens in KisLayer::copyOriginalToProjection(). It applies * *only* when the layer is 'filthy', that is was the cause of * the merge process. See KisCloneLayer for example of usage. * * The flow of changed areas can be illustrated in the * following way: * * 1. Current projection of size R1 is stored in KisAsyncMerger::m_currentProjection * | * | <-- KisUpdateOriginalVisitor writes data into layer's original() device. * | The changed area on KisLayer::original() is * | R2 = KisLayer::incomingChangeRect(R1) * | * 2. KisLayer::original() / changed rect: R2 * | * | <-- KisLayer::updateProjection() starts composing a layer * | It calls KisLayer::copyOriginalToProjection() which copies some area * | to a temporaty device. The temporary device now stores * | R3 = KisLayer::outgoingChangeRect(R2) * | * 3. Temporary device / changed rect: R3 * | * | <-- KisLayer::updateProjection() continues composing a layer. It merges a mask. * | R4 = KisMask::changeRect(R3) * | * 4. KisLayer::original() / changed rect: R4 * * So in the end rect R4 will be passed up to the next layers in the stack. */ virtual QRect incomingChangeRect(const QRect &rect) const; /** * \see incomingChangeRect() */ virtual QRect outgoingChangeRect(const QRect &rect) const; /** * Return need rect that should be prepared on original() * device of the layer to get \p rect on its projection. * * This method is used either for layers that can have other * layers as children (yes, KisGroupLayer, I'm looking at you!), * or for layers that depend on the lower nodes (it's you, * KisAdjustmentLayer!). * * These layers may have some filter masks that need a bit * more pixels than requested, therefore child nodes should do * a bit more work to prepare them. */ QRect needRectForOriginal(const QRect &rect) const; /** * @param rectVariesFlag (out param) a flag, showing whether * a rect varies from mask to mask * @return an area that should be updated because of * the change of @requestedRect of the layer */ QRect masksChangeRect(const QList &masks, const QRect &requestedRect, bool &rectVariesFlag) const; /** * Get needRects for all masks * @param changeRect requested rect to be updated on final * projection. Should be a return value * of @ref masksChangedRect() * @param applyRects (out param) a stack of the rects where filters * should be applied * @param rectVariesFlag (out param) a flag, showing whether * a rect varies from mask to mask * @return a needRect that should be prepared on the layer's * paintDevice for all masks to succeed */ QRect masksNeedRect(const QList &masks, const QRect &changeRect, QStack &applyRects, bool &rectVariesFlag) const; QRect applyMasks(const KisPaintDeviceSP source, KisPaintDeviceSP destination, const QRect &requestedRect, KisNodeSP filthyNode, KisNodeSP lastNode) const; bool canMergeAndKeepBlendOptions(KisLayerSP otherLayer); QList searchEffectMasks(KisNodeSP lastNode) const; private: friend class KisLayerMasksCache; friend class KisLayerProjectionPlane; friend class KisTransformMask; friend class KisLayerTest; private: QRect layerExtentImpl(bool exactBounds) const; private: struct Private; Private * const m_d; }; Q_DECLARE_METATYPE(KisLayerSP) #endif // KIS_LAYER_H_ diff --git a/libs/image/kis_mask.cc b/libs/image/kis_mask.cc index 71743c2c7b..5983bd2d95 100644 --- a/libs/image/kis_mask.cc +++ b/libs/image/kis_mask.cc @@ -1,513 +1,510 @@ /* * 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); 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)); - 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)); 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)); 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 { 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); } } 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) +QImage KisMask::createThumbnail(qint32 w, qint32 h, Qt::AspectRatioMode aspectRatioMode) { KisPaintDeviceSP originalDevice = selection() ? selection()->projection() : 0; return originalDevice ? - originalDevice->createThumbnail(w, h, 1, + originalDevice->createThumbnail(w, h, aspectRatioMode, 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())); } 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_mask.h b/libs/image/kis_mask.h index 2c11ed079c..862f65b3b4 100644 --- a/libs/image/kis_mask.h +++ b/libs/image/kis_mask.h @@ -1,236 +1,236 @@ /* * 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.b */ #ifndef _KIS_MASK_ #define _KIS_MASK_ #include #include "kis_types.h" #include "kis_global.h" #include "kis_node.h" #include "kis_indirect_painting_support.h" #include /** KisMask is the base class for all single channel mask-like paint devices in Krita. Masks can be rendered in different ways at different moments during the rendering stack. Masks are "owned" by layers (of any type), and cannot occur by themselves on themselves. The properties that masks implement are made available through the iterators created on their parent layer, or through iterators that can be created on the paint device that holds the mask data: masks are just paint devices, too. Masks should show up in the layerbox as sub-layers for the layer they are associated with and be ccp'able and draggable to other layers. Examples of masks are: - filter masks: like the alpha filter mask that is the most common type of mask and is simply known as "mask" in the gui. Other filter masks use any of krita's filters to filter the pixels of their parent. (In this they differ from adjustment layers, which filter all layers under them in their group stack). - selections: the selection mask is rendered after composition and zooming and determines the selectedness of the pixels of the parent layer. - painterly overlays: painterly overlays indicate a particular property of the pixel in the parent paint device they are associated with, like wetness, height or gravity. XXX: For now, all masks are 8 bit. Make the channel depth settable. */ class KRITAIMAGE_EXPORT KisMask : public KisNode, public KisIndirectPaintingSupport { Q_OBJECT public: /** * Create a new KisMask. */ KisMask(const QString & name); /** * Copy the mask */ KisMask(const KisMask& rhs); ~KisMask() override; void setImage(KisImageWSP image) override; bool allowAsChild(KisNodeSP node) const override; /** * @brief initSelection initializes the selection for the mask from * the given selection's projection. * @param copyFrom the selection we base the mask on * @param parentLayer the parent of this mask; it determines the default bounds of the mask. */ void initSelection(KisSelectionSP copyFrom, KisLayerSP parentLayer); /** * @brief initSelection initializes the selection for the mask from * the given paint device. * @param copyFromDevice the paint device we base the mask on * @param parentLayer the parent of this mask; it determines the default bounds of the mask. */ void initSelection(KisPaintDeviceSP copyFromDevice, KisLayerSP parentLayer); /** * @brief initSelection initializes an empty selection * @param parentLayer the parent of this mask; it determines the default bounds of the mask. */ void initSelection(KisLayerSP parentLayer); const KoColorSpace * colorSpace() const override; const KoCompositeOp * compositeOp() const override; /** * Return the selection associated with this mask. A selection can * contain both a paint device and shapes. */ KisSelectionSP selection() const; /** * @return the selection: if you paint on mask, you paint on the selections */ KisPaintDeviceSP paintDevice() const override; /** * @return the same as paintDevice() */ KisPaintDeviceSP original() const override; /** * @return the same as paintDevice() */ KisPaintDeviceSP projection() const override; KisAbstractProjectionPlaneSP projectionPlane() const override; /** * Change the selection to the specified selection object. The * selection is deep copied. */ void setSelection(KisSelectionSP selection); /** * Selected the specified rect with the specified amount of selectedness. */ void select(const QRect & rc, quint8 selectedness = MAX_SELECTED); /** * The extent and bounds of the mask are those of the selection inside */ QRect extent() const override; QRect exactBounds() const override; /** * overridden from KisBaseNode */ qint32 x() const override; /** * overridden from KisBaseNode */ void setX(qint32 x) override; /** * overridden from KisBaseNode */ qint32 y() const override; /** * overridden from KisBaseNode */ void setY(qint32 y) override; /** * Usually masks themselves do not have any paint device and * all their final effect on the layer stack is computed using * the changeRect() of the dirty rect of the parent layer. Their * extent() and exectBounds() methods work the same way: by taking * the extent of the parent layer and computing the rect basing * on it. But some of the masks like Colorize Mask may have their * own "projection", which is painted independently from the changed * area of the parent layer. This additional "non-dependent" extent * is added to the extent of the parent layer. */ virtual QRect nonDependentExtent() const; QRect needRect(const QRect &rect, PositionToFilthy pos = N_FILTHY) const override; QRect changeRect(const QRect &rect, PositionToFilthy pos = N_FILTHY) const override; - QImage createThumbnail(qint32 w, qint32 h) override; + QImage createThumbnail(qint32 w, qint32 h, Qt::AspectRatioMode aspectRatioMode = Qt::IgnoreAspectRatio) override; void testingInitSelection(const QRect &rect, KisLayerSP parentLayer); protected: /** * Apply the effect the projection using the mask as a selection. * Made public in KisEffectMask */ void apply(KisPaintDeviceSP projection, const QRect & applyRect, const QRect & needRect, PositionToFilthy maskPos) const; virtual void mergeInMaskInternal(KisPaintDeviceSP projection, KisSelectionSP effectiveSelection, const QRect &applyRect, const QRect &preparedNeedRect, PositionToFilthy maskPos) const; /** * A special callback for calling selection->updateProjection() during * the projection calculation process. Some masks (e.g. selection masks) * don't need it, because they do it separately. */ virtual void flattenSelectionProjection(KisSelectionSP selection, const QRect &dirtyRect) const; virtual QRect decorateRect(KisPaintDeviceSP &src, KisPaintDeviceSP &dst, const QRect & rc, PositionToFilthy maskPos) const; virtual bool paintsOutsideSelection() const; KisKeyframeChannel *requestKeyframeChannel(const QString &id) override; void baseNodeChangedCallback() override; private: friend class KisMaskProjectionPlane; private: struct Private; Private * const m_d; }; #endif diff --git a/libs/image/kis_paint_device.cc b/libs/image/kis_paint_device.cc index cd96946f37..9580ea884d 100644 --- a/libs/image/kis_paint_device.cc +++ b/libs/image/kis_paint_device.cc @@ -1,2250 +1,2276 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_paint_device.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_image.h" #include "kis_random_sub_accessor.h" #include "kis_selection.h" #include "kis_node.h" #include "kis_datamanager.h" #include "kis_paint_device_writer.h" #include "kis_selection_component.h" #include "kis_pixel_selection.h" #include "kis_repeat_iterators_pixel.h" #include "kis_fixed_paint_device.h" #include "tiles3/kis_hline_iterator.h" #include "tiles3/kis_vline_iterator.h" #include "tiles3/kis_random_accessor.h" #include "kis_default_bounds.h" #include "kis_lod_transform.h" #include "kis_raster_keyframe_channel.h" #include "kis_paint_device_cache.h" #include "kis_paint_device_data.h" #include "kis_paint_device_frames_interface.h" #include "kis_transform_worker.h" #include "kis_filter_strategy.h" #include "krita_utils.h" struct KisPaintDeviceSPStaticRegistrar { KisPaintDeviceSPStaticRegistrar() { qRegisterMetaType("KisPaintDeviceSP"); } }; static KisPaintDeviceSPStaticRegistrar __registrar; struct KisPaintDevice::Private { /** * Used when the paint device is loading to ensure no lod/animation * interferes the process. */ static const KisDefaultBoundsSP transitionalDefaultBounds; public: class KisPaintDeviceStrategy; class KisPaintDeviceWrappedStrategy; class DeviceChangeProfileCommand; class DeviceChangeColorSpaceCommand; Private(KisPaintDevice *paintDevice); ~Private(); KisPaintDevice *q; KisNodeWSP parent; QScopedPointer contentChannel; KisDefaultBoundsBaseSP defaultBounds; QScopedPointer basicStrategy; QScopedPointer wrappedStrategy; QMutex m_wrappedStrategyMutex; QScopedPointer framesInterface; bool isProjectionDevice; KisPaintDeviceStrategy* currentStrategy(); void init(const KoColorSpace *cs, const quint8 *defaultPixel); void convertColorSpace(const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags, KUndo2Command *parentCommand); bool assignProfile(const KoColorProfile * profile, KUndo2Command *parentCommand); + KUndo2Command* reincarnateWithDetachedHistory(bool copyContent); + + inline const KoColorSpace* colorSpace() const { return currentData()->colorSpace(); } inline KisDataManagerSP dataManager() const { return currentData()->dataManager(); } inline qint32 x() const { return currentData()->x(); } inline qint32 y() const { return currentData()->y(); } inline void setX(qint32 x) { currentData()->setX(x); } inline void setY(qint32 y) { currentData()->setY(y); } inline KisPaintDeviceCache* cache() { return currentData()->cache(); } inline KisIteratorCompleteListener* cacheInvalidator() { return currentData()->cacheInvalidator(); } void cloneAllDataObjects(Private *rhs, bool copyFrames) { m_lodData.reset(); m_externalFrameData.reset(); if (!m_frames.isEmpty()) { m_frames.clear(); } if (!copyFrames) { if (m_data) { m_data->prepareClone(rhs->currentNonLodData(), true); } else { m_data = toQShared(new KisPaintDeviceData(q, rhs->currentNonLodData(), true)); } } else { if (m_data && !rhs->m_data) { m_data.clear(); } else if (!m_data && rhs->m_data) { m_data = toQShared(new KisPaintDeviceData(q, rhs->m_data.data(), true)); } else if (m_data && rhs->m_data) { m_data->prepareClone(rhs->m_data.data(), true); } if (!rhs->m_frames.isEmpty()) { FramesHash::const_iterator it = rhs->m_frames.constBegin(); FramesHash::const_iterator end = rhs->m_frames.constEnd(); for (; it != end; ++it) { DataSP data = toQShared(new KisPaintDeviceData(q, it.value().data(), true)); m_frames.insert(it.key(), data); } } m_nextFreeFrameId = rhs->m_nextFreeFrameId; } if (rhs->m_lodData) { m_lodData.reset(new KisPaintDeviceData(q, rhs->m_lodData.data(), true)); } } void prepareClone(KisPaintDeviceSP src) { prepareCloneImpl(src, src->m_d->currentData()); KIS_SAFE_ASSERT_RECOVER_NOOP(fastBitBltPossible(src)); } bool fastBitBltPossible(KisPaintDeviceSP src) { return fastBitBltPossibleImpl(src->m_d->currentData()); } int currentFrameId() const { KIS_ASSERT_RECOVER(contentChannel) { return -1; } return !defaultBounds->currentLevelOfDetail() ? contentChannel->frameIdAt(defaultBounds->currentTime()) : -1; } KisDataManagerSP frameDataManager(int frameId) const { DataSP data = m_frames[frameId]; return data->dataManager(); } void invalidateFrameCache(int frameId) { DataSP data = m_frames[frameId]; return data->cache()->invalidate(); } private: typedef KisPaintDeviceData Data; typedef QSharedPointer DataSP; typedef QHash FramesHash; class FrameInsertionCommand : public KUndo2Command { public: FrameInsertionCommand(FramesHash *hash, DataSP data, int frameId, bool insert, KUndo2Command *parentCommand) : KUndo2Command(parentCommand), m_hash(hash), m_data(data), m_frameId(frameId), m_insert(insert) { } void redo() override { doSwap(m_insert); } void undo() override { doSwap(!m_insert); } private: void doSwap(bool insert) { if (insert) { m_hash->insert(m_frameId, m_data); } else { DataSP deletedData = m_hash->take(m_frameId); } } private: FramesHash *m_hash; DataSP m_data; int m_frameId; bool m_insert; }; public: int getNextFrameId() { int frameId = 0; while (m_frames.contains(frameId = m_nextFreeFrameId++)); KIS_SAFE_ASSERT_RECOVER_NOOP(!m_frames.contains(frameId)); return frameId; } int createFrame(bool copy, int copySrc, const QPoint &offset, KUndo2Command *parentCommand) { KIS_ASSERT_RECOVER(parentCommand) { return -1; } DataSP data; bool initialFrame = false; if (m_frames.isEmpty()) { /** * Here we move the contents of the paint device to the * new frame and clear m_data to make the "background" for * the areas where there is no frame at all. */ data = toQShared(new Data(q, m_data.data(), true)); m_data->dataManager()->clear(); m_data->cache()->invalidate(); initialFrame = true; } else if (copy) { DataSP srcData = m_frames[copySrc]; data = toQShared(new Data(q, srcData.data(), true)); } else { DataSP srcData = m_frames.begin().value(); data = toQShared(new Data(q, srcData.data(), false)); } if (!initialFrame && !copy) { data->setX(offset.x()); data->setY(offset.y()); } int frameId = getNextFrameId(); KUndo2Command *cmd = new FrameInsertionCommand(&m_frames, data, frameId, true, parentCommand); cmd->redo(); return frameId; } void deleteFrame(int frame, KUndo2Command *parentCommand) { KIS_ASSERT_RECOVER_RETURN(m_frames.contains(frame)); KIS_ASSERT_RECOVER_RETURN(parentCommand); DataSP deletedData = m_frames[frame]; KUndo2Command *cmd = new FrameInsertionCommand(&m_frames, deletedData, frame, false, parentCommand); cmd->redo(); } QRect frameBounds(int frameId) { DataSP data = m_frames[frameId]; QRect extent = data->dataManager()->extent(); extent.translate(data->x(), data->y()); return extent; } QPoint frameOffset(int frameId) const { DataSP data = m_frames[frameId]; return QPoint(data->x(), data->y()); } void setFrameOffset(int frameId, const QPoint &offset) { DataSP data = m_frames[frameId]; data->setX(offset.x()); data->setY(offset.y()); } const QList frameIds() const { return m_frames.keys(); } bool readFrame(QIODevice *stream, int frameId) { bool retval = false; DataSP data = m_frames[frameId]; retval = data->dataManager()->read(stream); data->cache()->invalidate(); return retval; } bool writeFrame(KisPaintDeviceWriter &store, int frameId) { DataSP data = m_frames[frameId]; return data->dataManager()->write(store); } void setFrameDefaultPixel(const KoColor &defPixel, int frameId) { DataSP data = m_frames[frameId]; KoColor color(defPixel); color.convertTo(data->colorSpace()); data->dataManager()->setDefaultPixel(color.data()); } KoColor frameDefaultPixel(int frameId) const { DataSP data = m_frames[frameId]; return KoColor(data->dataManager()->defaultPixel(), data->colorSpace()); } void fetchFrame(int frameId, KisPaintDeviceSP targetDevice); void uploadFrame(int srcFrameId, int dstFrameId, KisPaintDeviceSP srcDevice); void uploadFrame(int dstFrameId, KisPaintDeviceSP srcDevice); void uploadFrameData(DataSP srcData, DataSP dstData); struct LodDataStructImpl; LodDataStruct* createLodDataStruct(int lod); void updateLodDataStruct(LodDataStruct *dst, const QRect &srcRect); void uploadLodDataStruct(LodDataStruct *dst); KisRegion regionForLodSyncing() const; void updateLodDataManager(KisDataManager *srcDataManager, KisDataManager *dstDataManager, const QPoint &srcOffset, const QPoint &dstOffset, const QRect &originalRect, int lod); void generateLodCloneDevice(KisPaintDeviceSP dst, const QRect &originalRect, int lod); void tesingFetchLodDevice(KisPaintDeviceSP targetDevice); private: qint64 estimateDataSize(Data *data) const { const QRect &rc = data->dataManager()->extent(); return rc.width() * rc.height() * data->colorSpace()->pixelSize(); } public: void estimateMemoryStats(qint64 &imageData, qint64 &temporaryData, qint64 &lodData) const { imageData = 0; temporaryData = 0; lodData = 0; if (m_data) { imageData += estimateDataSize(m_data.data()); } if (m_lodData) { lodData += estimateDataSize(m_lodData.data()); } if (m_externalFrameData) { temporaryData += estimateDataSize(m_externalFrameData.data()); } Q_FOREACH (DataSP value, m_frames.values()) { imageData += estimateDataSize(value.data()); } } private: inline DataSP currentFrameData() const { DataSP data; const int numberOfFrames = contentChannel->keyframeCount(); if (numberOfFrames > 1) { int frameId = contentChannel->frameIdAt(defaultBounds->currentTime()); if (frameId == -1) { data = m_data; } else { KIS_ASSERT_RECOVER(m_frames.contains(frameId)) { return m_frames.begin().value(); } data = m_frames[frameId]; } } else if (numberOfFrames == 1) { data = m_frames.begin().value(); } else { data = m_data; } return data; } inline Data* currentNonLodData() const { Data *data = m_data.data(); if (contentChannel) { data = currentFrameData().data(); } else if (isProjectionDevice && defaultBounds->externalFrameActive()) { if (!m_externalFrameData) { QMutexLocker l(&m_dataSwitchLock); if (!m_externalFrameData) { m_externalFrameData.reset(new Data(q, m_data.data(), false)); } } data = m_externalFrameData.data(); } return data; } inline void ensureLodDataPresent() const { if (!m_lodData) { Data *srcData = currentNonLodData(); QMutexLocker l(&m_dataSwitchLock); if (!m_lodData) { m_lodData.reset(new Data(q, srcData, false)); } } } inline Data* currentData() const { Data *data; if (defaultBounds->currentLevelOfDetail()) { ensureLodDataPresent(); data = m_lodData.data(); } else { data = currentNonLodData(); } return data; } void prepareCloneImpl(KisPaintDeviceSP src, Data *srcData) { /** * The result of currentData() depends on the current * level of detail and animation frame index. So we * should first connect the device to the new * default bounds object, and only after that ask * currentData() to start cloning. */ q->setDefaultPixel(KoColor(srcData->dataManager()->defaultPixel(), colorSpace())); q->setDefaultBounds(src->defaultBounds()); currentData()->prepareClone(srcData); } bool fastBitBltPossibleImpl(Data *srcData) { return x() == srcData->x() && y() == srcData->y() && *colorSpace() == *srcData->colorSpace(); } QList allDataObjects() const { QList dataObjects; if (m_frames.isEmpty()) { dataObjects << m_data.data(); } dataObjects << m_lodData.data(); dataObjects << m_externalFrameData.data(); Q_FOREACH (DataSP value, m_frames.values()) { dataObjects << value.data(); } return dataObjects; } void transferFromData(Data *data, KisPaintDeviceSP targetDevice); struct Q_DECL_HIDDEN StrategyPolicy; typedef KisSequentialIteratorBase, StrategyPolicy> InternalSequentialConstIterator; typedef KisSequentialIteratorBase, StrategyPolicy> InternalSequentialIterator; private: friend class KisPaintDeviceFramesInterface; private: DataSP m_data; mutable QScopedPointer m_lodData; mutable QScopedPointer m_externalFrameData; mutable QMutex m_dataSwitchLock; FramesHash m_frames; int m_nextFreeFrameId; }; const KisDefaultBoundsSP KisPaintDevice::Private::transitionalDefaultBounds = new KisDefaultBounds(); #include "kis_paint_device_strategies.h" KisPaintDevice::Private::Private(KisPaintDevice *paintDevice) : q(paintDevice), basicStrategy(new KisPaintDeviceStrategy(paintDevice, this)), isProjectionDevice(false), m_data(new Data(paintDevice)), m_nextFreeFrameId(0) { } KisPaintDevice::Private::~Private() { m_frames.clear(); } KisPaintDevice::Private::KisPaintDeviceStrategy* KisPaintDevice::Private::currentStrategy() { if (!defaultBounds->wrapAroundMode()) { return basicStrategy.data(); } const QRect wrapRect = defaultBounds->bounds(); if (!wrappedStrategy || wrappedStrategy->wrapRect() != wrapRect) { QMutexLocker locker(&m_wrappedStrategyMutex); if (!wrappedStrategy) { wrappedStrategy.reset(new KisPaintDeviceWrappedStrategy(wrapRect, q, this)); } else if (wrappedStrategy->wrapRect() != wrapRect) { wrappedStrategy->setWrapRect(wrapRect); } } return wrappedStrategy.data(); } struct KisPaintDevice::Private::StrategyPolicy { StrategyPolicy(KisPaintDevice::Private::KisPaintDeviceStrategy *strategy, KisDataManager *dataManager, qint32 offsetX, qint32 offsetY) : m_strategy(strategy), m_dataManager(dataManager), m_offsetX(offsetX), m_offsetY(offsetY) { } KisHLineConstIteratorSP createConstIterator(const QRect &rect) { return m_strategy->createHLineConstIteratorNG(m_dataManager, rect.x(), rect.y(), rect.width(), m_offsetX, m_offsetY); } KisHLineIteratorSP createIterator(const QRect &rect) { return m_strategy->createHLineIteratorNG(m_dataManager, rect.x(), rect.y(), rect.width(), m_offsetX, m_offsetY); } int pixelSize() const { return m_dataManager->pixelSize(); } KisPaintDeviceStrategy *m_strategy; KisDataManager *m_dataManager; int m_offsetX; int m_offsetY; }; struct KisPaintDevice::Private::LodDataStructImpl : public KisPaintDevice::LodDataStruct { LodDataStructImpl(Data *_lodData) : lodData(_lodData) {} QScopedPointer lodData; }; KisRegion KisPaintDevice::Private::regionForLodSyncing() const { Data *srcData = currentNonLodData(); return srcData->dataManager()->region().translated(srcData->x(), srcData->y()); } KisPaintDevice::LodDataStruct* KisPaintDevice::Private::createLodDataStruct(int newLod) { KIS_SAFE_ASSERT_RECOVER_NOOP(newLod > 0); Data *srcData = currentNonLodData(); Data *lodData = new Data(q, srcData, false); LodDataStruct *lodStruct = new LodDataStructImpl(lodData); int expectedX = KisLodTransform::coordToLodCoord(srcData->x(), newLod); int expectedY = KisLodTransform::coordToLodCoord(srcData->y(), newLod); /** * We compare color spaces as pure pointers, because they must be * exactly the same, since they come from the common source. */ if (lodData->levelOfDetail() != newLod || lodData->colorSpace() != srcData->colorSpace() || lodData->x() != expectedX || lodData->y() != expectedY) { lodData->prepareClone(srcData); lodData->setLevelOfDetail(newLod); lodData->setX(expectedX); lodData->setY(expectedY); // FIXME: different kind of synchronization } lodData->cache()->invalidate(); return lodStruct; } void KisPaintDevice::Private::updateLodDataManager(KisDataManager *srcDataManager, KisDataManager *dstDataManager, const QPoint &srcOffset, const QPoint &dstOffset, const QRect &originalRect, int lod) { if (originalRect.isEmpty()) return; const int srcStepSize = 1 << lod; KIS_ASSERT_RECOVER_RETURN(lod > 0); const QRect srcRect = KisLodTransform::alignedRect(originalRect, lod); const QRect dstRect = KisLodTransform::scaledRect(srcRect, lod); if (!srcRect.isValid() || !dstRect.isValid()) return; KIS_ASSERT_RECOVER_NOOP(srcRect.width() / srcStepSize == dstRect.width()); const int pixelSize = srcDataManager->pixelSize(); int rowsAccumulated = 0; int columnsAccumulated = 0; KoMixColorsOp *mixOp = colorSpace()->mixColorsOp(); QScopedArrayPointer blendData(new quint8[srcStepSize * srcRect.width() * pixelSize]); quint8 *blendDataPtr = blendData.data(); int blendDataOffset = 0; const int srcCellSize = srcStepSize * srcStepSize; const int srcCellStride = srcCellSize * pixelSize; const int srcStepStride = srcStepSize * pixelSize; const int srcColumnStride = (srcStepSize - 1) * srcStepStride; QScopedArrayPointer weights(new qint16[srcCellSize]); { const qint16 averageWeight = qCeil(255.0 / srcCellSize); const qint16 extraWeight = averageWeight * srcCellSize - 255; KIS_ASSERT_RECOVER_NOOP(extraWeight == 1); for (int i = 0; i < srcCellSize - 1; i++) { weights[i] = averageWeight; } weights[srcCellSize - 1] = averageWeight - extraWeight; } InternalSequentialConstIterator srcIntIt(StrategyPolicy(currentStrategy(), srcDataManager, srcOffset.x(), srcOffset.y()), srcRect); InternalSequentialIterator dstIntIt(StrategyPolicy(currentStrategy(), dstDataManager, dstOffset.x(), dstOffset.y()), dstRect); int rowsRemaining = srcRect.height(); while (rowsRemaining > 0) { int colsRemaining = srcRect.width(); while (colsRemaining > 0 && srcIntIt.nextPixel()) { memcpy(blendDataPtr, srcIntIt.rawDataConst(), pixelSize); blendDataPtr += pixelSize; columnsAccumulated++; if (columnsAccumulated >= srcStepSize) { blendDataPtr += srcColumnStride; columnsAccumulated = 0; } colsRemaining--; } rowsAccumulated++; if (rowsAccumulated >= srcStepSize) { // blend and write the final data blendDataPtr = blendData.data(); int colsRemaining = dstRect.width(); while (colsRemaining > 0 && dstIntIt.nextPixel()) { mixOp->mixColors(blendDataPtr, weights.data(), srcCellSize, dstIntIt.rawData()); blendDataPtr += srcCellStride; colsRemaining--; } // reset counters rowsAccumulated = 0; blendDataPtr = blendData.data(); blendDataOffset = 0; } else { blendDataOffset += srcStepStride; blendDataPtr = blendData.data() + blendDataOffset; } rowsRemaining--; } } void KisPaintDevice::Private::updateLodDataStruct(LodDataStruct *_dst, const QRect &originalRect) { LodDataStructImpl *dst = dynamic_cast(_dst); KIS_SAFE_ASSERT_RECOVER_RETURN(dst); Data *lodData = dst->lodData.data(); Data *srcData = currentNonLodData(); const int lod = lodData->levelOfDetail(); updateLodDataManager(srcData->dataManager().data(), lodData->dataManager().data(), QPoint(srcData->x(), srcData->y()), QPoint(lodData->x(), lodData->y()), originalRect, lod); } void KisPaintDevice::Private::generateLodCloneDevice(KisPaintDeviceSP dst, const QRect &originalRect, int lod) { KIS_SAFE_ASSERT_RECOVER_RETURN(fastBitBltPossible(dst)); Data *srcData = currentNonLodData(); updateLodDataManager(srcData->dataManager().data(), dst->dataManager().data(), QPoint(srcData->x(), srcData->y()), QPoint(dst->x(), dst->y()), originalRect, lod); } void KisPaintDevice::Private::uploadLodDataStruct(LodDataStruct *_dst) { LodDataStructImpl *dst = dynamic_cast(_dst); KIS_SAFE_ASSERT_RECOVER_RETURN(dst); KIS_SAFE_ASSERT_RECOVER_RETURN( dst->lodData->levelOfDetail() == defaultBounds->currentLevelOfDetail()); ensureLodDataPresent(); m_lodData->prepareClone(dst->lodData.data()); m_lodData->dataManager()->bitBltRough(dst->lodData->dataManager(), dst->lodData->dataManager()->extent()); } void KisPaintDevice::Private::transferFromData(Data *data, KisPaintDeviceSP targetDevice) { QRect extent = data->dataManager()->extent(); extent.translate(data->x(), data->y()); targetDevice->m_d->prepareCloneImpl(q, data); targetDevice->m_d->currentStrategy()->fastBitBltRough(data->dataManager(), extent); } void KisPaintDevice::Private::fetchFrame(int frameId, KisPaintDeviceSP targetDevice) { DataSP data = m_frames[frameId]; transferFromData(data.data(), targetDevice); } void KisPaintDevice::Private::uploadFrame(int srcFrameId, int dstFrameId, KisPaintDeviceSP srcDevice) { DataSP dstData = m_frames[dstFrameId]; KIS_ASSERT_RECOVER_RETURN(dstData); DataSP srcData = srcDevice->m_d->m_frames[srcFrameId]; KIS_ASSERT_RECOVER_RETURN(srcData); uploadFrameData(srcData, dstData); } void KisPaintDevice::Private::uploadFrame(int dstFrameId, KisPaintDeviceSP srcDevice) { DataSP dstData = m_frames[dstFrameId]; KIS_ASSERT_RECOVER_RETURN(dstData); DataSP srcData = srcDevice->m_d->m_data; KIS_ASSERT_RECOVER_RETURN(srcData); uploadFrameData(srcData, dstData); } void KisPaintDevice::Private::uploadFrameData(DataSP srcData, DataSP dstData) { if (srcData->colorSpace() != dstData->colorSpace() && *srcData->colorSpace() != *dstData->colorSpace()) { KUndo2Command tempCommand; srcData = toQShared(new Data(q, srcData.data(), true)); srcData->convertDataColorSpace(dstData->colorSpace(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags(), &tempCommand); } dstData->dataManager()->clear(); dstData->cache()->invalidate(); const QRect rect = srcData->dataManager()->extent(); dstData->dataManager()->bitBltRough(srcData->dataManager(), rect); dstData->setX(srcData->x()); dstData->setY(srcData->y()); } void KisPaintDevice::Private::tesingFetchLodDevice(KisPaintDeviceSP targetDevice) { Data *data = m_lodData.data(); Q_ASSERT(data); transferFromData(data, targetDevice); } class KisPaintDevice::Private::DeviceChangeProfileCommand : public KUndo2Command { public: DeviceChangeProfileCommand(KisPaintDeviceSP device, KUndo2Command *parent = 0) : KUndo2Command(parent), m_device(device) { } virtual void emitNotifications() { m_device->emitProfileChanged(); } void redo() override { if (m_firstRun) { m_firstRun = false; return; } KUndo2Command::redo(); emitNotifications(); } void undo() override { KUndo2Command::undo(); emitNotifications(); } protected: KisPaintDeviceSP m_device; private: bool m_firstRun {true}; }; class KisPaintDevice::Private::DeviceChangeColorSpaceCommand : public DeviceChangeProfileCommand { public: DeviceChangeColorSpaceCommand(KisPaintDeviceSP device, KUndo2Command *parent = 0) : DeviceChangeProfileCommand(device, parent) { } void emitNotifications() override { m_device->emitColorSpaceChanged(); } }; void KisPaintDevice::Private::convertColorSpace(const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags, KUndo2Command *parentCommand) { QList dataObjects = allDataObjects(); if (dataObjects.isEmpty()) return; KUndo2Command *mainCommand = parentCommand ? new DeviceChangeColorSpaceCommand(q, parentCommand) : 0; Q_FOREACH (Data *data, dataObjects) { if (!data) continue; data->convertDataColorSpace(dstColorSpace, renderingIntent, conversionFlags, mainCommand); } q->emitColorSpaceChanged(); } bool KisPaintDevice::Private::assignProfile(const KoColorProfile * profile, KUndo2Command *parentCommand) { if (!profile) return false; const KoColorSpace *dstColorSpace = KoColorSpaceRegistry::instance()->colorSpace(colorSpace()->colorModelId().id(), colorSpace()->colorDepthId().id(), profile); if (!dstColorSpace) return false; KUndo2Command *mainCommand = parentCommand ? new DeviceChangeColorSpaceCommand(q, parentCommand) : 0; QList dataObjects = allDataObjects(); Q_FOREACH (Data *data, dataObjects) { if (!data) continue; data->assignColorSpace(dstColorSpace, mainCommand); } q->emitProfileChanged(); // no undo information is provided here return true; } +KUndo2Command *KisPaintDevice::Private::reincarnateWithDetachedHistory(bool copyContent) +{ + KUndo2Command *mainCommand = new KUndo2Command(); + currentData()->reincarnateWithDetachedHistory(copyContent, mainCommand); + return mainCommand; +} + void KisPaintDevice::Private::init(const KoColorSpace *cs, const quint8 *defaultPixel) { QList dataObjects = allDataObjects(); Q_FOREACH (Data *data, dataObjects) { if (!data) continue; KisDataManagerSP dataManager = new KisDataManager(cs->pixelSize(), defaultPixel); data->init(cs, dataManager); } } KisPaintDevice::KisPaintDevice(const KoColorSpace * colorSpace, const QString& name) : QObject(0) , m_d(new Private(this)) { init(colorSpace, new KisDefaultBounds(), 0, name); } KisPaintDevice::KisPaintDevice(KisNodeWSP parent, const KoColorSpace * colorSpace, KisDefaultBoundsBaseSP defaultBounds, const QString& name) : QObject(0) , m_d(new Private(this)) { init(colorSpace, defaultBounds, parent, name); } void KisPaintDevice::init(const KoColorSpace *colorSpace, KisDefaultBoundsBaseSP defaultBounds, KisNodeWSP parent, const QString& name) { Q_ASSERT(colorSpace); setObjectName(name); // temporary def. bounds object for the initialization phase only m_d->defaultBounds = m_d->transitionalDefaultBounds; if (!defaultBounds) { // Reuse transitionalDefaultBounds here. Change if you change // semantics of transitionalDefaultBounds defaultBounds = m_d->transitionalDefaultBounds; } QScopedArrayPointer defaultPixel(new quint8[colorSpace->pixelSize()]); colorSpace->fromQColor(Qt::transparent, defaultPixel.data()); m_d->init(colorSpace, defaultPixel.data()); Q_ASSERT(m_d->colorSpace()); setDefaultBounds(defaultBounds); setParentNode(parent); } KisPaintDevice::KisPaintDevice(const KisPaintDevice& rhs, KritaUtils::DeviceCopyMode copyMode, KisNode *newParentNode) : QObject() , KisShared() , m_d(new Private(this)) { if (this != &rhs) { makeFullCopyFrom(rhs, copyMode, newParentNode); } } void KisPaintDevice::makeFullCopyFrom(const KisPaintDevice &rhs, KritaUtils::DeviceCopyMode copyMode, KisNode *newParentNode) { // temporary def. bounds object for the initialization phase only m_d->defaultBounds = m_d->transitionalDefaultBounds; // copy data objects with or without frames m_d->cloneAllDataObjects(rhs.m_d, copyMode == KritaUtils::CopyAllFrames); if (copyMode == KritaUtils::CopyAllFrames && rhs.m_d->framesInterface) { KIS_ASSERT_RECOVER_RETURN(rhs.m_d->framesInterface); KIS_ASSERT_RECOVER_RETURN(rhs.m_d->contentChannel); m_d->framesInterface.reset(new KisPaintDeviceFramesInterface(this)); m_d->contentChannel.reset(new KisRasterKeyframeChannel(*rhs.m_d->contentChannel.data(), newParentNode, this)); } setDefaultBounds(rhs.m_d->defaultBounds); setParentNode(newParentNode); } KisPaintDevice::~KisPaintDevice() { delete m_d; } void KisPaintDevice::setProjectionDevice(bool value) { m_d->isProjectionDevice = value; } void KisPaintDevice::prepareClone(KisPaintDeviceSP src) { m_d->prepareClone(src); } void KisPaintDevice::makeCloneFrom(KisPaintDeviceSP src, const QRect &rect) { prepareClone(src); // we guarantee that *this is totally empty, so copy pixels that // are areally present on the source image only const QRect optimizedRect = rect & src->extent(); fastBitBlt(src, optimizedRect); } void KisPaintDevice::makeCloneFromRough(KisPaintDeviceSP src, const QRect &minimalRect) { prepareClone(src); // we guarantee that *this is totally empty, so copy pixels that // are areally present on the source image only const QRect optimizedRect = minimalRect & src->extent(); fastBitBltRough(src, optimizedRect); } void KisPaintDevice::setDirty() { m_d->cache()->invalidate(); if (m_d->parent.isValid()) m_d->parent->setDirty(); } void KisPaintDevice::setDirty(const QRect & rc) { m_d->cache()->invalidate(); if (m_d->parent.isValid()) m_d->parent->setDirty(rc); } void KisPaintDevice::setDirty(const KisRegion ®ion) { m_d->cache()->invalidate(); if (m_d->parent.isValid()) m_d->parent->setDirty(region); } void KisPaintDevice::setDirty(const QVector &rects) { m_d->cache()->invalidate(); if (m_d->parent.isValid()) m_d->parent->setDirty(rects); } void KisPaintDevice::requestTimeSwitch(int time) { if (m_d->parent.isValid()) { m_d->parent->requestTimeSwitch(time); } } int KisPaintDevice::sequenceNumber() const { return m_d->cache()->sequenceNumber(); } void KisPaintDevice::estimateMemoryStats(qint64 &imageData, qint64 &temporaryData, qint64 &lodData) const { m_d->estimateMemoryStats(imageData, temporaryData, lodData); } void KisPaintDevice::setParentNode(KisNodeWSP parent) { m_d->parent = parent; } // for testing purposes only KisNodeWSP KisPaintDevice::parentNode() const { return m_d->parent; } void KisPaintDevice::setDefaultBounds(KisDefaultBoundsBaseSP defaultBounds) { m_d->defaultBounds = defaultBounds; m_d->cache()->invalidate(); } KisDefaultBoundsBaseSP KisPaintDevice::defaultBounds() const { return m_d->defaultBounds; } void KisPaintDevice::moveTo(const QPoint &pt) { m_d->currentStrategy()->move(pt); m_d->cache()->invalidate(); } QPoint KisPaintDevice::offset() const { return QPoint(x(), y()); } void KisPaintDevice::moveTo(qint32 x, qint32 y) { moveTo(QPoint(x, y)); } void KisPaintDevice::setX(qint32 x) { moveTo(QPoint(x, m_d->y())); } void KisPaintDevice::setY(qint32 y) { moveTo(QPoint(m_d->x(), y)); } qint32 KisPaintDevice::x() const { return m_d->x(); } qint32 KisPaintDevice::y() const { return m_d->y(); } void KisPaintDevice::extent(qint32 &x, qint32 &y, qint32 &w, qint32 &h) const { QRect rc = extent(); x = rc.x(); y = rc.y(); w = rc.width(); h = rc.height(); } QRect KisPaintDevice::extent() const { return m_d->currentStrategy()->extent(); } KisRegion KisPaintDevice::region() const { return m_d->currentStrategy()->region(); } QRect KisPaintDevice::nonDefaultPixelArea() const { return m_d->cache()->nonDefaultPixelArea(); } QRect KisPaintDevice::exactBounds() const { return m_d->cache()->exactBounds(); } QRect KisPaintDevice::exactBoundsAmortized() const { return m_d->cache()->exactBoundsAmortized(); } namespace Impl { struct CheckFullyTransparent { CheckFullyTransparent(const KoColorSpace *colorSpace) : m_colorSpace(colorSpace) { } bool isPixelEmpty(const quint8 *pixelData) { return m_colorSpace->opacityU8(pixelData) == OPACITY_TRANSPARENT_U8; } private: const KoColorSpace *m_colorSpace; }; struct CheckNonDefault { CheckNonDefault(int pixelSize, const quint8 *defaultPixel) : m_pixelSize(pixelSize), m_defaultPixel(defaultPixel) { } bool isPixelEmpty(const quint8 *pixelData) { return memcmp(m_defaultPixel, pixelData, m_pixelSize) == 0; } private: int m_pixelSize; const quint8 *m_defaultPixel; }; template QRect calculateExactBoundsImpl(const KisPaintDevice *device, const QRect &startRect, const QRect &endRect, ComparePixelOp compareOp) { if (startRect == endRect) return startRect; // the passed extent might have weird invalid structure that // can overflow integer precision when calling startRect.right() if (!startRect.isValid()) return QRect(); // Solution n°2 int x, y, w, h; int boundLeft, boundTop, boundRight, boundBottom; int endDirN, endDirE, endDirS, endDirW; startRect.getRect(&x, &y, &w, &h); if (endRect.isEmpty()) { endDirS = startRect.bottom(); endDirN = startRect.top(); endDirE = startRect.right(); endDirW = startRect.left(); startRect.getCoords(&boundLeft, &boundTop, &boundRight, &boundBottom); } else { endDirS = endRect.top() - 1; endDirN = endRect.bottom() + 1; endDirE = endRect.left() - 1; endDirW = endRect.right() + 1; endRect.getCoords(&boundLeft, &boundTop, &boundRight, &boundBottom); } // XXX: a small optimization is possible by using H/V line iterators in the first // and third cases, at the cost of making the code a bit more complex KisRandomConstAccessorSP accessor = device->createRandomConstAccessorNG(); bool found = false; { for (qint32 y2 = y; y2 <= endDirS; ++y2) { for (qint32 x2 = x; x2 < x + w || found; ++ x2) { accessor->moveTo(x2, y2); if (!compareOp.isPixelEmpty(accessor->rawDataConst())) { boundTop = y2; found = true; break; } } if (found) break; } } /** * If the first pass hasn't found any opaque pixel, there is no * reason to check that 3 more times. They will not appear in the * meantime. Just return an empty bounding rect. */ if (!found && endRect.isEmpty()) { return QRect(); } found = false; for (qint32 y2 = y + h - 1; y2 >= endDirN ; --y2) { for (qint32 x2 = x + w - 1; x2 >= x || found; --x2) { accessor->moveTo(x2, y2); if (!compareOp.isPixelEmpty(accessor->rawDataConst())) { boundBottom = y2; found = true; break; } } if (found) break; } found = false; { for (qint32 x2 = x; x2 <= endDirE ; ++x2) { for (qint32 y2 = y; y2 < y + h || found; ++y2) { accessor->moveTo(x2, y2); if (!compareOp.isPixelEmpty(accessor->rawDataConst())) { boundLeft = x2; found = true; break; } } if (found) break; } } found = false; // Look for right edge ) { for (qint32 x2 = x + w - 1; x2 >= endDirW; --x2) { for (qint32 y2 = y + h - 1; y2 >= y || found; --y2) { accessor->moveTo(x2, y2); if (!compareOp.isPixelEmpty(accessor->rawDataConst())) { boundRight = x2; found = true; break; } } if (found) break; } } return QRect(boundLeft, boundTop, boundRight - boundLeft + 1, boundBottom - boundTop + 1); } } QRect KisPaintDevice::calculateExactBounds(bool nonDefaultOnly) const { QRect startRect = extent(); QRect endRect; quint8 defaultOpacity = defaultPixel().opacityU8(); if (defaultOpacity != OPACITY_TRANSPARENT_U8) { if (!nonDefaultOnly) { /** * We will calculate exact bounds only outside of the * image bounds, and that'll be nondefault area only. */ endRect = defaultBounds()->bounds(); nonDefaultOnly = true; } else { startRect = region().boundingRect(); } } if (nonDefaultOnly) { const KoColor defaultPixel = this->defaultPixel(); Impl::CheckNonDefault compareOp(pixelSize(), defaultPixel.data()); endRect = Impl::calculateExactBoundsImpl(this, startRect, endRect, compareOp); } else { Impl::CheckFullyTransparent compareOp(m_d->colorSpace()); endRect = Impl::calculateExactBoundsImpl(this, startRect, endRect, compareOp); } return endRect; } KisRegion KisPaintDevice::regionExact() const { QVector sourceRects = region().rects(); QVector resultRects; const KoColor defaultPixel = this->defaultPixel(); Impl::CheckNonDefault compareOp(pixelSize(), defaultPixel.data()); Q_FOREACH (const QRect &rc1, sourceRects) { const int patchSize = 64; QVector smallerRects = KritaUtils::splitRectIntoPatches(rc1, QSize(patchSize, patchSize)); Q_FOREACH (const QRect &rc2, smallerRects) { const QRect result = Impl::calculateExactBoundsImpl(this, rc2, QRect(), compareOp); if (!result.isEmpty()) { resultRects << result; } } } return KisRegion(std::move(resultRects)); } void KisPaintDevice::crop(qint32 x, qint32 y, qint32 w, qint32 h) { crop(QRect(x, y, w, h)); } void KisPaintDevice::crop(const QRect &rect) { m_d->currentStrategy()->crop(rect); } void KisPaintDevice::purgeDefaultPixels() { KisDataManagerSP dm = m_d->dataManager(); dm->purge(dm->extent()); } void KisPaintDevice::setDefaultPixel(const KoColor &defPixel) { KoColor color(defPixel); color.convertTo(colorSpace()); m_d->dataManager()->setDefaultPixel(color.data()); m_d->cache()->invalidate(); } KoColor KisPaintDevice::defaultPixel() const { return KoColor(m_d->dataManager()->defaultPixel(), colorSpace()); } void KisPaintDevice::clear() { m_d->dataManager()->clear(); m_d->cache()->invalidate(); } void KisPaintDevice::clear(const QRect & rc) { m_d->currentStrategy()->clear(rc); } void KisPaintDevice::fill(const QRect & rc, const KoColor &color) { KIS_ASSERT_RECOVER_RETURN(*color.colorSpace() == *colorSpace()); m_d->currentStrategy()->fill(rc, color.data()); } void KisPaintDevice::fill(qint32 x, qint32 y, qint32 w, qint32 h, const quint8 *fillPixel) { m_d->currentStrategy()->fill(QRect(x, y, w, h), fillPixel); } bool KisPaintDevice::write(KisPaintDeviceWriter &store) { return m_d->dataManager()->write(store); } bool KisPaintDevice::read(QIODevice *stream) { bool retval; retval = m_d->dataManager()->read(stream); m_d->cache()->invalidate(); return retval; } void KisPaintDevice::emitColorSpaceChanged() { emit colorSpaceChanged(m_d->colorSpace()); } void KisPaintDevice::emitProfileChanged() { emit profileChanged(m_d->colorSpace()->profile()); } void KisPaintDevice::convertTo(const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags, KUndo2Command *parentCommand) { m_d->convertColorSpace(dstColorSpace, renderingIntent, conversionFlags, parentCommand); } bool KisPaintDevice::setProfile(const KoColorProfile * profile, KUndo2Command *parentCommand) { return m_d->assignProfile(profile, parentCommand); } +KUndo2Command *KisPaintDevice::reincarnateWithDetachedHistory(bool copyContent) +{ + return m_d->reincarnateWithDetachedHistory(copyContent); +} + KisDataManagerSP KisPaintDevice::dataManager() const { return m_d->dataManager(); } void KisPaintDevice::convertFromQImage(const QImage& _image, const KoColorProfile *profile, qint32 offsetX, qint32 offsetY) { QImage image = _image; if (image.format() != QImage::Format_ARGB32) { image = image.convertToFormat(QImage::Format_ARGB32); } // Don't convert if not no profile is given and both paint dev and qimage are rgba. if (!profile && colorSpace()->id() == "RGBA") { writeBytes(image.constBits(), offsetX, offsetY, image.width(), image.height()); } else { try { quint8 * dstData = new quint8[image.width() * image.height() * pixelSize()]; KoColorSpaceRegistry::instance() ->colorSpace(RGBAColorModelID.id(), Integer8BitsColorDepthID.id(), profile) ->convertPixelsTo(image.constBits(), dstData, colorSpace(), image.width() * image.height(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); writeBytes(dstData, offsetX, offsetY, image.width(), image.height()); delete[] dstData; } catch (const std::bad_alloc&) { warnKrita << "KisPaintDevice::convertFromQImage: Could not allocate" << image.width() * image.height() * pixelSize() << "bytes"; return; } } m_d->cache()->invalidate(); } QImage KisPaintDevice::convertToQImage(const KoColorProfile *dstProfile, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const { qint32 x1; qint32 y1; qint32 w; qint32 h; QRect rc = exactBounds(); x1 = rc.x(); y1 = rc.y(); w = rc.width(); h = rc.height(); return convertToQImage(dstProfile, x1, y1, w, h, renderingIntent, conversionFlags); } QImage KisPaintDevice::convertToQImage(const KoColorProfile *dstProfile, const QRect &rc, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const { return convertToQImage(dstProfile, rc.x(), rc.y(), rc.width(), rc.height(), renderingIntent, conversionFlags); } QImage KisPaintDevice::convertToQImage(const KoColorProfile *dstProfile, qint32 x1, qint32 y1, qint32 w, qint32 h, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const { if (w < 0) return QImage(); if (h < 0) return QImage(); quint8 *data = 0; try { data = new quint8 [w * h * pixelSize()]; } catch (const std::bad_alloc&) { warnKrita << "KisPaintDevice::convertToQImage std::bad_alloc for " << w << " * " << h << " * " << pixelSize(); //delete[] data; // data is not allocated, so don't free it return QImage(); } Q_CHECK_PTR(data); // XXX: Is this really faster than converting line by line and building the QImage directly? // This copies potentially a lot of data. readBytes(data, x1, y1, w, h); QImage image = colorSpace()->convertToQImage(data, w, h, dstProfile, renderingIntent, conversionFlags); delete[] data; return image; } inline bool moveBy(KisSequentialConstIterator& iter, int numPixels) { int pos = 0; while (pos < numPixels) { int step = std::min(iter.nConseqPixels(), numPixels - pos); if (!iter.nextPixels(step)) return false; pos += step; } return true; } static KisPaintDeviceSP createThumbnailDeviceInternal(const KisPaintDevice* srcDev, qint32 srcX0, qint32 srcY0, qint32 srcWidth, qint32 srcHeight, qint32 w, qint32 h, QRect outputRect) { KisPaintDeviceSP thumbnail = new KisPaintDevice(srcDev->colorSpace()); qint32 pixelSize = srcDev->pixelSize(); KisRandomConstAccessorSP srcIter = srcDev->createRandomConstAccessorNG(); KisRandomAccessorSP dstIter = thumbnail->createRandomAccessorNG(); for (qint32 y = outputRect.y(); y < outputRect.y() + outputRect.height(); ++y) { qint32 iY = srcY0 + (y * srcHeight) / h; for (qint32 x = outputRect.x(); x < outputRect.x() + outputRect.width(); ++x) { qint32 iX = srcX0 + (x * srcWidth) / w; srcIter->moveTo(iX, iY); dstIter->moveTo(x, y); memcpy(dstIter->rawData(), srcIter->rawDataConst(), pixelSize); } } return thumbnail; } QSize fixThumbnailSize(QSize size) { if (!size.width() && size.height()) { size.setWidth(1); } if (size.width() && !size.height()) { size.setHeight(1); } return size; } KisPaintDeviceSP KisPaintDevice::createThumbnailDevice(qint32 w, qint32 h, QRect rect, QRect outputRect) const { QSize thumbnailSize(w, h); QRect imageRect = rect.isValid() ? rect : extent(); if ((thumbnailSize.width() > imageRect.width()) || (thumbnailSize.height() > imageRect.height())) { thumbnailSize.scale(imageRect.size(), Qt::KeepAspectRatio); } thumbnailSize = fixThumbnailSize(thumbnailSize); //can't create thumbnail for an empty device, e.g. layer thumbnail for empty image if (imageRect.isEmpty() || thumbnailSize.isEmpty()) { return new KisPaintDevice(colorSpace()); } int srcWidth, srcHeight; int srcX0, srcY0; imageRect.getRect(&srcX0, &srcY0, &srcWidth, &srcHeight); if (!outputRect.isValid()) { outputRect = QRect(0, 0, w, h); } KisPaintDeviceSP thumbnail = createThumbnailDeviceInternal(this, imageRect.x(), imageRect.y(), imageRect.width(), imageRect.height(), thumbnailSize.width(), thumbnailSize.height(), outputRect); return thumbnail; } KisPaintDeviceSP KisPaintDevice::createThumbnailDeviceOversampled(qint32 w, qint32 h, qreal oversample, QRect rect, QRect outputTileRect) const { QSize thumbnailSize(w, h); qreal oversampleAdjusted = qMax(oversample, 1.); QSize thumbnailOversampledSize = oversampleAdjusted * thumbnailSize; QRect outputRect; QRect imageRect = rect.isValid() ? rect : extent(); qint32 hstart = thumbnailOversampledSize.height(); if ((thumbnailOversampledSize.width() > imageRect.width()) || (thumbnailOversampledSize.height() > imageRect.height())) { thumbnailOversampledSize.scale(imageRect.size(), Qt::KeepAspectRatio); } thumbnailOversampledSize = fixThumbnailSize(thumbnailOversampledSize); //can't create thumbnail for an empty device, e.g. layer thumbnail for empty image if (imageRect.isEmpty() || thumbnailSize.isEmpty() || thumbnailOversampledSize.isEmpty()) { return new KisPaintDevice(colorSpace()); } oversampleAdjusted *= (hstart > 0) ? ((qreal)thumbnailOversampledSize.height() / hstart) : 1.; //readjusting oversample ratio, given that we had to adjust thumbnail size outputRect = QRect(0, 0, thumbnailOversampledSize.width(), thumbnailOversampledSize.height()); if (outputTileRect.isValid()) { //compensating output rectangle for oversampling outputTileRect = QRect(oversampleAdjusted * outputTileRect.topLeft(), oversampleAdjusted * outputTileRect.bottomRight()); outputRect = outputRect.intersected(outputTileRect); } KisPaintDeviceSP thumbnail = createThumbnailDeviceInternal(this, imageRect.x(), imageRect.y(), imageRect.width(), imageRect.height(), thumbnailOversampledSize.width(), thumbnailOversampledSize.height(), outputRect); if (oversample != 1. && oversampleAdjusted != 1.) { KoDummyUpdater updater; KisTransformWorker worker(thumbnail, 1 / oversampleAdjusted, 1 / oversampleAdjusted, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, &updater, KisFilterStrategyRegistry::instance()->value("Bilinear")); worker.run(); } return thumbnail; } QImage KisPaintDevice::createThumbnail(qint32 w, qint32 h, QRect rect, qreal oversample, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { QSize size = fixThumbnailSize(QSize(w, h)); KisPaintDeviceSP dev = createThumbnailDeviceOversampled(size.width(), size.height(), oversample, rect); QImage thumbnail = dev->convertToQImage(KoColorSpaceRegistry::instance()->rgb8()->profile(), 0, 0, w, h, renderingIntent, conversionFlags); return thumbnail; } QImage KisPaintDevice::createThumbnail(qint32 w, qint32 h, qreal oversample, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { QSize size = fixThumbnailSize(QSize(w, h)); return m_d->cache()->createThumbnail(size.width(), size.height(), oversample, renderingIntent, conversionFlags); } +QImage KisPaintDevice::createThumbnail(qint32 maxw, qint32 maxh, + Qt::AspectRatioMode aspectRatioMode, + qreal oversample, KoColorConversionTransformation::Intent renderingIntent, + KoColorConversionTransformation::ConversionFlags conversionFlags) +{ + const QRect deviceExtent = extent(); + const QSize thumbnailSize = deviceExtent.size().scaled(maxw, maxh, aspectRatioMode); + return createThumbnail(thumbnailSize.width(), thumbnailSize.height(), + oversample, renderingIntent, conversionFlags); +} + KisHLineIteratorSP KisPaintDevice::createHLineIteratorNG(qint32 x, qint32 y, qint32 w) { m_d->cache()->invalidate(); return m_d->currentStrategy()->createHLineIteratorNG(m_d->dataManager().data(), x, y, w, m_d->x(), m_d->y()); } KisHLineConstIteratorSP KisPaintDevice::createHLineConstIteratorNG(qint32 x, qint32 y, qint32 w) const { return m_d->currentStrategy()->createHLineConstIteratorNG(m_d->dataManager().data(), x, y, w, m_d->x(), m_d->y()); } KisVLineIteratorSP KisPaintDevice::createVLineIteratorNG(qint32 x, qint32 y, qint32 w) { m_d->cache()->invalidate(); return m_d->currentStrategy()->createVLineIteratorNG(x, y, w); } KisVLineConstIteratorSP KisPaintDevice::createVLineConstIteratorNG(qint32 x, qint32 y, qint32 w) const { return m_d->currentStrategy()->createVLineConstIteratorNG(x, y, w); } KisRepeatHLineConstIteratorSP KisPaintDevice::createRepeatHLineConstIterator(qint32 x, qint32 y, qint32 w, const QRect& _dataWidth) const { return new KisRepeatHLineConstIteratorNG(m_d->dataManager().data(), x, y, w, m_d->x(), m_d->y(), _dataWidth, m_d->cacheInvalidator()); } KisRepeatVLineConstIteratorSP KisPaintDevice::createRepeatVLineConstIterator(qint32 x, qint32 y, qint32 h, const QRect& _dataWidth) const { return new KisRepeatVLineConstIteratorNG(m_d->dataManager().data(), x, y, h, m_d->x(), m_d->y(), _dataWidth, m_d->cacheInvalidator()); } KisRandomAccessorSP KisPaintDevice::createRandomAccessorNG() { m_d->cache()->invalidate(); return m_d->currentStrategy()->createRandomAccessorNG(); } KisRandomConstAccessorSP KisPaintDevice::createRandomConstAccessorNG() const { return m_d->currentStrategy()->createRandomConstAccessorNG(); } KisRandomSubAccessorSP KisPaintDevice::createRandomSubAccessor() const { KisPaintDevice* pd = const_cast(this); return new KisRandomSubAccessor(pd); } void KisPaintDevice::clearSelection(KisSelectionSP selection) { const KoColorSpace *colorSpace = m_d->colorSpace(); const QRect r = selection->selectedExactRect(); if (r.isValid()) { { KisHLineIteratorSP devIt = createHLineIteratorNG(r.x(), r.y(), r.width()); KisHLineConstIteratorSP selectionIt = selection->projection()->createHLineConstIteratorNG(r.x(), r.y(), r.width()); const KoColor defaultPixel = this->defaultPixel(); bool transparentDefault = (defaultPixel.opacityU8() == OPACITY_TRANSPARENT_U8); for (qint32 y = 0; y < r.height(); y++) { do { // XXX: Optimize by using stretches colorSpace->applyInverseAlphaU8Mask(devIt->rawData(), selectionIt->rawDataConst(), 1); if (transparentDefault && colorSpace->opacityU8(devIt->rawData()) == OPACITY_TRANSPARENT_U8) { memcpy(devIt->rawData(), defaultPixel.data(), colorSpace->pixelSize()); } } while (devIt->nextPixel() && selectionIt->nextPixel()); devIt->nextRow(); selectionIt->nextRow(); } } // purge() must be executed **after** all iterators have been destroyed! m_d->dataManager()->purge(r.translated(-m_d->x(), -m_d->y())); setDirty(r); } } bool KisPaintDevice::pixel(qint32 x, qint32 y, QColor *c) const { KisHLineConstIteratorSP iter = createHLineConstIteratorNG(x, y, 1); const quint8 *pix = iter->rawDataConst(); if (!pix) return false; colorSpace()->toQColor(pix, c); return true; } bool KisPaintDevice::pixel(qint32 x, qint32 y, KoColor * kc) const { KisHLineConstIteratorSP iter = createHLineConstIteratorNG(x, y, 1); const quint8 *pix = iter->rawDataConst(); if (!pix) return false; kc->setColor(pix, m_d->colorSpace()); return true; } KoColor KisPaintDevice::pixel(const QPoint &pos) const { KisHLineConstIteratorSP iter = createHLineConstIteratorNG(pos.x(), pos.y(), 1); return KoColor(iter->rawDataConst(), m_d->colorSpace()); } bool KisPaintDevice::setPixel(qint32 x, qint32 y, const QColor& c) { KisHLineIteratorSP iter = createHLineIteratorNG(x, y, 1); colorSpace()->fromQColor(c, iter->rawData()); m_d->cache()->invalidate(); return true; } bool KisPaintDevice::setPixel(qint32 x, qint32 y, const KoColor& kc) { const quint8 * pix; KisHLineIteratorSP iter = createHLineIteratorNG(x, y, 1); if (kc.colorSpace() != m_d->colorSpace()) { KoColor kc2(kc, m_d->colorSpace()); pix = kc2.data(); memcpy(iter->rawData(), pix, m_d->colorSpace()->pixelSize()); } else { pix = kc.data(); memcpy(iter->rawData(), pix, m_d->colorSpace()->pixelSize()); } m_d->cache()->invalidate(); return true; } bool KisPaintDevice::fastBitBltPossible(KisPaintDeviceSP src) { return m_d->fastBitBltPossible(src); } void KisPaintDevice::fastBitBlt(KisPaintDeviceSP src, const QRect &rect) { m_d->currentStrategy()->fastBitBlt(src, rect); } void KisPaintDevice::fastBitBltOldData(KisPaintDeviceSP src, const QRect &rect) { m_d->currentStrategy()->fastBitBltOldData(src, rect); } void KisPaintDevice::fastBitBltRough(KisPaintDeviceSP src, const QRect &rect) { m_d->currentStrategy()->fastBitBltRough(src, rect); } void KisPaintDevice::fastBitBltRoughOldData(KisPaintDeviceSP src, const QRect &rect) { m_d->currentStrategy()->fastBitBltRoughOldData(src, rect); } void KisPaintDevice::readBytes(quint8 * data, qint32 x, qint32 y, qint32 w, qint32 h) const { readBytes(data, QRect(x, y, w, h)); } void KisPaintDevice::readBytes(quint8 *data, const QRect &rect) const { m_d->currentStrategy()->readBytes(data, rect); } void KisPaintDevice::writeBytes(const quint8 *data, qint32 x, qint32 y, qint32 w, qint32 h) { writeBytes(data, QRect(x, y, w, h)); } void KisPaintDevice::writeBytes(const quint8 *data, const QRect &rect) { m_d->currentStrategy()->writeBytes(data, rect); } QVector KisPaintDevice::readPlanarBytes(qint32 x, qint32 y, qint32 w, qint32 h) const { return m_d->currentStrategy()->readPlanarBytes(x, y, w, h); } void KisPaintDevice::writePlanarBytes(QVector planes, qint32 x, qint32 y, qint32 w, qint32 h) { m_d->currentStrategy()->writePlanarBytes(planes, x, y, w, h); } quint32 KisPaintDevice::pixelSize() const { quint32 _pixelSize = m_d->colorSpace()->pixelSize(); Q_ASSERT(_pixelSize > 0); return _pixelSize; } quint32 KisPaintDevice::channelCount() const { quint32 _channelCount = m_d->colorSpace()->channelCount(); Q_ASSERT(_channelCount > 0); return _channelCount; } KisRasterKeyframeChannel *KisPaintDevice::createKeyframeChannel(const KoID &id) { Q_ASSERT(!m_d->framesInterface); m_d->framesInterface.reset(new KisPaintDeviceFramesInterface(this)); Q_ASSERT(!m_d->contentChannel); if (m_d->parent.isValid()) { m_d->contentChannel.reset(new KisRasterKeyframeChannel(id, this, m_d->parent)); } else { //fallback when paint device is isolated / does not belong to a node. ENTER_FUNCTION() << ppVar(this) << ppVar(m_d->defaultBounds); m_d->contentChannel.reset(new KisRasterKeyframeChannel(id, this, m_d->defaultBounds)); } // Raster channels always have at least one frame (representing a static image) KUndo2Command tempParentCommand; m_d->contentChannel->addKeyframe(0, &tempParentCommand); return m_d->contentChannel.data(); } KisRasterKeyframeChannel* KisPaintDevice::keyframeChannel() const { if (m_d->contentChannel) { return m_d->contentChannel.data(); } return 0; } const KoColorSpace* KisPaintDevice::colorSpace() const { Q_ASSERT(m_d->colorSpace() != 0); return m_d->colorSpace(); } KisPaintDeviceSP KisPaintDevice::createCompositionSourceDevice() const { KisPaintDeviceSP device = new KisPaintDevice(compositionSourceColorSpace()); device->setDefaultBounds(defaultBounds()); return device; } KisPaintDeviceSP KisPaintDevice::createCompositionSourceDevice(KisPaintDeviceSP cloneSource) const { KisPaintDeviceSP clone = new KisPaintDevice(*cloneSource); clone->setDefaultBounds(defaultBounds()); clone->convertTo(compositionSourceColorSpace(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); return clone; } KisPaintDeviceSP KisPaintDevice::createCompositionSourceDevice(KisPaintDeviceSP cloneSource, const QRect roughRect) const { KisPaintDeviceSP clone = new KisPaintDevice(colorSpace()); clone->setDefaultBounds(defaultBounds()); clone->makeCloneFromRough(cloneSource, roughRect); clone->convertTo(compositionSourceColorSpace(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); return clone; } KisFixedPaintDeviceSP KisPaintDevice::createCompositionSourceDeviceFixed() const { return new KisFixedPaintDevice(compositionSourceColorSpace()); } const KoColorSpace* KisPaintDevice::compositionSourceColorSpace() const { return colorSpace(); } QVector KisPaintDevice::channelSizes() const { QVector sizes; QList channels = colorSpace()->channels(); std::sort(channels.begin(), channels.end()); Q_FOREACH (KoChannelInfo * channelInfo, channels) { sizes.append(channelInfo->size()); } return sizes; } KisPaintDevice::MemoryReleaseObject::~MemoryReleaseObject() { KisDataManager::releaseInternalPools(); } KisPaintDevice::MemoryReleaseObject* KisPaintDevice::createMemoryReleaseObject() { return new MemoryReleaseObject(); } KisPaintDevice::LodDataStruct::~LodDataStruct() { } KisRegion KisPaintDevice::regionForLodSyncing() const { return m_d->regionForLodSyncing(); } KisPaintDevice::LodDataStruct* KisPaintDevice::createLodDataStruct(int lod) { return m_d->createLodDataStruct(lod); } void KisPaintDevice::updateLodDataStruct(LodDataStruct *dst, const QRect &srcRect) { m_d->updateLodDataStruct(dst, srcRect); } void KisPaintDevice::uploadLodDataStruct(LodDataStruct *dst) { m_d->uploadLodDataStruct(dst); } void KisPaintDevice::generateLodCloneDevice(KisPaintDeviceSP dst, const QRect &originalRect, int lod) { m_d->generateLodCloneDevice(dst, originalRect, lod); } KisPaintDeviceFramesInterface* KisPaintDevice::framesInterface() { return m_d->framesInterface.data(); } /******************************************************************/ /* KisPaintDeviceFramesInterface */ /******************************************************************/ KisPaintDeviceFramesInterface::KisPaintDeviceFramesInterface(KisPaintDevice *parentDevice) : q(parentDevice) { } QList KisPaintDeviceFramesInterface::frames() { return q->m_d->frameIds(); } int KisPaintDeviceFramesInterface::createFrame(bool copy, int copySrc, const QPoint &offset, KUndo2Command *parentCommand) { return q->m_d->createFrame(copy, copySrc, offset, parentCommand); } void KisPaintDeviceFramesInterface::deleteFrame(int frame, KUndo2Command *parentCommand) { return q->m_d->deleteFrame(frame, parentCommand); } void KisPaintDeviceFramesInterface::fetchFrame(int frameId, KisPaintDeviceSP targetDevice) { q->m_d->fetchFrame(frameId, targetDevice); } void KisPaintDeviceFramesInterface::uploadFrame(int srcFrameId, int dstFrameId, KisPaintDeviceSP srcDevice) { q->m_d->uploadFrame(srcFrameId, dstFrameId, srcDevice); } void KisPaintDeviceFramesInterface::uploadFrame(int dstFrameId, KisPaintDeviceSP srcDevice) { q->m_d->uploadFrame(dstFrameId, srcDevice); } QRect KisPaintDeviceFramesInterface::frameBounds(int frameId) { return q->m_d->frameBounds(frameId); } QPoint KisPaintDeviceFramesInterface::frameOffset(int frameId) const { return q->m_d->frameOffset(frameId); } void KisPaintDeviceFramesInterface::setFrameDefaultPixel(const KoColor &defPixel, int frameId) { KIS_ASSERT_RECOVER_RETURN(frameId >= 0); q->m_d->setFrameDefaultPixel(defPixel, frameId); } KoColor KisPaintDeviceFramesInterface::frameDefaultPixel(int frameId) const { KIS_ASSERT_RECOVER(frameId >= 0) { return KoColor(Qt::red, q->m_d->colorSpace()); } return q->m_d->frameDefaultPixel(frameId); } bool KisPaintDeviceFramesInterface::writeFrame(KisPaintDeviceWriter &store, int frameId) { KIS_ASSERT_RECOVER(frameId >= 0) { return false; } return q->m_d->writeFrame(store, frameId); } bool KisPaintDeviceFramesInterface::readFrame(QIODevice *stream, int frameId) { KIS_ASSERT_RECOVER(frameId >= 0) { return false; } return q->m_d->readFrame(stream, frameId); } int KisPaintDeviceFramesInterface::currentFrameId() const { return q->m_d->currentFrameId(); } KisDataManagerSP KisPaintDeviceFramesInterface::frameDataManager(int frameId) const { KIS_ASSERT_RECOVER(frameId >= 0) { return q->m_d->dataManager(); } return q->m_d->frameDataManager(frameId); } void KisPaintDeviceFramesInterface::invalidateFrameCache(int frameId) { KIS_ASSERT_RECOVER_RETURN(frameId >= 0); return q->m_d->invalidateFrameCache(frameId); } void KisPaintDeviceFramesInterface::setFrameOffset(int frameId, const QPoint &offset) { KIS_ASSERT_RECOVER_RETURN(frameId >= 0); return q->m_d->setFrameOffset(frameId, offset); } KisPaintDeviceFramesInterface::TestingDataObjects KisPaintDeviceFramesInterface::testingGetDataObjects() const { TestingDataObjects objects; objects.m_data = q->m_d->m_data.data(); objects.m_lodData = q->m_d->m_lodData.data(); objects.m_externalFrameData = q->m_d->m_externalFrameData.data(); typedef KisPaintDevice::Private::FramesHash FramesHash; FramesHash::const_iterator it = q->m_d->m_frames.constBegin(); FramesHash::const_iterator end = q->m_d->m_frames.constEnd(); for (; it != end; ++it) { objects.m_frames.insert(it.key(), it.value().data()); } objects.m_currentData = q->m_d->currentData(); return objects; } QList KisPaintDeviceFramesInterface::testingGetDataObjectsList() const { return q->m_d->allDataObjects(); } void KisPaintDevice::tesingFetchLodDevice(KisPaintDeviceSP targetDevice) { m_d->tesingFetchLodDevice(targetDevice); } diff --git a/libs/image/kis_paint_device.h b/libs/image/kis_paint_device.h index f2ea29725b..709ada1969 100644 --- a/libs/image/kis_paint_device.h +++ b/libs/image/kis_paint_device.h @@ -1,898 +1,927 @@ /* * Copyright (c) 2002 patrick julien * Copyright (c) 2006 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_PAINT_DEVICE_IMPL_H_ #define KIS_PAINT_DEVICE_IMPL_H_ #include #include #include #include "kis_debug.h" #include #include "kis_types.h" #include "kis_shared.h" #include "kis_default_bounds_base.h" #include class KUndo2Command; class QRect; class QImage; class QPoint; class QString; class QColor; class QIODevice; class KoColor; class KoColorSpace; class KoColorProfile; class KisRegion; class KisDataManager; class KisPaintDeviceWriter; class KisKeyframe; class KisRasterKeyframeChannel; class KisPaintDeviceFramesInterface; typedef KisSharedPtr KisDataManagerSP; namespace KritaUtils { enum DeviceCopyMode { CopySnapshot = 0, CopyAllFrames }; } /** * A paint device contains the actual pixel data and offers methods * to read and write pixels. A paint device has an integer x, y position * (it is not positioned on the image with sub-pixel accuracy). * A KisPaintDevice doesn't have any fixed size, the size changes dynamically * when pixels are accessed by an iterator. */ class KRITAIMAGE_EXPORT KisPaintDevice : public QObject , public KisShared { Q_OBJECT public: /** * Create a new paint device with the specified colorspace. * * @param colorSpace the colorspace of this paint device * @param name for debugging purposes */ explicit KisPaintDevice(const KoColorSpace * colorSpace, const QString& name = QString()); /** * Create a new paint device with the specified colorspace. The * parent node will be notified of changes to this paint device. * * @param parent the node that contains this paint device * @param colorSpace the colorspace of this paint device * @param defaultBounds boundaries of the device in case it is empty * @param name for debugging purposes */ KisPaintDevice(KisNodeWSP parent, const KoColorSpace * colorSpace, KisDefaultBoundsBaseSP defaultBounds = KisDefaultBoundsBaseSP(), const QString& name = QString()); /** * Creates a copy of this device. * * If \p copyMode is CopySnapshot, the newly created device clones the * current frame of \p rhs only (default and efficient * behavior). If \p copyFrames is CopyAllFrames, the new device is a deep * copy of the source with all the frames included. */ KisPaintDevice(const KisPaintDevice& rhs, KritaUtils::DeviceCopyMode copyMode = KritaUtils::CopySnapshot, KisNode *newParentNode = 0); ~KisPaintDevice() override; void makeFullCopyFrom(const KisPaintDevice& rhs, KritaUtils::DeviceCopyMode copyMode = KritaUtils::CopySnapshot, KisNode *newParentNode = 0); protected: /** * A special constructor for usage in KisPixelSelection. It allows * two paint devices to share a data manager. * * @param explicitDataManager data manager to use inside paint device * @param src source paint device to copy parameters from * @param name for debugging purposes */ KisPaintDevice(KisDataManagerSP explicitDataManager, KisPaintDeviceSP src, const QString& name = QString()); public: /** * Write the pixels of this paint device into the specified file store. */ bool write(KisPaintDeviceWriter &store); /** * Fill this paint device with the pixels from the specified file store. */ bool read(QIODevice *stream); public: /** * set the parent node of the paint device */ void setParentNode(KisNodeWSP parent); /** * set the default bounds for the paint device when * the default pixel is not completely transparent */ void setDefaultBounds(KisDefaultBoundsBaseSP bounds); /** * the default bounds rect of the paint device */ KisDefaultBoundsBaseSP defaultBounds() const; /** * Moves the device to these new coordinates (no incremental move) */ void moveTo(qint32 x, qint32 y); /** * Convenience method for the above. */ virtual void moveTo(const QPoint& pt); /** * Return an X,Y offset of the device in a convenient form */ QPoint offset() const; /** * The X offset of the paint device */ qint32 x() const; /** * The Y offset of the paint device */ qint32 y() const; /** * set the X offset of the paint device */ void setX(qint32 x); /** * set the Y offset of the paint device */ void setY(qint32 y); /** * Retrieve the bounds of the paint device. The size is not exact, * but may be larger if the underlying datamanager works that way. * For instance, the tiled datamanager keeps the extent to the nearest * multiple of 64. * * If default pixel is not transparent, then the actual extent * rect is united with the defaultBounds()->bounds() value * (the size of the image, usually). */ QRect extent() const; /// Convenience method for the above void extent(qint32 &x, qint32 &y, qint32 &w, qint32 &h) const; /** * Get the exact bounds of this paint device. The real solution is * very slow because it does a linear scanline search, but it * uses caching, so calling to this function without changing * the device is quite cheap. * * Exactbounds follows these rules: * *
    *
  • if default pixel is transparent, then exact bounds * of actual pixel data are returned *
  • if default pixel is not transparent, then the union * (defaultBounds()->bounds() | nonDefaultPixelArea()) is * returned *
* \see calculateExactBounds() */ QRect exactBounds() const; /** * Relaxed version of the exactBounds() that can be used in tight * loops. If the exact bounds value is present in the paint * device cache, returns this value. If the cache is invalidated, * returns extent() and tries to recalculate the exact bounds not * faster than once in 1000 ms. */ QRect exactBoundsAmortized() const; /** * Returns exact rectangle of the paint device that contains * non-default pixels. For paint devices with fully transparent * default pixel is equivalent to exactBounds(). * * nonDefaultPixelArea() follows these rules: * *
    *
  • if default pixel is transparent, then exact bounds * of actual pixel data are returned. The same as exactBounds() *
  • if default pixel is not transparent, then calculates the * rectangle of non-default pixels. May be smaller or greater * than image bounds *
* \see calculateExactBounds() */ QRect nonDefaultPixelArea() const; /** * Returns a rough approximation of region covered by device. * For tiled data manager, it region will consist of a number * of rects each corresponding to a tile. */ KisRegion region() const; /** * The slow version of region() that searches for exact bounds of * each rectangle in the region */ KisRegion regionExact() const; /** * Cut the paint device down to the specified rect. If the crop * area is bigger than the paint device, nothing will happen. */ void crop(qint32 x, qint32 y, qint32 w, qint32 h); /// Convenience method for the above void crop(const QRect & r); /** * Complete erase the current paint device. Its size will become 0. This * does not take the selection into account. */ virtual void clear(); /** * Clear the given rectangle to transparent black. The paint device will expand to * contain the given rect. */ void clear(const QRect & rc); /** * Frees the memory occupied by the pixels containing default * values. The extents() and exactBounds() of the paint device will * probably also shrink */ void purgeDefaultPixels(); /** * Sets the default pixel. New data will be initialised with this pixel. The pixel is copied: the * caller still owns the pointer and needs to delete it to avoid memory leaks. * If frame ID is given, set default pixel for that frame. Otherwise use active frame. */ void setDefaultPixel(const KoColor &defPixel); /** * Get a pointer to the default pixel. * If the frame parameter is given, get the default pixel of * specified frame. Otherwise use currently active frame. */ KoColor defaultPixel() const; /** * Fill the given rectangle with the given pixel. The paint device will expand to * contain the given rect. */ void fill(const QRect & rc, const KoColor &color); /** * Overloaded function. For legacy purposes only. * Please use fill(const QRect & rc, const KoColor &color) instead */ void fill(qint32 x, qint32 y, qint32 w, qint32 h, const quint8 *fillPixel); public: /** * Prepares the device for fastBitBlt operation. It clears * the device, switches x,y shifts and colorspace if needed. * After this call fastBitBltPossible will return true. * May be used for initialization of temporary devices. */ void prepareClone(KisPaintDeviceSP src); /** * Make this device to become a clone of \a src. It will have the same * x,y shifts, colorspace and will share pixels inside \a rect. * After calling this function: * (this->extent() >= this->exactBounds() == rect). * * Rule of thumb: * * "Use makeCloneFrom() or makeCloneFromRough() if and only if you * are the only owner of the destination paint device and you are * 100% sure no other thread has access to it" */ void makeCloneFrom(KisPaintDeviceSP src, const QRect &rect); /** * Make this device to become a clone of \a src. It will have the same * x,y shifts, colorspace and will share pixels inside \a rect. * Be careful, this function will copy *at least* \a rect * of pixels. Actual copy area will be a bigger - it will * be aligned by tiles borders. So after calling this function: * (this->extent() == this->exactBounds() >= rect). * * Rule of thumb: * * "Use makeCloneFrom() or makeCloneFromRough() if and only if you * are the only owner of the destination paint device and you are * 100% sure no other thread has access to it" */ void makeCloneFromRough(KisPaintDeviceSP src, const QRect &minimalRect); protected: friend class KisPaintDeviceTest; friend class DataReaderThread; /** * Checks whether a src paint device can be used as source * of fast bitBlt operation. The result of the check may * depend on whether color spaces coincide, whether there is * any shift of tiles between the devices and etc. * * WARNING: This check must be done before performing any * fast bitBlt operation! * * \see fastBitBlt * \see fastBitBltRough */ bool fastBitBltPossible(KisPaintDeviceSP src); /** * Clones rect from another paint device. The cloned area will be * shared between both paint devices as much as possible using * copy-on-write. Parts of the rect that cannot be shared * (cross tiles) are deep-copied, * * \see fastBitBltPossible * \see fastBitBltRough */ void fastBitBlt(KisPaintDeviceSP src, const QRect &rect); /** * The same as \ref fastBitBlt() but reads old data */ void fastBitBltOldData(KisPaintDeviceSP src, const QRect &rect); /** * Clones rect from another paint device in a rough and fast way. * All the tiles touched by rect will be shared, between both * devices, that means it will copy a bigger area than was * requested. This method is supposed to be used for bitBlt'ing * into temporary paint devices. * * \see fastBitBltPossible * \see fastBitBlt */ void fastBitBltRough(KisPaintDeviceSP src, const QRect &rect); /** * The same as \ref fastBitBltRough() but reads old data */ void fastBitBltRoughOldData(KisPaintDeviceSP src, const QRect &rect); public: /** * Read the bytes representing the rectangle described by x, y, w, h into * data. If data is not big enough, Krita will gladly overwrite the rest * of your precious memory. * * Since this is a copy, you need to make sure you have enough memory. * * Reading from areas not previously initialized will read the default * pixel value into data but not initialize that region. */ void readBytes(quint8 * data, qint32 x, qint32 y, qint32 w, qint32 h) const; /** * Read the bytes representing the rectangle rect into * data. If data is not big enough, Krita will gladly overwrite the rest * of your precious memory. * * Since this is a copy, you need to make sure you have enough memory. * * Reading from areas not previously initialized will read the default * pixel value into data but not initialize that region. * @param data The address of the memory to receive the bytes read * @param rect The rectangle in the paint device to read from */ void readBytes(quint8 * data, const QRect &rect) const; /** * Copy the bytes in data into the rect specified by x, y, w, h. If the * data is too small or uninitialized, Krita will happily read parts of * memory you never wanted to be read. * * If the data is written to areas of the paint device not previously initialized, * the paint device will grow. */ void writeBytes(const quint8 * data, qint32 x, qint32 y, qint32 w, qint32 h); /** * Copy the bytes in data into the rectangle rect. If the * data is too small or uninitialized, Krita will happily read parts of * memory you never wanted to be read. * * If the data is written to areas of the paint device not previously initialized, * the paint device will grow. * @param data The address of the memory to write bytes from * @param rect The rectangle in the paint device to write to */ void writeBytes(const quint8 * data, const QRect &rect); /** * Copy the bytes in the paint device into a vector of arrays of bytes, * where the number of arrays is the number of channels in the * paint device. If the specified area is larger than the paint * device's extent, the default pixel will be read. */ QVector readPlanarBytes(qint32 x, qint32 y, qint32 w, qint32 h) const; /** * Write the data in the separate arrays to the channes. If there * are less vectors than channels, the remaining channels will not * be copied. If any of the arrays points to 0, the channel in * that location will not be touched. If the specified area is * larger than the paint device, the paint device will be * extended. There are no guards: if the area covers more pixels * than there are bytes in the arrays, krita will happily fill * your paint device with areas of memory you never wanted to be * read. Krita may also crash. * * XXX: what about undo? */ void writePlanarBytes(QVector planes, qint32 x, qint32 y, qint32 w, qint32 h); /** * Converts the paint device to a different colorspace */ void convertTo(const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::internalConversionFlags(), KUndo2Command *parentCommand = 0); /** * Changes the profile of the colorspace of this paint device to the given * profile. If the given profile is 0, nothing happens. */ bool setProfile(const KoColorProfile * profile, KUndo2Command *parentCommand); /** * Fill this paint device with the data from image; starting at (offsetX, offsetY) * @param image the image * @param profile name of the RGB profile to interpret the image as. 0 is interpreted as sRGB * @param offsetX x offset * @param offsetY y offset */ void convertFromQImage(const QImage& image, const KoColorProfile *profile, qint32 offsetX = 0, qint32 offsetY = 0); /** * Create an RGBA QImage from a rectangle in the paint device. * * @param dstProfile RGB profile to use in conversion. May be 0, in which * case it's up to the color strategy to choose a profile (most * like sRGB). * @param x Left coordinate of the rectangle * @param y Top coordinate of the rectangle * @param w Width of the rectangle in pixels * @param h Height of the rectangle in pixels * @param renderingIntent Rendering intent * @param conversionFlags Conversion flags */ QImage convertToQImage(const KoColorProfile *dstProfile, qint32 x, qint32 y, qint32 w, qint32 h, KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::internalConversionFlags()) const; /** * Overridden method for convenience */ QImage convertToQImage(const KoColorProfile *dstProfile, const QRect &rc, KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::internalConversionFlags()) const; /** * Create an RGBA QImage from a rectangle in the paint device. The * rectangle is defined by the parent image's bounds. * * @param dstProfile RGB profile to use in conversion. May be 0, in which * case it's up to the color strategy to choose a profile (most * like sRGB). * @param renderingIntent Rendering intent * @param conversionFlags Conversion flags */ QImage convertToQImage(const KoColorProfile * dstProfile, KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::internalConversionFlags()) const; /** * Creates a paint device thumbnail of the paint device, retaining * the aspect ratio. The width and height of the returned device * won't exceed \p maxw and \p maxw, but they may be smaller. * * @param w maximum width * @param h maximum height * @param rect only this rect will be used for the thumbnail * @param outputRect output rectangle * */ KisPaintDeviceSP createThumbnailDevice(qint32 w, qint32 h, QRect rect = QRect(), QRect outputRect = QRect()) const; KisPaintDeviceSP createThumbnailDeviceOversampled(qint32 w, qint32 h, qreal oversample, QRect rect = QRect(), QRect outputRect = QRect()) const; /** * Creates a thumbnail of the paint device, retaining the aspect ratio. * The width and height of the returned QImage won't exceed \p maxw and \p maxw, but they may be smaller. * The colors are not corrected for display! * * @param maxw: maximum width * @param maxh: maximum height * @param rect: only this rect will be used for the thumbnail * @param oversample: ratio used for antialiasing * @param renderingIntent Rendering intent * @param conversionFlags Conversion flags */ QImage createThumbnail(qint32 maxw, qint32 maxh, QRect rect, qreal oversample = 1, KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::internalConversionFlags()); /** * Cached version of createThumbnail(qint32 maxw, qint32 maxh, const KisSelection *selection, QRect rect) */ QImage createThumbnail(qint32 maxw, qint32 maxh, qreal oversample = 1, KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::internalConversionFlags()); + /** + * Cached version of createThumbnail that also adjusts aspect ratio of the + * thumbnail to fit the extents of the paint device. + */ + QImage createThumbnail(qint32 maxw, qint32 maxh, + Qt::AspectRatioMode aspectRatioMode, + qreal oversample = 1, + KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::internalRenderingIntent(), + KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::internalConversionFlags()); + /** * Fill c and opacity with the values found at x and y. * * The color values will be transformed from the profile of * this paint device to the display profile. * * @return true if the operation was successful. */ bool pixel(qint32 x, qint32 y, QColor *c) const; /** * Fill kc with the values found at x and y. This method differs * from the above in using KoColor, which can be of any colorspace * * The color values will be transformed from the profile of * this paint device to the display profile. * * @return true if the operation was successful. */ bool pixel(qint32 x, qint32 y, KoColor * kc) const; /** * Return pixel value in a form of KoColor. Please don't use this method * for iteration, it is highly inefficient. Use iterators instead. */ KoColor pixel(const QPoint &pos) const; /** * Set the specified pixel to the specified color. Note that this * bypasses KisPainter. the PaintDevice is here used as an equivalent * to QImage, not QPixmap. This means that this is not undoable; also, * there is no compositing with an existing value at this location. * * The color values will be transformed from the display profile to * the paint device profile. * * Note that this will use 8-bit values and may cause a significant * degradation when used on 16-bit or hdr quality images. * * @return true if the operation was successful */ bool setPixel(qint32 x, qint32 y, const QColor& c); /// Convenience method for the above bool setPixel(qint32 x, qint32 y, const KoColor& kc); /** * @return the colorspace of the pixels in this paint device */ const KoColorSpace* colorSpace() const; /** * There is quite a common technique in Krita. It is used in * cases, when we want to paint something over a paint device * using the composition, opacity or selection. E.g. painting a * dab in a paint op, filling the selection in the Fill Tool. * Such work is usually done in the following way: * * 1) Create a paint device * * 2) Fill it with the desired color or data * * 3) Create a KisPainter and set all the properties of the * transaction: selection, compositeOp, opacity and etc. * * 4) Paint a newly created paint device over the destination * device. * * The following two methods (createCompositionSourceDevice() or * createCompositionSourceDeviceFixed())should be used for the * accomplishing the step 1). The point is that the desired color * space of the temporary device may not coincide with the color * space of the destination. That is the case, for example, for * the alpha8() colorspace used in the selections. So for such * devices the temporary target would have a different (grayscale) * color space. * * So there are two rules of thumb: * * 1) If you need a temporary device which you are going to fill * with some data and then paint over the paint device, create * it with either createCompositionSourceDevice() or * createCompositionSourceDeviceFixed(). * * 2) Do *not* expect that the color spaces of the destination and * the temporary device would coincide. If you need to copy a * single pixel from one device to another, you can use * KisCrossDeviceColorPicker class, that will handle all the * necessary conversions for you. * * \see createCompositionSourceDeviceFixed() * \see compositionSourceColorSpace() * \see KisCrossDeviceColorPicker * \see KisCrossDeviceColorPickerInt */ KisPaintDeviceSP createCompositionSourceDevice() const; /** * The same as createCompositionSourceDevice(), but initializes the * newly created device with the content of \p cloneSource * * \see createCompositionSourceDevice() */ KisPaintDeviceSP createCompositionSourceDevice(KisPaintDeviceSP cloneSource) const; /** * The same as createCompositionSourceDevice(), but initializes * the newly created device with the *rough* \p roughRect of * \p cloneSource. * * "Rough rect" means that it may copy a bit more than * requested. It is expected that the caller will not use the area * outside \p roughRect. * * \see createCompositionSourceDevice() */ KisPaintDeviceSP createCompositionSourceDevice(KisPaintDeviceSP cloneSource, const QRect roughRect) const; /** * This is a convenience method for createCompositionSourceDevice() * * \see createCompositionSourceDevice() */ KisFixedPaintDeviceSP createCompositionSourceDeviceFixed() const; /** * This is a lowlevel method for the principle used in * createCompositionSourceDevice(). In most of the cases the paint * device creation methods should be used instead of this function. * * \see createCompositionSourceDevice() * \see createCompositionSourceDeviceFixed() */ virtual const KoColorSpace* compositionSourceColorSpace() const; /** * @return the internal datamanager that keeps the pixels. */ KisDataManagerSP dataManager() const; /** * Replace the pixel data, color strategy, and profile. */ void setDataManager(KisDataManagerSP data, const KoColorSpace * colorSpace = 0); /** * Return the number of bytes a pixel takes. */ quint32 pixelSize() const; /** * Return the number of channels a pixel takes */ quint32 channelCount() const; /** * Create a keyframe channel for the content on this device. * @param id identifier for the channel * @return keyframe channel or 0 if there is not one */ KisRasterKeyframeChannel *createKeyframeChannel(const KoID &id); KisRasterKeyframeChannel* keyframeChannel() const; /** * An interface to modify/load/save frames stored inside this device */ KisPaintDeviceFramesInterface* framesInterface(); public: /** * Add the specified rect to the parent layer's set of dirty rects * (if there is a parent layer) */ void setDirty(const QRect & rc); void setDirty(const KisRegion ®ion); /** * Set the parent layer completely dirty, if this paint device has * as parent layer. */ void setDirty(); void setDirty(const QVector &rects); /** * Called by KisTransactionData when it thinks current time should * be changed. And the requests is forwarded to the image if * needed. */ void requestTimeSwitch(int time); /** * \return a sequence number corresponding to the current paint * device state. Every time the paint device is changed, * the sequence number is increased */ int sequenceNumber() const; void estimateMemoryStats(qint64 &imageData, qint64 &temporaryData, qint64 &lodData) const; public: KisHLineIteratorSP createHLineIteratorNG(qint32 x, qint32 y, qint32 w); KisHLineConstIteratorSP createHLineConstIteratorNG(qint32 x, qint32 y, qint32 w) const; KisVLineIteratorSP createVLineIteratorNG(qint32 x, qint32 y, qint32 h); KisVLineConstIteratorSP createVLineConstIteratorNG(qint32 x, qint32 y, qint32 h) const; KisRandomAccessorSP createRandomAccessorNG(); KisRandomConstAccessorSP createRandomConstAccessorNG() const; /** * Create an iterator that will "artificially" extend the paint device with the * value of the border when trying to access values outside the range of data. * * @param x x of top left corner * @param y y of top left corner * @param w width of the border * @param _dataWidth indicates the rectangle that truly contains data */ KisRepeatHLineConstIteratorSP createRepeatHLineConstIterator(qint32 x, qint32 y, qint32 w, const QRect& _dataWidth) const; /** * Create an iterator that will "artificially" extend the paint device with the * value of the border when trying to access values outside the range of data. * * @param x x of top left corner * @param y y of top left corner * @param h height of the border * @param _dataWidth indicates the rectangle that truly contains data */ KisRepeatVLineConstIteratorSP createRepeatVLineConstIterator(qint32 x, qint32 y, qint32 h, const QRect& _dataWidth) const; /** * This function create a random accessor which can easily access to sub pixel values. */ KisRandomSubAccessorSP createRandomSubAccessor() const; /** Clear the selected pixels from the paint device */ void clearSelection(KisSelectionSP selection); + /** + * Converts a paint device into a "new" paint device, that has + * unconnected history. That is, after reincarnation, the device's + * life starts a new page. No history. No memories. + * + * When the device is fed up with the new life, it can reincarnate + * back to its previous life by undoing the command returned by + * reincarnateWithDetachedHistory(). The old undo will continue + * working as if nothing has happened. + * + * NOTE: reincarnation affects only the current lod plane and/or + * current frame. All other frames are kept unaffected. + * + * @param copyContent decides if the device should take its current + * content to the new life + * @return undo command for execution and undoing of the reincarnation + */ + KUndo2Command* reincarnateWithDetachedHistory(bool copyContent); + Q_SIGNALS: void profileChanged(const KoColorProfile * profile); void colorSpaceChanged(const KoColorSpace *colorspace); public: friend class PaintDeviceCache; /** * Caclculates exact bounds of the device. Used internally * by a transparent caching system. The solution is very slow * because it does a linear scanline search. So the complexity * is n*n at worst. * * \see exactBounds(), nonDefaultPixelArea() */ QRect calculateExactBounds(bool nonDefaultOnly) const; public: struct MemoryReleaseObject : public QObject { ~MemoryReleaseObject() override; }; static MemoryReleaseObject* createMemoryReleaseObject(); public: struct LodDataStruct { virtual ~LodDataStruct(); }; KisRegion regionForLodSyncing() const; LodDataStruct* createLodDataStruct(int lod); void updateLodDataStruct(LodDataStruct *dst, const QRect &srcRect); void uploadLodDataStruct(LodDataStruct *dst); void generateLodCloneDevice(KisPaintDeviceSP dst, const QRect &originalRect, int lod); void setProjectionDevice(bool value); void tesingFetchLodDevice(KisPaintDeviceSP targetDevice); private: KisPaintDevice& operator=(const KisPaintDevice&); void init(const KoColorSpace *colorSpace, KisDefaultBoundsBaseSP defaultBounds, KisNodeWSP parent, const QString& name); // Only KisPainter is allowed to have access to these low-level methods friend class KisPainter; /** * Return a vector with in order the size in bytes of the channels * in the colorspace of this paint device. */ QVector channelSizes() const; void emitColorSpaceChanged(); void emitProfileChanged(); private: friend class KisPaintDeviceFramesInterface; protected: friend class KisSelectionTest; KisNodeWSP parentNode() const; private: struct Private; Private * const m_d; }; #endif // KIS_PAINT_DEVICE_IMPL_H_ diff --git a/libs/image/kis_paint_device_data.h b/libs/image/kis_paint_device_data.h index 836b972d63..aa6624dc31 100644 --- a/libs/image/kis_paint_device_data.h +++ b/libs/image/kis_paint_device_data.h @@ -1,317 +1,357 @@ /* * 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_PAINT_DEVICE_DATA_H #define __KIS_PAINT_DEVICE_DATA_H #include "KoAlwaysInline.h" #include "kundo2command.h" +#include "kis_command_utils.h" struct DirectDataAccessPolicy { DirectDataAccessPolicy(KisDataManager *dataManager, KisIteratorCompleteListener *completionListener) : m_dataManager(dataManager), m_completionListener(completionListener){} KisHLineConstIteratorSP createConstIterator(const QRect &rect) { const int xOffset = 0; const int yOffset = 0; return new KisHLineIterator2(m_dataManager, rect.x(), rect.y(), rect.width(), xOffset, yOffset, false, m_completionListener); } KisHLineIteratorSP createIterator(const QRect &rect) { const int xOffset = 0; const int yOffset = 0; return new KisHLineIterator2(m_dataManager, rect.x(), rect.y(), rect.width(), xOffset, yOffset, true, m_completionListener); } int pixelSize() const { return m_dataManager->pixelSize(); } KisDataManager *m_dataManager; KisIteratorCompleteListener *m_completionListener; }; class KisPaintDeviceData { public: KisPaintDeviceData(KisPaintDevice *paintDevice) : m_cache(paintDevice), m_x(0), m_y(0), m_colorSpace(0), m_levelOfDetail(0), m_cacheInvalidator(this) { } KisPaintDeviceData(KisPaintDevice *paintDevice, const KisPaintDeviceData *rhs, bool cloneContent) : m_dataManager(cloneContent ? new KisDataManager(*rhs->m_dataManager) : new KisDataManager(rhs->m_dataManager->pixelSize(), rhs->m_dataManager->defaultPixel())), m_cache(paintDevice), m_x(rhs->m_x), m_y(rhs->m_y), m_colorSpace(rhs->m_colorSpace), m_levelOfDetail(rhs->m_levelOfDetail), m_cacheInvalidator(this) { m_cache.setupCache(); } void init(const KoColorSpace *cs, KisDataManagerSP dataManager) { m_colorSpace = cs; m_dataManager = dataManager; m_cache.setupCache(); } class ChangeProfileCommand : public KUndo2Command { public: ChangeProfileCommand(KisPaintDeviceData *data, const KoColorSpace *oldCs, const KoColorSpace *newCs, KUndo2Command *parent) : KUndo2Command(parent), m_data(data), m_oldCs(oldCs), m_newCs(newCs) { } virtual void forcedRedo() { m_data->m_colorSpace = m_newCs; m_data->m_cache.setupCache(); } void redo() override { if (m_firstRun) { m_firstRun = false; return; } KUndo2Command::redo(); forcedRedo(); } void undo() override { m_data->m_colorSpace = m_oldCs; m_data->m_cache.setupCache(); KUndo2Command::undo(); } protected: KisPaintDeviceData *m_data; private: bool m_firstRun {true}; const KoColorSpace *m_oldCs; const KoColorSpace *m_newCs; }; class ChangeColorSpaceCommand : public ChangeProfileCommand { public: ChangeColorSpaceCommand(KisPaintDeviceData *data, KisDataManagerSP oldDm, KisDataManagerSP newDm, const KoColorSpace *oldCs, const KoColorSpace *newCs, KUndo2Command *parent) : ChangeProfileCommand(data, oldCs, newCs, parent), m_oldDm(oldDm), m_newDm(newDm) { } void forcedRedo() override { m_data->m_dataManager = m_newDm; ChangeProfileCommand::forcedRedo(); } void undo() override { m_data->m_dataManager = m_oldDm; ChangeProfileCommand::undo(); } private: KisDataManagerSP m_oldDm; KisDataManagerSP m_newDm; }; void assignColorSpace(const KoColorSpace *dstColorSpace, KUndo2Command *parentCommand) { if (*m_colorSpace->profile() == *dstColorSpace->profile()) return; KIS_ASSERT_RECOVER_RETURN(m_colorSpace->pixelSize() == dstColorSpace->pixelSize()); ChangeProfileCommand *cmd = new ChangeProfileCommand(this, m_colorSpace, dstColorSpace, parentCommand); cmd->forcedRedo(); if (!parentCommand) { delete cmd; } } void convertDataColorSpace(const KoColorSpace *dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags, KUndo2Command *parentCommand) { typedef KisSequentialIteratorBase, DirectDataAccessPolicy> InternalSequentialConstIterator; typedef KisSequentialIteratorBase, DirectDataAccessPolicy> InternalSequentialIterator; if (m_colorSpace == dstColorSpace || *m_colorSpace == *dstColorSpace) { return; } QRect rc = m_dataManager->region().boundingRect(); const int dstPixelSize = dstColorSpace->pixelSize(); QScopedArrayPointer dstDefaultPixel(new quint8[dstPixelSize]); memset(dstDefaultPixel.data(), 0, dstPixelSize); m_colorSpace->convertPixelsTo(m_dataManager->defaultPixel(), dstDefaultPixel.data(), dstColorSpace, 1, renderingIntent, conversionFlags); KisDataManagerSP dstDataManager = new KisDataManager(dstPixelSize, dstDefaultPixel.data()); if (!rc.isEmpty()) { InternalSequentialConstIterator srcIt(DirectDataAccessPolicy(m_dataManager.data(), cacheInvalidator()), rc); InternalSequentialIterator dstIt(DirectDataAccessPolicy(dstDataManager.data(), cacheInvalidator()), rc); int nConseqPixels = srcIt.nConseqPixels(); // since we are accessing data managers directly, the columns are always aligned KIS_SAFE_ASSERT_RECOVER_NOOP(srcIt.nConseqPixels() == dstIt.nConseqPixels()); while(srcIt.nextPixels(nConseqPixels) && dstIt.nextPixels(nConseqPixels)) { nConseqPixels = srcIt.nConseqPixels(); const quint8 *srcData = srcIt.rawDataConst(); quint8 *dstData = dstIt.rawData(); m_colorSpace->convertPixelsTo(srcData, dstData, dstColorSpace, nConseqPixels, renderingIntent, conversionFlags); } } // becomes owned by the parent ChangeColorSpaceCommand *cmd = new ChangeColorSpaceCommand(this, m_dataManager, dstDataManager, m_colorSpace, dstColorSpace, parentCommand); cmd->forcedRedo(); if (!parentCommand) { delete cmd; } } + void reincarnateWithDetachedHistory(bool copyContent, KUndo2Command *parentCommand) { + struct SwitchDataManager : public KUndo2Command + { + SwitchDataManager(KisPaintDeviceData *data, + KisDataManagerSP oldDm, KisDataManagerSP newDm, + KUndo2Command *parent = 0) + : KUndo2Command(parent), + m_data(data), + m_oldDm(oldDm), + m_newDm(newDm) + { + } + + void redo() override { + m_data->m_dataManager = m_newDm; + m_data->cache()->invalidate(); + } + + void undo() override { + m_data->m_dataManager = m_oldDm; + m_data->cache()->invalidate(); + } + + private: + KisPaintDeviceData *m_data; + KisDataManagerSP m_oldDm; + KisDataManagerSP m_newDm; + }; + + new KisCommandUtils::LambdaCommand(parentCommand, + [this, copyContent] () { + KisDataManagerSP newDm = + copyContent ? + new KisDataManager(*this->dataManager()) : + new KisDataManager(this->dataManager()->pixelSize(), this->dataManager()->defaultPixel()); + return new SwitchDataManager(this, this->dataManager(), newDm); + }); + } + void prepareClone(const KisPaintDeviceData *srcData, bool copyContent = false) { m_x = srcData->x(); m_y = srcData->y(); if (copyContent) { m_dataManager = new KisDataManager(*srcData->dataManager()); } else if (m_dataManager->pixelSize() != srcData->dataManager()->pixelSize()) { // NOTE: we don't check default pixel value! it is the task of // the higher level! m_dataManager = new KisDataManager(srcData->dataManager()->pixelSize(), srcData->dataManager()->defaultPixel()); m_cache.setupCache(); } else { m_dataManager->clear(); const quint8 *srcDefPixel = srcData->dataManager()->defaultPixel(); const int cmp = memcmp(srcDefPixel, m_dataManager->defaultPixel(), m_dataManager->pixelSize()); if (cmp != 0) { m_dataManager->setDefaultPixel(srcDefPixel); } } m_levelOfDetail = srcData->levelOfDetail(); m_colorSpace = srcData->colorSpace(); m_cache.invalidate(); } ALWAYS_INLINE KisDataManagerSP dataManager() const { return m_dataManager; } ALWAYS_INLINE KisPaintDeviceCache* cache() { return &m_cache; } ALWAYS_INLINE qint32 x() const { return m_x; } ALWAYS_INLINE void setX(qint32 value) { m_x = value; } ALWAYS_INLINE qint32 y() const { return m_y; } ALWAYS_INLINE void setY(qint32 value) { m_y = value; } ALWAYS_INLINE const KoColorSpace* colorSpace() const { return m_colorSpace; } ALWAYS_INLINE qint32 levelOfDetail() const { return m_levelOfDetail; } ALWAYS_INLINE void setLevelOfDetail(qint32 value) { m_levelOfDetail = value; } ALWAYS_INLINE KisIteratorCompleteListener* cacheInvalidator() { return &m_cacheInvalidator; } private: struct CacheInvalidator : public KisIteratorCompleteListener { CacheInvalidator(KisPaintDeviceData *_q) : q(_q) {} void notifyWritableIteratorCompleted() override { q->cache()->invalidate(); } private: KisPaintDeviceData *q; }; private: KisDataManagerSP m_dataManager; KisPaintDeviceCache m_cache; qint32 m_x; qint32 m_y; const KoColorSpace* m_colorSpace; qint32 m_levelOfDetail; CacheInvalidator m_cacheInvalidator; }; #endif /* __KIS_PAINT_DEVICE_DATA_H */ diff --git a/libs/image/kis_painter.cc b/libs/image/kis_painter.cc index 44f44fdc9a..bc4a92966d 100644 --- a/libs/image/kis_painter.cc +++ b/libs/image/kis_painter.cc @@ -1,3038 +1,3078 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2004 Clarence Dang * Copyright (c) 2004 Adrian Page * Copyright (c) 2004 Cyrille Berger * Copyright (c) 2008-2010 Lukáš Tvrdý * Copyright (c) 2010 José Luis Vergara Toloza * 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_painter.h" #include #include #include #include #include #ifndef Q_OS_WIN #include #endif #include #include #include #include #include #include #include #include "kis_image.h" #include "filter/kis_filter.h" #include "kis_layer.h" #include "kis_paint_device.h" #include "kis_fixed_paint_device.h" #include "kis_transaction.h" #include "kis_vec.h" #include "kis_iterator_ng.h" #include "kis_random_accessor_ng.h" #include "filter/kis_filter_configuration.h" #include "kis_pixel_selection.h" #include #include "kis_paintop_registry.h" #include "kis_perspective_math.h" #include "tiles3/kis_random_accessor.h" #include #include #include "kis_lod_transform.h" #include "kis_algebra_2d.h" #include "krita_utils.h" // Maximum distance from a Bezier control point to the line through the start // and end points for the curve to be considered flat. #define BEZIER_FLATNESS_THRESHOLD 0.5 #include "kis_painter_p.h" KisPainter::KisPainter() : d(new Private(this)) { init(); } KisPainter::KisPainter(KisPaintDeviceSP device) : d(new Private(this, device->colorSpace())) { init(); Q_ASSERT(device); begin(device); } KisPainter::KisPainter(KisPaintDeviceSP device, KisSelectionSP selection) : d(new Private(this, device->colorSpace())) { init(); Q_ASSERT(device); begin(device); d->selection = selection; } void KisPainter::init() { d->selection = 0 ; d->transaction = 0; d->paintOp = 0; d->sourceLayer = 0; d->fillStyle = FillStyleNone; d->strokeStyle = StrokeStyleBrush; d->antiAliasPolygonFill = true; d->progressUpdater = 0; d->maskPainter = 0; d->fillPainter = 0; d->maskImageWidth = 255; d->maskImageHeight = 255; d->mirrorHorizontally = false; d->mirrorVertically = false; d->isOpacityUnit = true; d->paramInfo = KoCompositeOp::ParameterInfo(); d->renderingIntent = KoColorConversionTransformation::internalRenderingIntent(); d->conversionFlags = KoColorConversionTransformation::internalConversionFlags(); + d->patternTransform = QTransform(); } KisPainter::~KisPainter() { // TODO: Maybe, don't be that strict? // deleteTransaction(); end(); delete d->paintOp; delete d->maskPainter; delete d->fillPainter; delete d; } template void copyAreaOptimizedImpl(const QPoint &dstPt, KisPaintDeviceSP src, KisPaintDeviceSP dst, const QRect &srcRect) { const QRect dstRect(dstPt, srcRect.size()); const QRect srcExtent = src->extent(); const QRect dstExtent = dst->extent(); const QRect srcSampleRect = srcExtent & srcRect; const QRect dstSampleRect = dstExtent & dstRect; const bool srcEmpty = srcSampleRect.isEmpty(); const bool dstEmpty = dstSampleRect.isEmpty(); if (!srcEmpty || !dstEmpty) { if (srcEmpty) { dst->clear(dstRect); } else { QRect srcCopyRect = srcRect; QRect dstCopyRect = dstRect; if (!srcExtent.contains(srcRect)) { if (src->defaultPixel() == dst->defaultPixel()) { const QRect dstSampleInSrcCoords = dstSampleRect.translated(srcRect.topLeft() - dstPt); if (dstSampleInSrcCoords.isEmpty() || srcSampleRect.contains(dstSampleInSrcCoords)) { srcCopyRect = srcSampleRect; } else { srcCopyRect = srcSampleRect | dstSampleInSrcCoords; } dstCopyRect = QRect(dstPt + srcCopyRect.topLeft() - srcRect.topLeft(), srcCopyRect.size()); } } KisPainter gc(dst); gc.setCompositeOp(dst->colorSpace()->compositeOp(COMPOSITE_COPY)); if (useOldData) { gc.bitBltOldData(dstCopyRect.topLeft(), src, srcCopyRect); } else { gc.bitBlt(dstCopyRect.topLeft(), src, srcCopyRect); } } } } void KisPainter::copyAreaOptimized(const QPoint &dstPt, KisPaintDeviceSP src, KisPaintDeviceSP dst, const QRect &srcRect) { copyAreaOptimizedImpl(dstPt, src, dst, srcRect); } void KisPainter::copyAreaOptimizedOldData(const QPoint &dstPt, KisPaintDeviceSP src, KisPaintDeviceSP dst, const QRect &srcRect) { copyAreaOptimizedImpl(dstPt, src, dst, srcRect); } void KisPainter::copyAreaOptimized(const QPoint &dstPt, KisPaintDeviceSP src, KisPaintDeviceSP dst, const QRect &originalSrcRect, KisSelectionSP selection) { if (!selection) { copyAreaOptimized(dstPt, src, dst, originalSrcRect); return; } const QRect selectionRect = selection->selectedRect(); const QRect srcRect = originalSrcRect & selectionRect; const QPoint dstOffset = srcRect.topLeft() - originalSrcRect.topLeft(); const QRect dstRect = QRect(dstPt + dstOffset, srcRect.size()); const bool srcEmpty = (src->extent() & srcRect).isEmpty(); const bool dstEmpty = (dst->extent() & dstRect).isEmpty(); if (!srcEmpty || !dstEmpty) { //if (srcEmpty) { // doesn't support dstRect // dst->clearSelection(selection); // } else */ { KisPainter gc(dst); gc.setSelection(selection); gc.setCompositeOp(dst->colorSpace()->compositeOp(COMPOSITE_COPY)); gc.bitBlt(dstRect.topLeft(), src, srcRect); } } } KisPaintDeviceSP KisPainter::convertToAlphaAsAlpha(KisPaintDeviceSP src) { const KoColorSpace *srcCS = src->colorSpace(); const QRect processRect = src->extent(); KisPaintDeviceSP dst(new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8())); if (processRect.isEmpty()) return dst; KisSequentialConstIterator srcIt(src, processRect); KisSequentialIterator dstIt(dst, processRect); while (srcIt.nextPixel() && dstIt.nextPixel()) { const quint8 *srcPtr = srcIt.rawDataConst(); quint8 *alpha8Ptr = dstIt.rawData(); const quint8 white = srcCS->intensity8(srcPtr); const quint8 alpha = srcCS->opacityU8(srcPtr); *alpha8Ptr = KoColorSpaceMaths::multiply(alpha, KoColorSpaceMathsTraits::unitValue - white); } return dst; } KisPaintDeviceSP KisPainter::convertToAlphaAsGray(KisPaintDeviceSP src) { const KoColorSpace *srcCS = src->colorSpace(); const QRect processRect = src->extent(); KisPaintDeviceSP dst(new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8())); if (processRect.isEmpty()) return dst; KisSequentialConstIterator srcIt(src, processRect); KisSequentialIterator dstIt(dst, processRect); while (srcIt.nextPixel() && dstIt.nextPixel()) { const quint8 *srcPtr = srcIt.rawDataConst(); quint8 *alpha8Ptr = dstIt.rawData(); *alpha8Ptr = srcCS->intensity8(srcPtr); } return dst; } KisPaintDeviceSP KisPainter::convertToAlphaAsPureAlpha(KisPaintDeviceSP src) { const KoColorSpace *srcCS = src->colorSpace(); const QRect processRect = src->extent(); KisPaintDeviceSP dst(new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8())); if (processRect.isEmpty()) return dst; KisSequentialConstIterator srcIt(src, processRect); KisSequentialIterator dstIt(dst, processRect); while (srcIt.nextPixel() && dstIt.nextPixel()) { const quint8 *srcPtr = srcIt.rawDataConst(); quint8 *alpha8Ptr = dstIt.rawData(); *alpha8Ptr = srcCS->opacityU8(srcPtr); } return dst; } bool KisPainter::checkDeviceHasTransparency(KisPaintDeviceSP dev) { const QRect deviceBounds = dev->exactBounds(); const QRect imageBounds = dev->defaultBounds()->bounds(); if (deviceBounds.isEmpty() || (deviceBounds & imageBounds) != imageBounds) { return true; } const KoColorSpace *cs = dev->colorSpace(); KisSequentialConstIterator it(dev, deviceBounds); while(it.nextPixel()) { if (cs->opacityU8(it.rawDataConst()) != OPACITY_OPAQUE_U8) { return true; } } return false; } void KisPainter::begin(KisPaintDeviceSP device) { begin(device, d->selection); } void KisPainter::begin(KisPaintDeviceSP device, KisSelectionSP selection) { if (!device) return; d->selection = selection; Q_ASSERT(device->colorSpace()); end(); d->device = device; d->colorSpace = device->colorSpace(); d->compositeOp = d->colorSpace->compositeOp(COMPOSITE_OVER); d->pixelSize = device->pixelSize(); } void KisPainter::end() { Q_ASSERT_X(!d->transaction, "KisPainter::end()", "end() was called for the painter having a transaction. " "Please use end/deleteTransaction() instead"); } void KisPainter::beginTransaction(const KUndo2MagicString& transactionName,int timedID) { Q_ASSERT_X(!d->transaction, "KisPainter::beginTransaction()", "You asked for a new transaction while still having " "another one. Please finish the first one with " "end/deleteTransaction() first"); d->transaction = new KisTransaction(transactionName, d->device); Q_CHECK_PTR(d->transaction); d->transaction->undoCommand()->setTimedID(timedID); } void KisPainter::revertTransaction() { Q_ASSERT_X(d->transaction, "KisPainter::revertTransaction()", "No transaction is in progress"); d->transaction->revert(); delete d->transaction; d->transaction = 0; } void KisPainter::endTransaction(KisUndoAdapter *undoAdapter) { Q_ASSERT_X(d->transaction, "KisPainter::endTransaction()", "No transaction is in progress"); d->transaction->commit(undoAdapter); delete d->transaction; d->transaction = 0; } void KisPainter::endTransaction(KisPostExecutionUndoAdapter *undoAdapter) { Q_ASSERT_X(d->transaction, "KisPainter::endTransaction()", "No transaction is in progress"); d->transaction->commit(undoAdapter); delete d->transaction; d->transaction = 0; } KUndo2Command* KisPainter::endAndTakeTransaction() { Q_ASSERT_X(d->transaction, "KisPainter::endTransaction()", "No transaction is in progress"); KUndo2Command *transactionData = d->transaction->endAndTake(); delete d->transaction; d->transaction = 0; return transactionData; } void KisPainter::deleteTransaction() { if (!d->transaction) return; delete d->transaction; d->transaction = 0; } void KisPainter::putTransaction(KisTransaction* transaction) { Q_ASSERT_X(!d->transaction, "KisPainter::putTransaction()", "You asked for a new transaction while still having " "another one. Please finish the first one with " "end/deleteTransaction() first"); d->transaction = transaction; } KisTransaction* KisPainter::takeTransaction() { Q_ASSERT_X(d->transaction, "KisPainter::takeTransaction()", "No transaction is in progress"); KisTransaction *temp = d->transaction; d->transaction = 0; return temp; } QVector KisPainter::takeDirtyRegion() { QVector vrect = d->dirtyRects; d->dirtyRects.clear(); return vrect; } void KisPainter::addDirtyRect(const QRect & rc) { QRect r = rc.normalized(); if (r.isValid()) { d->dirtyRects.append(rc); } } void KisPainter::addDirtyRects(const QVector &rects) { d->dirtyRects.reserve(d->dirtyRects.size() + rects.size()); Q_FOREACH (const QRect &rc, rects) { const QRect r = rc.normalized(); if (r.isValid()) { d->dirtyRects.append(rc); } } } inline bool KisPainter::Private::tryReduceSourceRect(const KisPaintDevice *srcDev, QRect *srcRect, qint32 *srcX, qint32 *srcY, qint32 *srcWidth, qint32 *srcHeight, qint32 *dstX, qint32 *dstY) { + bool needsReadjustParams = false; + /** * In case of COMPOSITE_COPY and Wrap Around Mode even the pixels * outside the device extent matter, because they will be either * directly copied (former case) or cloned from another area of * the image. */ if (compositeOp->id() != COMPOSITE_COPY && compositeOp->id() != COMPOSITE_DESTINATION_IN && compositeOp->id() != COMPOSITE_DESTINATION_ATOP && !srcDev->defaultBounds()->wrapAroundMode()) { /** * If srcDev->extent() (the area of the tiles containing * srcDev) is smaller than srcRect, then shrink srcRect to * that size. This is done as a speed optimization, useful for * stack recomposition in KisImage. srcRect won't grow if * srcDev->extent() is larger. */ *srcRect &= srcDev->extent(); if (srcRect->isEmpty()) return true; + needsReadjustParams = true; + } + + if (selection) { + /** + * We should also crop the blitted area by the selected region, + * because we cannot paint outside the selection. + */ + *srcRect &= selection->selectedRect(); + + if (srcRect->isEmpty()) return true; + needsReadjustParams = true; + } + + if (!paramInfo.channelFlags.isEmpty()) { + const QBitArray onlyColor = colorSpace->channelFlags(true, false); + KIS_SAFE_ASSERT_RECOVER_NOOP(onlyColor.size() == paramInfo.channelFlags.size()); + + // check if we have alpha channel locked + if ((paramInfo.channelFlags & onlyColor) == paramInfo.channelFlags) { + *srcRect &= device->extent(); + if (srcRect->isEmpty()) return true; + needsReadjustParams = true; + } + } + + if (needsReadjustParams) { // Readjust the function paramenters to the new dimensions. *dstX += srcRect->x() - *srcX; // This will only add, not subtract *dstY += srcRect->y() - *srcY; // Idem srcRect->getRect(srcX, srcY, srcWidth, srcHeight); } return false; } void KisPainter::bitBltWithFixedSelection(qint32 dstX, qint32 dstY, const KisPaintDeviceSP srcDev, const KisFixedPaintDeviceSP selection, qint32 selX, qint32 selY, qint32 srcX, qint32 srcY, qint32 srcWidth, qint32 srcHeight) { // TODO: get selX and selY working as intended /* This check for nonsense ought to be a Q_ASSERT. However, when paintops are just initializing they perform some dummy passes with those parameters, and it must not crash */ if (srcWidth == 0 || srcHeight == 0) return; if (srcDev.isNull()) return; if (d->device.isNull()) return; // Check that selection has an alpha colorspace, crash if false Q_ASSERT(selection->colorSpace() == KoColorSpaceRegistry::instance()->alpha8()); QRect srcRect = QRect(srcX, srcY, srcWidth, srcHeight); QRect selRect = QRect(selX, selY, srcWidth, srcHeight); /* Trying to read outside a KisFixedPaintDevice is inherently wrong and shouldn't be done, so crash if someone attempts to do this. Don't resize YET as it would obfuscate the mistake. */ Q_ASSERT(selection->bounds().contains(selRect)); Q_UNUSED(selRect); // only used by the above Q_ASSERT /** * An optimization, which crops the source rect by the bounds of * the source device when it is possible */ if (d->tryReduceSourceRect(srcDev, &srcRect, &srcX, &srcY, &srcWidth, &srcHeight, &dstX, &dstY)) return; /* Create an intermediate byte array to hold information before it is written to the current paint device (d->device) */ quint8* dstBytes = 0; try { dstBytes = new quint8[srcWidth * srcHeight * d->device->pixelSize()]; } catch (const std::bad_alloc&) { warnKrita << "KisPainter::bitBltWithFixedSelection std::bad_alloc for " << srcWidth << " * " << srcHeight << " * " << d->device->pixelSize() << "dst bytes"; return; } d->device->readBytes(dstBytes, dstX, dstY, srcWidth, srcHeight); // Copy the relevant bytes of raw data from srcDev quint8* srcBytes = 0; try { srcBytes = new quint8[srcWidth * srcHeight * srcDev->pixelSize()]; } catch (const std::bad_alloc&) { warnKrita << "KisPainter::bitBltWithFixedSelection std::bad_alloc for " << srcWidth << " * " << srcHeight << " * " << d->device->pixelSize() << "src bytes"; return; } srcDev->readBytes(srcBytes, srcX, srcY, srcWidth, srcHeight); QRect selBounds = selection->bounds(); const quint8 *selRowStart = selection->data() + (selBounds.width() * (selY - selBounds.top()) + (selX - selBounds.left())) * selection->pixelSize(); /* * This checks whether there is nothing selected. */ if (!d->selection) { /* As there's nothing selected, blit to dstBytes (intermediary bit array), ignoring d->selection (the user selection)*/ d->paramInfo.dstRowStart = dstBytes; d->paramInfo.dstRowStride = srcWidth * d->device->pixelSize(); d->paramInfo.srcRowStart = srcBytes; d->paramInfo.srcRowStride = srcWidth * srcDev->pixelSize(); d->paramInfo.maskRowStart = selRowStart; d->paramInfo.maskRowStride = selBounds.width() * selection->pixelSize(); d->paramInfo.rows = srcHeight; d->paramInfo.cols = srcWidth; d->colorSpace->bitBlt(srcDev->colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); } else { /* Read the user selection (d->selection) bytes into an array, ready to merge in the next block*/ quint32 totalBytes = srcWidth * srcHeight * selection->pixelSize(); quint8* mergedSelectionBytes = 0; try { mergedSelectionBytes = new quint8[ totalBytes ]; } catch (const std::bad_alloc&) { warnKrita << "KisPainter::bitBltWithFixedSelection std::bad_alloc for " << srcWidth << " * " << srcHeight << " * " << d->device->pixelSize() << "total bytes"; return; } d->selection->projection()->readBytes(mergedSelectionBytes, dstX, dstY, srcWidth, srcHeight); // Merge selections here by multiplying them - compositeOP(COMPOSITE_MULT) d->paramInfo.dstRowStart = mergedSelectionBytes; d->paramInfo.dstRowStride = srcWidth * selection->pixelSize(); d->paramInfo.srcRowStart = selRowStart; d->paramInfo.srcRowStride = selBounds.width() * selection->pixelSize(); d->paramInfo.maskRowStart = 0; d->paramInfo.maskRowStride = 0; d->paramInfo.rows = srcHeight; d->paramInfo.cols = srcWidth; KoColorSpaceRegistry::instance()->alpha8()->compositeOp(COMPOSITE_MULT)->composite(d->paramInfo); // Blit to dstBytes (intermediary bit array) d->paramInfo.dstRowStart = dstBytes; d->paramInfo.dstRowStride = srcWidth * d->device->pixelSize(); d->paramInfo.srcRowStart = srcBytes; d->paramInfo.srcRowStride = srcWidth * srcDev->pixelSize(); d->paramInfo.maskRowStart = mergedSelectionBytes; d->paramInfo.maskRowStride = srcWidth * selection->pixelSize(); d->colorSpace->bitBlt(srcDev->colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); delete[] mergedSelectionBytes; } d->device->writeBytes(dstBytes, dstX, dstY, srcWidth, srcHeight); delete[] dstBytes; delete[] srcBytes; addDirtyRect(QRect(dstX, dstY, srcWidth, srcHeight)); } void KisPainter::bitBltWithFixedSelection(qint32 dstX, qint32 dstY, const KisPaintDeviceSP srcDev, const KisFixedPaintDeviceSP selection, qint32 srcWidth, qint32 srcHeight) { bitBltWithFixedSelection(dstX, dstY, srcDev, selection, 0, 0, 0, 0, srcWidth, srcHeight); } template void KisPainter::bitBltImpl(qint32 dstX, qint32 dstY, const KisPaintDeviceSP srcDev, qint32 srcX, qint32 srcY, qint32 srcWidth, qint32 srcHeight) { /* This check for nonsense ought to be a Q_ASSERT. However, when paintops are just initializing they perform some dummy passes with those parameters, and it must not crash */ if (srcWidth == 0 || srcHeight == 0) return; if (srcDev.isNull()) return; if (d->device.isNull()) return; QRect srcRect = QRect(srcX, srcY, srcWidth, srcHeight); if (d->compositeOp->id() == COMPOSITE_COPY) { if(!d->selection && d->isOpacityUnit && srcX == dstX && srcY == dstY && d->device->fastBitBltPossible(srcDev)) { if(useOldSrcData) { d->device->fastBitBltOldData(srcDev, srcRect); } else { d->device->fastBitBlt(srcDev, srcRect); } addDirtyRect(srcRect); return; } } else { /** * An optimization, which crops the source rect by the bounds of * the source device when it is possible */ if (d->tryReduceSourceRect(srcDev, &srcRect, &srcX, &srcY, &srcWidth, &srcHeight, &dstX, &dstY)) return; } qint32 dstY_ = dstY; qint32 srcY_ = srcY; qint32 rowsRemaining = srcHeight; // Read below KisRandomConstAccessorSP srcIt = srcDev->createRandomConstAccessorNG(); KisRandomAccessorSP dstIt = d->device->createRandomAccessorNG(); /* Here be a huge block of verbose code that does roughly the same than the other bit blit operations. This one is longer than the rest in an effort to optimize speed and memory use */ if (d->selection) { KisPaintDeviceSP selectionProjection(d->selection->projection()); KisRandomConstAccessorSP maskIt = selectionProjection->createRandomConstAccessorNG(); while (rowsRemaining > 0) { qint32 dstX_ = dstX; qint32 srcX_ = srcX; qint32 columnsRemaining = srcWidth; qint32 numContiguousDstRows = dstIt->numContiguousRows(dstY_); qint32 numContiguousSrcRows = srcIt->numContiguousRows(srcY_); qint32 numContiguousSelRows = maskIt->numContiguousRows(dstY_); qint32 rows = qMin(numContiguousDstRows, numContiguousSrcRows); rows = qMin(rows, numContiguousSelRows); rows = qMin(rows, rowsRemaining); while (columnsRemaining > 0) { qint32 numContiguousDstColumns = dstIt->numContiguousColumns(dstX_); qint32 numContiguousSrcColumns = srcIt->numContiguousColumns(srcX_); qint32 numContiguousSelColumns = maskIt->numContiguousColumns(dstX_); qint32 columns = qMin(numContiguousDstColumns, numContiguousSrcColumns); columns = qMin(columns, numContiguousSelColumns); columns = qMin(columns, columnsRemaining); qint32 srcRowStride = srcIt->rowStride(srcX_, srcY_); srcIt->moveTo(srcX_, srcY_); qint32 dstRowStride = dstIt->rowStride(dstX_, dstY_); dstIt->moveTo(dstX_, dstY_); qint32 maskRowStride = maskIt->rowStride(dstX_, dstY_); maskIt->moveTo(dstX_, dstY_); d->paramInfo.dstRowStart = dstIt->rawData(); d->paramInfo.dstRowStride = dstRowStride; // if we don't use the oldRawData, we need to access the rawData of the source device. d->paramInfo.srcRowStart = useOldSrcData ? srcIt->oldRawData() : static_cast(srcIt.data())->rawData(); d->paramInfo.srcRowStride = srcRowStride; d->paramInfo.maskRowStart = static_cast(maskIt.data())->rawData(); d->paramInfo.maskRowStride = maskRowStride; d->paramInfo.rows = rows; d->paramInfo.cols = columns; d->colorSpace->bitBlt(srcDev->colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); srcX_ += columns; dstX_ += columns; columnsRemaining -= columns; } srcY_ += rows; dstY_ += rows; rowsRemaining -= rows; } } else { while (rowsRemaining > 0) { qint32 dstX_ = dstX; qint32 srcX_ = srcX; qint32 columnsRemaining = srcWidth; qint32 numContiguousDstRows = dstIt->numContiguousRows(dstY_); qint32 numContiguousSrcRows = srcIt->numContiguousRows(srcY_); qint32 rows = qMin(numContiguousDstRows, numContiguousSrcRows); rows = qMin(rows, rowsRemaining); while (columnsRemaining > 0) { qint32 numContiguousDstColumns = dstIt->numContiguousColumns(dstX_); qint32 numContiguousSrcColumns = srcIt->numContiguousColumns(srcX_); qint32 columns = qMin(numContiguousDstColumns, numContiguousSrcColumns); columns = qMin(columns, columnsRemaining); qint32 srcRowStride = srcIt->rowStride(srcX_, srcY_); srcIt->moveTo(srcX_, srcY_); qint32 dstRowStride = dstIt->rowStride(dstX_, dstY_); dstIt->moveTo(dstX_, dstY_); d->paramInfo.dstRowStart = dstIt->rawData(); d->paramInfo.dstRowStride = dstRowStride; // if we don't use the oldRawData, we need to access the rawData of the source device. d->paramInfo.srcRowStart = useOldSrcData ? srcIt->oldRawData() : static_cast(srcIt.data())->rawData(); d->paramInfo.srcRowStride = srcRowStride; d->paramInfo.maskRowStart = 0; d->paramInfo.maskRowStride = 0; d->paramInfo.rows = rows; d->paramInfo.cols = columns; d->colorSpace->bitBlt(srcDev->colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); srcX_ += columns; dstX_ += columns; columnsRemaining -= columns; } srcY_ += rows; dstY_ += rows; rowsRemaining -= rows; } } addDirtyRect(QRect(dstX, dstY, srcWidth, srcHeight)); } void KisPainter::bitBlt(qint32 dstX, qint32 dstY, const KisPaintDeviceSP srcDev, qint32 srcX, qint32 srcY, qint32 srcWidth, qint32 srcHeight) { bitBltImpl(dstX, dstY, srcDev, srcX, srcY, srcWidth, srcHeight); } void KisPainter::bitBlt(const QPoint & pos, const KisPaintDeviceSP srcDev, const QRect & srcRect) { bitBlt(pos.x(), pos.y(), srcDev, srcRect.x(), srcRect.y(), srcRect.width(), srcRect.height()); } void KisPainter::bitBltOldData(qint32 dstX, qint32 dstY, const KisPaintDeviceSP srcDev, qint32 srcX, qint32 srcY, qint32 srcWidth, qint32 srcHeight) { bitBltImpl(dstX, dstY, srcDev, srcX, srcY, srcWidth, srcHeight); } void KisPainter::bitBltOldData(const QPoint & pos, const KisPaintDeviceSP srcDev, const QRect & srcRect) { bitBltOldData(pos.x(), pos.y(), srcDev, srcRect.x(), srcRect.y(), srcRect.width(), srcRect.height()); } void KisPainter::fill(qint32 x, qint32 y, qint32 width, qint32 height, const KoColor& color) { /* This check for nonsense ought to be a Q_ASSERT. However, when paintops are just * initializing they perform some dummy passes with those parameters, and it must not crash */ if(width == 0 || height == 0 || d->device.isNull()) return; KoColor srcColor(color, d->device->compositionSourceColorSpace()); qint32 dstY = y; qint32 rowsRemaining = height; KisRandomAccessorSP dstIt = d->device->createRandomAccessorNG(); if(d->selection) { KisPaintDeviceSP selectionProjection(d->selection->projection()); KisRandomConstAccessorSP maskIt = selectionProjection->createRandomConstAccessorNG(); while(rowsRemaining > 0) { qint32 dstX = x; qint32 columnsRemaining = width; qint32 numContiguousDstRows = dstIt->numContiguousRows(dstY); qint32 numContiguousSelRows = maskIt->numContiguousRows(dstY); qint32 rows = qMin(numContiguousDstRows, numContiguousSelRows); rows = qMin(rows, rowsRemaining); while (columnsRemaining > 0) { qint32 numContiguousDstColumns = dstIt->numContiguousColumns(dstX); qint32 numContiguousSelColumns = maskIt->numContiguousColumns(dstX); qint32 columns = qMin(numContiguousDstColumns, numContiguousSelColumns); columns = qMin(columns, columnsRemaining); qint32 dstRowStride = dstIt->rowStride(dstX, dstY); dstIt->moveTo(dstX, dstY); qint32 maskRowStride = maskIt->rowStride(dstX, dstY); maskIt->moveTo(dstX, dstY); d->paramInfo.dstRowStart = dstIt->rawData(); d->paramInfo.dstRowStride = dstRowStride; d->paramInfo.srcRowStart = srcColor.data(); d->paramInfo.srcRowStride = 0; // srcRowStride is set to zero to use the compositeOp with only a single color pixel d->paramInfo.maskRowStart = maskIt->oldRawData(); d->paramInfo.maskRowStride = maskRowStride; d->paramInfo.rows = rows; d->paramInfo.cols = columns; d->colorSpace->bitBlt(srcColor.colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); dstX += columns; columnsRemaining -= columns; } dstY += rows; rowsRemaining -= rows; } } else { while(rowsRemaining > 0) { qint32 dstX = x; qint32 columnsRemaining = width; qint32 numContiguousDstRows = dstIt->numContiguousRows(dstY); qint32 rows = qMin(numContiguousDstRows, rowsRemaining); while(columnsRemaining > 0) { qint32 numContiguousDstColumns = dstIt->numContiguousColumns(dstX); qint32 columns = qMin(numContiguousDstColumns, columnsRemaining); qint32 dstRowStride = dstIt->rowStride(dstX, dstY); dstIt->moveTo(dstX, dstY); d->paramInfo.dstRowStart = dstIt->rawData(); d->paramInfo.dstRowStride = dstRowStride; d->paramInfo.srcRowStart = srcColor.data(); d->paramInfo.srcRowStride = 0; // srcRowStride is set to zero to use the compositeOp with only a single color pixel d->paramInfo.maskRowStart = 0; d->paramInfo.maskRowStride = 0; d->paramInfo.rows = rows; d->paramInfo.cols = columns; d->colorSpace->bitBlt(srcColor.colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); dstX += columns; columnsRemaining -= columns; } dstY += rows; rowsRemaining -= rows; } } addDirtyRect(QRect(x, y, width, height)); } void KisPainter::bltFixed(qint32 dstX, qint32 dstY, const KisFixedPaintDeviceSP srcDev, qint32 srcX, qint32 srcY, qint32 srcWidth, qint32 srcHeight) { /* This check for nonsense ought to be a Q_ASSERT. However, when paintops are just initializing they perform some dummy passes with those parameters, and it must not crash */ if (srcWidth == 0 || srcHeight == 0) return; if (srcDev.isNull()) return; if (d->device.isNull()) return; QRect srcRect = QRect(srcX, srcY, srcWidth, srcHeight); QRect srcBounds = srcDev->bounds(); /* Trying to read outside a KisFixedPaintDevice is inherently wrong and shouldn't be done, so crash if someone attempts to do this. Don't resize as it would obfuscate the mistake. */ KIS_SAFE_ASSERT_RECOVER_RETURN(srcBounds.contains(srcRect)); Q_UNUSED(srcRect); // only used in above assertion /* Create an intermediate byte array to hold information before it is written to the current paint device (aka: d->device) */ quint8* dstBytes = 0; try { dstBytes = new quint8[srcWidth * srcHeight * d->device->pixelSize()]; } catch (const std::bad_alloc&) { warnKrita << "KisPainter::bltFixed std::bad_alloc for " << srcWidth << " * " << srcHeight << " * " << d->device->pixelSize() << "total bytes"; return; } d->device->readBytes(dstBytes, dstX, dstY, srcWidth, srcHeight); const quint8 *srcRowStart = srcDev->data() + (srcBounds.width() * (srcY - srcBounds.top()) + (srcX - srcBounds.left())) * srcDev->pixelSize(); d->paramInfo.dstRowStart = dstBytes; d->paramInfo.dstRowStride = srcWidth * d->device->pixelSize(); d->paramInfo.srcRowStart = srcRowStart; d->paramInfo.srcRowStride = srcBounds.width() * srcDev->pixelSize(); d->paramInfo.maskRowStart = 0; d->paramInfo.maskRowStride = 0; d->paramInfo.rows = srcHeight; d->paramInfo.cols = srcWidth; if (d->selection) { /* d->selection is a KisPaintDevice, so first a readBytes is performed to get the area of interest... */ KisPaintDeviceSP selectionProjection(d->selection->projection()); quint8* selBytes = 0; try { selBytes = new quint8[srcWidth * srcHeight * selectionProjection->pixelSize()]; } catch (const std::bad_alloc&) { delete[] dstBytes; return; } selectionProjection->readBytes(selBytes, dstX, dstY, srcWidth, srcHeight); d->paramInfo.maskRowStart = selBytes; d->paramInfo.maskRowStride = srcWidth * selectionProjection->pixelSize(); } // ...and then blit. d->colorSpace->bitBlt(srcDev->colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); d->device->writeBytes(dstBytes, dstX, dstY, srcWidth, srcHeight); delete[] d->paramInfo.maskRowStart; delete[] dstBytes; addDirtyRect(QRect(dstX, dstY, srcWidth, srcHeight)); } void KisPainter::bltFixed(const QPoint & pos, const KisFixedPaintDeviceSP srcDev, const QRect & srcRect) { bltFixed(pos.x(), pos.y(), srcDev, srcRect.x(), srcRect.y(), srcRect.width(), srcRect.height()); } void KisPainter::bltFixedWithFixedSelection(qint32 dstX, qint32 dstY, const KisFixedPaintDeviceSP srcDev, const KisFixedPaintDeviceSP selection, qint32 selX, qint32 selY, qint32 srcX, qint32 srcY, quint32 srcWidth, quint32 srcHeight) { // TODO: get selX and selY working as intended /* This check for nonsense ought to be a Q_ASSERT. However, when paintops are just initializing they perform some dummy passes with those parameters, and it must not crash */ if (srcWidth == 0 || srcHeight == 0) return; if (srcDev.isNull()) return; if (d->device.isNull()) return; // Check that selection has an alpha colorspace, crash if false Q_ASSERT(selection->colorSpace() == KoColorSpaceRegistry::instance()->alpha8()); QRect srcRect = QRect(srcX, srcY, srcWidth, srcHeight); QRect selRect = QRect(selX, selY, srcWidth, srcHeight); QRect srcBounds = srcDev->bounds(); QRect selBounds = selection->bounds(); /* Trying to read outside a KisFixedPaintDevice is inherently wrong and shouldn't be done, so crash if someone attempts to do this. Don't resize as it would obfuscate the mistake. */ Q_ASSERT(srcBounds.contains(srcRect)); Q_UNUSED(srcRect); // only used in above assertion Q_ASSERT(selBounds.contains(selRect)); Q_UNUSED(selRect); // only used in above assertion /* Create an intermediate byte array to hold information before it is written to the current paint device (aka: d->device) */ quint8* dstBytes = 0; try { dstBytes = new quint8[srcWidth * srcHeight * d->device->pixelSize()]; } catch (const std::bad_alloc&) { warnKrita << "KisPainter::bltFixedWithFixedSelection std::bad_alloc for " << srcWidth << " * " << srcHeight << " * " << d->device->pixelSize() << "total bytes"; return; } d->device->readBytes(dstBytes, dstX, dstY, srcWidth, srcHeight); const quint8 *srcRowStart = srcDev->data() + (srcBounds.width() * (srcY - srcBounds.top()) + (srcX - srcBounds.left())) * srcDev->pixelSize(); const quint8 *selRowStart = selection->data() + (selBounds.width() * (selY - selBounds.top()) + (selX - selBounds.left())) * selection->pixelSize(); if (!d->selection) { /* As there's nothing selected, blit to dstBytes (intermediary bit array), ignoring d->selection (the user selection)*/ d->paramInfo.dstRowStart = dstBytes; d->paramInfo.dstRowStride = srcWidth * d->device->pixelSize(); d->paramInfo.srcRowStart = srcRowStart; d->paramInfo.srcRowStride = srcBounds.width() * srcDev->pixelSize(); d->paramInfo.maskRowStart = selRowStart; d->paramInfo.maskRowStride = selBounds.width() * selection->pixelSize(); d->paramInfo.rows = srcHeight; d->paramInfo.cols = srcWidth; d->colorSpace->bitBlt(srcDev->colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); } else { /* Read the user selection (d->selection) bytes into an array, ready to merge in the next block*/ quint32 totalBytes = srcWidth * srcHeight * selection->pixelSize(); quint8 * mergedSelectionBytes = 0; try { mergedSelectionBytes = new quint8[ totalBytes ]; } catch (const std::bad_alloc&) { warnKrita << "KisPainter::bltFixedWithFixedSelection std::bad_alloc for " << totalBytes << "total bytes"; delete[] dstBytes; return; } d->selection->projection()->readBytes(mergedSelectionBytes, dstX, dstY, srcWidth, srcHeight); // Merge selections here by multiplying them - compositeOp(COMPOSITE_MULT) d->paramInfo.dstRowStart = mergedSelectionBytes; d->paramInfo.dstRowStride = srcWidth * selection->pixelSize(); d->paramInfo.srcRowStart = selRowStart; d->paramInfo.srcRowStride = selBounds.width() * selection->pixelSize(); d->paramInfo.maskRowStart = 0; d->paramInfo.maskRowStride = 0; d->paramInfo.rows = srcHeight; d->paramInfo.cols = srcWidth; KoColorSpaceRegistry::instance()->alpha8()->compositeOp(COMPOSITE_MULT)->composite(d->paramInfo); // Blit to dstBytes (intermediary bit array) d->paramInfo.dstRowStart = dstBytes; d->paramInfo.dstRowStride = srcWidth * d->device->pixelSize(); d->paramInfo.srcRowStart = srcRowStart; d->paramInfo.srcRowStride = srcBounds.width() * srcDev->pixelSize(); d->paramInfo.maskRowStart = mergedSelectionBytes; d->paramInfo.maskRowStride = srcWidth * selection->pixelSize(); d->colorSpace->bitBlt(srcDev->colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); delete[] mergedSelectionBytes; } d->device->writeBytes(dstBytes, dstX, dstY, srcWidth, srcHeight); delete[] dstBytes; addDirtyRect(QRect(dstX, dstY, srcWidth, srcHeight)); } void KisPainter::bltFixedWithFixedSelection(qint32 dstX, qint32 dstY, const KisFixedPaintDeviceSP srcDev, const KisFixedPaintDeviceSP selection, quint32 srcWidth, quint32 srcHeight) { bltFixedWithFixedSelection(dstX, dstY, srcDev, selection, 0, 0, 0, 0, srcWidth, srcHeight); } void KisPainter::paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) { if (d->device && d->paintOp && d->paintOp->canPaint()) { d->paintOp->paintLine(pi1, pi2, currentDistance); } } void KisPainter::paintPolyline(const vQPointF &points, int index, int numPoints) { if (d->fillStyle != FillStyleNone) { fillPolygon(points, d->fillStyle); } if (d->strokeStyle == StrokeStyleNone) return; if (index >= points.count()) return; if (numPoints < 0) numPoints = points.count(); if (index + numPoints > points.count()) numPoints = points.count() - index; if (numPoints > 1) { KisDistanceInformation saveDist(points[0], KisAlgebra2D::directionBetweenPoints(points[0], points[1], 0.0)); for (int i = index; i < index + numPoints - 1; i++) { paintLine(points [i], points [i + 1], &saveDist); } } } static void getBezierCurvePoints(const KisVector2D &pos1, const KisVector2D &control1, const KisVector2D &control2, const KisVector2D &pos2, vQPointF& points) { LineEquation line = LineEquation::Through(pos1, pos2); qreal d1 = line.absDistance(control1); qreal d2 = line.absDistance(control2); if (d1 < BEZIER_FLATNESS_THRESHOLD && d2 < BEZIER_FLATNESS_THRESHOLD) { points.push_back(toQPointF(pos1)); } else { // Midpoint subdivision. See Foley & Van Dam Computer Graphics P.508 KisVector2D l2 = (pos1 + control1) / 2; KisVector2D h = (control1 + control2) / 2; KisVector2D l3 = (l2 + h) / 2; KisVector2D r3 = (control2 + pos2) / 2; KisVector2D r2 = (h + r3) / 2; KisVector2D l4 = (l3 + r2) / 2; getBezierCurvePoints(pos1, l2, l3, l4, points); getBezierCurvePoints(l4, r2, r3, pos2, points); } } void KisPainter::getBezierCurvePoints(const QPointF &pos1, const QPointF &control1, const QPointF &control2, const QPointF &pos2, vQPointF& points) const { ::getBezierCurvePoints(toKisVector2D(pos1), toKisVector2D(control1), toKisVector2D(control2), toKisVector2D(pos2), points); } void KisPainter::paintBezierCurve(const KisPaintInformation &pi1, const QPointF &control1, const QPointF &control2, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) { if (d->paintOp && d->paintOp->canPaint()) { d->paintOp->paintBezierCurve(pi1, control1, control2, pi2, currentDistance); } } void KisPainter::paintRect(const QRectF &rect) { QRectF normalizedRect = rect.normalized(); vQPointF points; points.push_back(normalizedRect.topLeft()); points.push_back(normalizedRect.bottomLeft()); points.push_back(normalizedRect.bottomRight()); points.push_back(normalizedRect.topRight()); paintPolygon(points); } void KisPainter::paintRect(const qreal x, const qreal y, const qreal w, const qreal h) { paintRect(QRectF(x, y, w, h)); } void KisPainter::paintEllipse(const QRectF &rect) { QRectF r = rect.normalized(); // normalize before checking as negative width and height are empty too if (r.isEmpty()) return; // See http://www.whizkidtech.redprince.net/bezier/circle/ for explanation. // kappa = (4/3*(sqrt(2)-1)) const qreal kappa = 0.5522847498; const qreal lx = (r.width() / 2) * kappa; const qreal ly = (r.height() / 2) * kappa; QPointF center = r.center(); QPointF p0(r.left(), center.y()); QPointF p1(r.left(), center.y() - ly); QPointF p2(center.x() - lx, r.top()); QPointF p3(center.x(), r.top()); vQPointF points; getBezierCurvePoints(p0, p1, p2, p3, points); QPointF p4(center.x() + lx, r.top()); QPointF p5(r.right(), center.y() - ly); QPointF p6(r.right(), center.y()); getBezierCurvePoints(p3, p4, p5, p6, points); QPointF p7(r.right(), center.y() + ly); QPointF p8(center.x() + lx, r.bottom()); QPointF p9(center.x(), r.bottom()); getBezierCurvePoints(p6, p7, p8, p9, points); QPointF p10(center.x() - lx, r.bottom()); QPointF p11(r.left(), center.y() + ly); getBezierCurvePoints(p9, p10, p11, p0, points); paintPolygon(points); } void KisPainter::paintEllipse(const qreal x, const qreal y, const qreal w, const qreal h) { paintEllipse(QRectF(x, y, w, h)); } void KisPainter::paintAt(const KisPaintInformation& pi, KisDistanceInformation *savedDist) { if (d->paintOp && d->paintOp->canPaint()) { d->paintOp->paintAt(pi, savedDist); } } void KisPainter::fillPolygon(const vQPointF& points, FillStyle fillStyle) { if (points.count() < 3) { return; } if (fillStyle == FillStyleNone) { return; } QPainterPath polygonPath; polygonPath.moveTo(points.at(0)); for (int pointIndex = 1; pointIndex < points.count(); pointIndex++) { polygonPath.lineTo(points.at(pointIndex)); } polygonPath.closeSubpath(); d->fillStyle = fillStyle; fillPainterPath(polygonPath); } void KisPainter::paintPolygon(const vQPointF& points) { if (d->fillStyle != FillStyleNone) { fillPolygon(points, d->fillStyle); } if (d->strokeStyle != StrokeStyleNone) { if (points.count() > 1) { KisDistanceInformation distance(points[0], KisAlgebra2D::directionBetweenPoints(points[0], points[1], 0.0)); for (int i = 0; i < points.count() - 1; i++) { paintLine(KisPaintInformation(points[i]), KisPaintInformation(points[i + 1]), &distance); } paintLine(points[points.count() - 1], points[0], &distance); } } } void KisPainter::paintPainterPath(const QPainterPath& path) { if (d->fillStyle != FillStyleNone) { fillPainterPath(path); } if (d->strokeStyle == StrokeStyleNone) return; QPointF lastPoint, nextPoint; int elementCount = path.elementCount(); KisDistanceInformation saveDist; for (int i = 0; i < elementCount; i++) { QPainterPath::Element element = path.elementAt(i); switch (element.type) { case QPainterPath::MoveToElement: lastPoint = QPointF(element.x, element.y); break; case QPainterPath::LineToElement: nextPoint = QPointF(element.x, element.y); paintLine(KisPaintInformation(lastPoint), KisPaintInformation(nextPoint), &saveDist); lastPoint = nextPoint; break; case QPainterPath::CurveToElement: nextPoint = QPointF(path.elementAt(i + 2).x, path.elementAt(i + 2).y); paintBezierCurve(KisPaintInformation(lastPoint), QPointF(path.elementAt(i).x, path.elementAt(i).y), QPointF(path.elementAt(i + 1).x, path.elementAt(i + 1).y), KisPaintInformation(nextPoint), &saveDist); lastPoint = nextPoint; break; default: continue; } } } void KisPainter::fillPainterPath(const QPainterPath& path) { fillPainterPath(path, QRect()); } void KisPainter::fillPainterPath(const QPainterPath& path, const QRect &requestedRect) { if (d->mirrorHorizontally || d->mirrorVertically) { KisLodTransform lod(d->device); QPointF effectiveAxesCenter = lod.map(d->axesCenter); QTransform C1 = QTransform::fromTranslate(-effectiveAxesCenter.x(), -effectiveAxesCenter.y()); QTransform C2 = QTransform::fromTranslate(effectiveAxesCenter.x(), effectiveAxesCenter.y()); QTransform t; QPainterPath newPath; QRect newRect; if (d->mirrorHorizontally) { t = C1 * QTransform::fromScale(-1,1) * C2; newPath = t.map(path); newRect = t.mapRect(requestedRect); d->fillPainterPathImpl(newPath, newRect); } if (d->mirrorVertically) { t = C1 * QTransform::fromScale(1,-1) * C2; newPath = t.map(path); newRect = t.mapRect(requestedRect); d->fillPainterPathImpl(newPath, newRect); } if (d->mirrorHorizontally && d->mirrorVertically) { t = C1 * QTransform::fromScale(-1,-1) * C2; newPath = t.map(path); newRect = t.mapRect(requestedRect); d->fillPainterPathImpl(newPath, newRect); } } d->fillPainterPathImpl(path, requestedRect); } void KisPainter::Private::fillPainterPathImpl(const QPainterPath& path, const QRect &requestedRect) { if (fillStyle == FillStyleNone) { return; } // Fill the polygon bounding rectangle with the required contents then we'll // create a mask for the actual polygon coverage. if (!fillPainter) { polygon = device->createCompositionSourceDevice(); fillPainter = new KisFillPainter(polygon); } else { polygon->clear(); } Q_CHECK_PTR(polygon); QRectF boundingRect = path.boundingRect(); QRect fillRect = boundingRect.toAlignedRect(); // Expand the rectangle to allow for anti-aliasing. fillRect.adjust(-1, -1, 1, 1); if (requestedRect.isValid()) { fillRect &= requestedRect; } switch (fillStyle) { default: Q_FALLTHROUGH(); case FillStyleForegroundColor: fillPainter->fillRect(fillRect, q->paintColor(), OPACITY_OPAQUE_U8); break; case FillStyleBackgroundColor: fillPainter->fillRect(fillRect, q->backgroundColor(), OPACITY_OPAQUE_U8); break; case FillStylePattern: if (pattern) { // if the user hasn't got any patterns installed, we shouldn't crash... - fillPainter->fillRect(fillRect, pattern); + fillPainter->fillRect(fillRect, pattern, patternTransform); } break; case FillStyleGenerator: if (generator) { // if the user hasn't got any generators, we shouldn't crash... fillPainter->fillRect(fillRect.x(), fillRect.y(), fillRect.width(), fillRect.height(), q->generator()); } break; } if (polygonMaskImage.isNull() || (maskPainter == 0)) { polygonMaskImage = QImage(maskImageWidth, maskImageHeight, QImage::Format_ARGB32_Premultiplied); maskPainter = new QPainter(&polygonMaskImage); maskPainter->setRenderHint(QPainter::Antialiasing, q->antiAliasPolygonFill()); } // Break the mask up into chunks so we don't have to allocate a potentially very large QImage. const QColor black(Qt::black); const QBrush brush(Qt::white); for (qint32 x = fillRect.x(); x < fillRect.x() + fillRect.width(); x += maskImageWidth) { for (qint32 y = fillRect.y(); y < fillRect.y() + fillRect.height(); y += maskImageHeight) { polygonMaskImage.fill(black.rgb()); maskPainter->translate(-x, -y); maskPainter->fillPath(path, brush); maskPainter->translate(x, y); qint32 rectWidth = qMin(fillRect.x() + fillRect.width() - x, maskImageWidth); qint32 rectHeight = qMin(fillRect.y() + fillRect.height() - y, maskImageHeight); KisHLineIteratorSP lineIt = polygon->createHLineIteratorNG(x, y, rectWidth); quint8 tmp; for (int row = y; row < y + rectHeight; row++) { QRgb* line = reinterpret_cast(polygonMaskImage.scanLine(row - y)); do { tmp = qRed(line[lineIt->x() - x]); polygon->colorSpace()->applyAlphaU8Mask(lineIt->rawData(), &tmp, 1); } while (lineIt->nextPixel()); lineIt->nextRow(); } } } QRect bltRect = !requestedRect.isEmpty() ? requestedRect : fillRect; q->bitBlt(bltRect.x(), bltRect.y(), polygon, bltRect.x(), bltRect.y(), bltRect.width(), bltRect.height()); } void KisPainter::drawPainterPath(const QPainterPath& path, const QPen& pen) { drawPainterPath(path, pen, QRect()); } void KisPainter::drawPainterPath(const QPainterPath& path, const QPen& _pen, const QRect &requestedRect) { QPen pen(_pen); pen.setColor(Qt::white); if (!d->fillPainter) { d->polygon = d->device->createCompositionSourceDevice(); d->fillPainter = new KisFillPainter(d->polygon); } else { d->polygon->clear(); } Q_CHECK_PTR(d->polygon); QRectF boundingRect = path.boundingRect(); QRect fillRect = boundingRect.toAlignedRect(); // take width of the pen into account int penWidth = qRound(pen.widthF()); fillRect.adjust(-penWidth, -penWidth, penWidth, penWidth); // Expand the rectangle to allow for anti-aliasing. fillRect.adjust(-1, -1, 1, 1); if (!requestedRect.isNull()) { fillRect &= requestedRect; } d->fillPainter->fillRect(fillRect, paintColor(), OPACITY_OPAQUE_U8); if (d->polygonMaskImage.isNull() || (d->maskPainter == 0)) { d->polygonMaskImage = QImage(d->maskImageWidth, d->maskImageHeight, QImage::Format_ARGB32_Premultiplied); d->maskPainter = new QPainter(&d->polygonMaskImage); d->maskPainter->setRenderHint(QPainter::Antialiasing, antiAliasPolygonFill()); } // Break the mask up into chunks so we don't have to allocate a potentially very large QImage. const QColor black(Qt::black); QPen oldPen = d->maskPainter->pen(); d->maskPainter->setPen(pen); for (qint32 x = fillRect.x(); x < fillRect.x() + fillRect.width(); x += d->maskImageWidth) { for (qint32 y = fillRect.y(); y < fillRect.y() + fillRect.height(); y += d->maskImageHeight) { d->polygonMaskImage.fill(black.rgb()); d->maskPainter->translate(-x, -y); d->maskPainter->drawPath(path); d->maskPainter->translate(x, y); qint32 rectWidth = qMin(fillRect.x() + fillRect.width() - x, d->maskImageWidth); qint32 rectHeight = qMin(fillRect.y() + fillRect.height() - y, d->maskImageHeight); KisHLineIteratorSP lineIt = d->polygon->createHLineIteratorNG(x, y, rectWidth); quint8 tmp; for (int row = y; row < y + rectHeight; row++) { QRgb* line = reinterpret_cast(d->polygonMaskImage.scanLine(row - y)); do { tmp = qRed(line[lineIt->x() - x]); d->polygon->colorSpace()->applyAlphaU8Mask(lineIt->rawData(), &tmp, 1); } while (lineIt->nextPixel()); lineIt->nextRow(); } } } d->maskPainter->setPen(oldPen); QRect r = d->polygon->extent(); bitBlt(r.x(), r.y(), d->polygon, r.x(), r.y(), r.width(), r.height()); } inline void KisPainter::compositeOnePixel(quint8 *dst, const KoColor &color) { d->paramInfo.dstRowStart = dst; d->paramInfo.dstRowStride = 0; d->paramInfo.srcRowStart = color.data(); d->paramInfo.srcRowStride = 0; d->paramInfo.maskRowStart = 0; d->paramInfo.maskRowStride = 0; d->paramInfo.rows = 1; d->paramInfo.cols = 1; d->colorSpace->bitBlt(color.colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); } /**/ void KisPainter::drawLine(const QPointF& start, const QPointF& end, qreal width, bool antialias){ int x1 = qFloor(start.x()); int y1 = qFloor(start.y()); int x2 = qFloor(end.x()); int y2 = qFloor(end.y()); if ((x2 == x1 ) && (y2 == y1)) return; int dstX = x2-x1; int dstY = y2-y1; qreal uniC = dstX*y1 - dstY*x1; qreal projectionDenominator = 1.0 / (pow((double)dstX, 2) + pow((double)dstY, 2)); qreal subPixel; if (qAbs(dstX) > qAbs(dstY)){ subPixel = start.x() - x1; }else{ subPixel = start.y() - y1; } qreal halfWidth = width * 0.5 + subPixel; int W_ = qRound(halfWidth) + 1; // save the state int X1_ = x1; int Y1_ = y1; int X2_ = x2; int Y2_ = y2; if (x2device->createRandomAccessorNG(); KisRandomConstAccessorSP selectionAccessor; if (d->selection) { selectionAccessor = d->selection->projection()->createRandomConstAccessorNG(); } for (int y = y1-W_; y < y2+W_ ; y++){ for (int x = x1-W_; x < x2+W_; x++){ projection = ( (x-X1_)* dstX + (y-Y1_)*dstY ) * projectionDenominator; scanX = X1_ + projection * dstX; scanY = Y1_ + projection * dstY; if (((scanX < x1) || (scanX > x2)) || ((scanY < y1) || (scanY > y2))) { AA_ = qMin( sqrt( pow((double)x - X1_, 2) + pow((double)y - Y1_, 2) ), sqrt( pow((double)x - X2_, 2) + pow((double)y - Y2_, 2) )); }else{ AA_ = qAbs(dstY*x - dstX*y + uniC) * denominator; } if (AA_>halfWidth) { continue; } accessor->moveTo(x, y); if (selectionAccessor) selectionAccessor->moveTo(x,y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { KoColor mycolor = d->paintColor; if (antialias && AA_ > halfWidth-1.0) { mycolor.colorSpace()->multiplyAlpha(mycolor.data(), 1.0 - (AA_-(halfWidth-1.0)), 1); } compositeOnePixel(accessor->rawData(), mycolor); } } } } /**/ void KisPainter::drawLine(const QPointF & start, const QPointF & end) { drawThickLine(start, end, 1, 1); } void KisPainter::drawDDALine(const QPointF & start, const QPointF & end) { int x = qFloor(start.x()); int y = qFloor(start.y()); int x2 = qFloor(end.x()); int y2 = qFloor(end.y()); // Width and height of the line int xd = x2 - x; int yd = y2 - y; float m = 0; bool lockAxis = true; if (xd == 0) { m = 2.0; } else if ( yd != 0) { lockAxis = false; m = (float)yd / (float)xd; } float fx = x; float fy = y; int inc; KisRandomAccessorSP accessor = d->device->createRandomAccessorNG(); KisRandomConstAccessorSP selectionAccessor; if (d->selection) { selectionAccessor = d->selection->projection()->createRandomConstAccessorNG(); } accessor->moveTo(x, y); if (selectionAccessor) selectionAccessor->moveTo(x,y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), d->paintColor); } if (fabs(m) > 1.0f) { inc = (yd > 0) ? 1 : -1; m = (lockAxis)? 0 : 1.0f / m; m *= inc; while (y != y2) { y = y + inc; fx = fx + m; x = qRound(fx); accessor->moveTo(x, y); if (selectionAccessor) selectionAccessor->moveTo(x, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), d->paintColor); } } } else { inc = (xd > 0) ? 1 : -1; m *= inc; while (x != x2) { x = x + inc; fy = fy + m; y = qRound(fy); accessor->moveTo(x, y); if (selectionAccessor) selectionAccessor->moveTo(x, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), d->paintColor); } } } } void KisPainter::drawWobblyLine(const QPointF & start, const QPointF & end) { KoColor mycolor(d->paintColor); int x1 = qFloor(start.x()); int y1 = qFloor(start.y()); int x2 = qFloor(end.x()); int y2 = qFloor(end.y()); KisRandomAccessorSP accessor = d->device->createRandomAccessorNG(); KisRandomConstAccessorSP selectionAccessor; if (d->selection) { selectionAccessor = d->selection->projection()->createRandomConstAccessorNG(); } // Width and height of the line int xd = (x2 - x1); int yd = (y2 - y1); int x; int y; float fx = (x = x1); float fy = (y = y1); float m = (float)yd / (float)xd; int inc; if (fabs(m) > 1) { inc = (yd > 0) ? 1 : -1; m = 1.0f / m; m *= inc; while (y != y2) { fx = fx + m; y = y + inc; x = qRound(fx); float br1 = qFloor(fx + 1) - fx; float br2 = fx - qFloor(fx); accessor->moveTo(x, y); if (selectionAccessor) selectionAccessor->moveTo(x, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { mycolor.setOpacity((quint8)(255*br1)); compositeOnePixel(accessor->rawData(), mycolor); } accessor->moveTo(x + 1, y); if (selectionAccessor) selectionAccessor->moveTo(x + 1, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { mycolor.setOpacity((quint8)(255*br2)); compositeOnePixel(accessor->rawData(), mycolor); } } } else { inc = (xd > 0) ? 1 : -1; m *= inc; while (x != x2) { fy = fy + m; x = x + inc; y = qRound(fy); float br1 = qFloor(fy + 1) - fy; float br2 = fy - qFloor(fy); accessor->moveTo(x, y); if (selectionAccessor) selectionAccessor->moveTo(x, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { mycolor.setOpacity((quint8)(255*br1)); compositeOnePixel(accessor->rawData(), mycolor); } accessor->moveTo(x, y + 1); if (selectionAccessor) selectionAccessor->moveTo(x, y + 1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { mycolor.setOpacity((quint8)(255*br2)); compositeOnePixel(accessor->rawData(), mycolor); } } } } void KisPainter::drawWuLine(const QPointF & start, const QPointF & end) { KoColor lineColor(d->paintColor); int x1 = qFloor(start.x()); int y1 = qFloor(start.y()); int x2 = qFloor(end.x()); int y2 = qFloor(end.y()); KisRandomAccessorSP accessor = d->device->createRandomAccessorNG(); KisRandomConstAccessorSP selectionAccessor; if (d->selection) { selectionAccessor = d->selection->projection()->createRandomConstAccessorNG(); } float grad, xd, yd; float xgap, ygap, xend, yend, yf, xf; float brightness1, brightness2; int ix1, ix2, iy1, iy2; quint8 c1, c2; // gradient of line xd = (x2 - x1); yd = (y2 - y1); if (yd == 0) { /* Horizontal line */ int incr = (x1 < x2) ? 1 : -1; ix1 = x1; ix2 = x2; iy1 = y1; while (ix1 != ix2) { ix1 = ix1 + incr; accessor->moveTo(ix1, iy1); if (selectionAccessor) selectionAccessor->moveTo(ix1, iy1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), lineColor); } } return; } if (xd == 0) { /* Vertical line */ int incr = (y1 < y2) ? 1 : -1; iy1 = y1; iy2 = y2; ix1 = x1; while (iy1 != iy2) { iy1 = iy1 + incr; accessor->moveTo(ix1, iy1); if (selectionAccessor) selectionAccessor->moveTo(ix1, iy1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), lineColor); } } return; } if (fabs(xd) > fabs(yd)) { // horizontal line // line have to be paint from left to right if (x1 > x2) { std::swap(x1, x2); std::swap(y1, y2); xd = (x2 - x1); yd = (y2 - y1); } grad = yd / xd; // nearest X,Y integer coordinates xend = x1; yend = y1 + grad * (xend - x1); xgap = invertFrac(x1 + 0.5f); ix1 = x1; iy1 = qFloor(yend); // calc the intensity of the other end point pixel pair. brightness1 = invertFrac(yend) * xgap; brightness2 = frac(yend) * xgap; c1 = (int)(brightness1 * OPACITY_OPAQUE_U8); c2 = (int)(brightness2 * OPACITY_OPAQUE_U8); accessor->moveTo(ix1, iy1); if (selectionAccessor) selectionAccessor->moveTo(ix1, iy1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c1); compositeOnePixel(accessor->rawData(), lineColor); } accessor->moveTo(ix1, iy1 + 1); if (selectionAccessor) selectionAccessor->moveTo(ix1, iy1 + 1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c2); compositeOnePixel(accessor->rawData(), lineColor); } // calc first Y-intersection for main loop yf = yend + grad; xend = x2; yend = y2 + grad * (xend - x2); xgap = invertFrac(x2 - 0.5f); ix2 = x2; iy2 = qFloor(yend); brightness1 = invertFrac(yend) * xgap; brightness2 = frac(yend) * xgap; c1 = (int)(brightness1 * OPACITY_OPAQUE_U8); c2 = (int)(brightness2 * OPACITY_OPAQUE_U8); accessor->moveTo(ix2, iy2); if (selectionAccessor) selectionAccessor->moveTo(ix2, iy2); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c1); compositeOnePixel(accessor->rawData(), lineColor); } accessor->moveTo(ix2, iy2 + 1); if (selectionAccessor) selectionAccessor->moveTo(ix2, iy2 + 1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c2); compositeOnePixel(accessor->rawData(), lineColor); } // main loop for (int x = ix1 + 1; x <= ix2 - 1; x++) { brightness1 = invertFrac(yf); brightness2 = frac(yf); c1 = (int)(brightness1 * OPACITY_OPAQUE_U8); c2 = (int)(brightness2 * OPACITY_OPAQUE_U8); accessor->moveTo(x, qFloor(yf)); if (selectionAccessor) selectionAccessor->moveTo(x, qFloor(yf)); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c1); compositeOnePixel(accessor->rawData(), lineColor); } accessor->moveTo(x, qFloor(yf) + 1); if (selectionAccessor) selectionAccessor->moveTo(x, qFloor(yf) + 1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c2); compositeOnePixel(accessor->rawData(), lineColor); } yf = yf + grad; } } else { //vertical // line have to be painted from left to right if (y1 > y2) { std::swap(x1, x2); std::swap(y1, y2); xd = (x2 - x1); yd = (y2 - y1); } grad = xd / yd; // nearest X,Y integer coordinates yend = y1; xend = x1 + grad * (yend - y1); ygap = y1; ix1 = qFloor(xend); iy1 = y1; // calc the intensity of the other end point pixel pair. brightness1 = invertFrac(xend) * ygap; brightness2 = frac(xend) * ygap; c1 = (int)(brightness1 * OPACITY_OPAQUE_U8); c2 = (int)(brightness2 * OPACITY_OPAQUE_U8); accessor->moveTo(ix1, iy1); if (selectionAccessor) selectionAccessor->moveTo(ix1, iy1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c1); compositeOnePixel(accessor->rawData(), lineColor); } accessor->moveTo(x1 + 1, y1); if (selectionAccessor) selectionAccessor->moveTo(x1 + 1, y1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c2); compositeOnePixel(accessor->rawData(), lineColor); } // calc first Y-intersection for main loop xf = xend + grad; yend = y2; xend = x2 + grad * (yend - y2); ygap = invertFrac(y2 - 0.5f); ix2 = qFloor(xend); iy2 = y2; brightness1 = invertFrac(xend) * ygap; brightness2 = frac(xend) * ygap; c1 = (int)(brightness1 * OPACITY_OPAQUE_U8); c2 = (int)(brightness2 * OPACITY_OPAQUE_U8); accessor->moveTo(ix2, iy2); if (selectionAccessor) selectionAccessor->moveTo(ix2, iy2); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c1); compositeOnePixel(accessor->rawData(), lineColor); } accessor->moveTo(ix2 + 1, iy2); if (selectionAccessor) selectionAccessor->moveTo(ix2 + 1, iy2); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c2); compositeOnePixel(accessor->rawData(), lineColor); } // main loop for (int y = iy1 + 1; y <= iy2 - 1; y++) { brightness1 = invertFrac(xf); brightness2 = frac(xf); c1 = (int)(brightness1 * OPACITY_OPAQUE_U8); c2 = (int)(brightness2 * OPACITY_OPAQUE_U8); accessor->moveTo(qFloor(xf), y); if (selectionAccessor) selectionAccessor->moveTo(qFloor(xf), y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c1); compositeOnePixel(accessor->rawData(), lineColor); } accessor->moveTo(qFloor(xf) + 1, y); if (selectionAccessor) selectionAccessor->moveTo(qFloor(xf) + 1, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c2); compositeOnePixel(accessor->rawData(), lineColor); } xf = xf + grad; } }//end-of-else } void KisPainter::drawThickLine(const QPointF & start, const QPointF & end, int startWidth, int endWidth) { KisRandomAccessorSP accessor = d->device->createRandomAccessorNG(); KisRandomConstAccessorSP selectionAccessor; if (d->selection) { selectionAccessor = d->selection->projection()->createRandomConstAccessorNG(); } const KoColorSpace *cs = d->device->colorSpace(); KoColor c1(d->paintColor); KoColor c2(d->paintColor); KoColor c3(d->paintColor); KoColor col1(c1); KoColor col2(c1); float grada, gradb, dxa, dxb, dya, dyb, fraca, fracb, xfa, yfa, xfb, yfb, b1a, b2a, b1b, b2b, dstX, dstY; int x, y, ix1, ix2, iy1, iy2; int x0a, y0a, x1a, y1a, x0b, y0b, x1b, y1b; int tp0, tn0, tp1, tn1; int horizontal = 0; float opacity = 1.0; tp0 = startWidth / 2; tn0 = startWidth / 2; if (startWidth % 2 == 0) // even width startWidth tn0--; tp1 = endWidth / 2; tn1 = endWidth / 2; if (endWidth % 2 == 0) // even width endWidth tn1--; int x0 = qRound(start.x()); int y0 = qRound(start.y()); int x1 = qRound(end.x()); int y1 = qRound(end.y()); dstX = x1 - x0; // run of general line dstY = y1 - y0; // rise of general line if (dstY < 0) dstY = -dstY; if (dstX < 0) dstX = -dstX; if (dstX > dstY) { // horizontalish horizontal = 1; x0a = x0; y0a = y0 - tn0; x0b = x0; y0b = y0 + tp0; x1a = x1; y1a = y1 - tn1; x1b = x1; y1b = y1 + tp1; } else { x0a = x0 - tn0; y0a = y0; x0b = x0 + tp0; y0b = y0; x1a = x1 - tn1; y1a = y1; x1b = x1 + tp1; y1b = y1; } if (horizontal) { // draw endpoints for (int i = y0a; i <= y0b; i++) { accessor->moveTo(x0, i); if (selectionAccessor) selectionAccessor->moveTo(x0, i); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), c1); } } for (int i = y1a; i <= y1b; i++) { accessor->moveTo(x1, i); if (selectionAccessor) selectionAccessor->moveTo(x1, i); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), c1); } } } else { for (int i = x0a; i <= x0b; i++) { accessor->moveTo(i, y0); if (selectionAccessor) selectionAccessor->moveTo(i, y0); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), c1); } } for (int i = x1a; i <= x1b; i++) { accessor->moveTo(i, y1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), c1); } } } //antialias endpoints if (x1 != x0 && y1 != y0) { if (horizontal) { accessor->moveTo(x0a, y0a - 1); if (selectionAccessor) selectionAccessor->moveTo(x0a, y0a - 1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = .25 * c1.opacityF() + (1 - .25) * alpha; col1.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col1); } accessor->moveTo(x1b, y1b + 1); if (selectionAccessor) selectionAccessor->moveTo(x1b, y1b + 1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = .25 * c2.opacityF() + (1 - .25) * alpha; col1.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col1); } } else { accessor->moveTo(x0a - 1, y0a); if (selectionAccessor) selectionAccessor->moveTo(x0a - 1, y0a); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = .25 * c1.opacityF() + (1 - .25) * alpha; col1.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col1); } accessor->moveTo(x1b + 1, y1b); if (selectionAccessor) selectionAccessor->moveTo(x1b + 1, y1b); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = .25 * c2.opacityF() + (1 - .25) * alpha; col1.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col1); } } } dxa = x1a - x0a; // run of a dya = y1a - y0a; // rise of a dxb = x1b - x0b; // run of b dyb = y1b - y0b; // rise of b if (horizontal) { // horizontal-ish lines if (x1 < x0) { int xt, yt, wt; KoColor tmp; xt = x1a; x1a = x0a; x0a = xt; yt = y1a; y1a = y0a; y0a = yt; xt = x1b; x1b = x0b; x0b = xt; yt = y1b; y1b = y0b; y0b = yt; xt = x1; x1 = x0; x0 = xt; yt = y1; y1 = y0; y0 = yt; tmp = c1; c1 = c2; c2 = tmp; wt = startWidth; startWidth = endWidth; endWidth = wt; } grada = dya / dxa; gradb = dyb / dxb; ix1 = x0; iy1 = y0; ix2 = x1; iy2 = y1; yfa = y0a + grada; yfb = y0b + gradb; for (x = ix1 + 1; x <= ix2 - 1; x++) { fraca = yfa - qFloor(yfa); b1a = 1 - fraca; b2a = fraca; fracb = yfb - qFloor(yfb); b1b = 1 - fracb; b2b = fracb; // color first pixel of bottom line opacity = ((x - ix1) / dstX) * c2.opacityF() + (1 - (x - ix1) / dstX) * c1.opacityF(); c3.setOpacity(opacity); accessor->moveTo(x, qFloor(yfa)); if (selectionAccessor) selectionAccessor->moveTo(x, qFloor(yfa)); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = b1a * c3.opacityF() + (1 - b1a) * alpha; col1.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col1); } // color first pixel of top line if (!(startWidth == 1 && endWidth == 1)) { accessor->moveTo(x, qFloor(yfb)); if (selectionAccessor) selectionAccessor->moveTo(x, qFloor(yfb)); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = b1b * c3.opacityF() + (1 - b1b) * alpha; col1.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col1); } } // color second pixel of bottom line if (grada != 0 && grada != 1) { // if not flat or exact diagonal accessor->moveTo(x, qFloor(yfa) + 1); if (selectionAccessor) selectionAccessor->moveTo(x, qFloor(yfa) + 1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = b2a * c3.opacityF() + (1 - b2a) * alpha; col2.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col2); } } // color second pixel of top line if (gradb != 0 && gradb != 1 && !(startWidth == 1 && endWidth == 1)) { accessor->moveTo(x, qFloor(yfb) + 1); if (selectionAccessor) selectionAccessor->moveTo(x, qFloor(yfb) + 1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = b2b * c3.opacityF() + (1 - b2b) * alpha; col2.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col2); } } // fill remaining pixels if (!(startWidth == 1 && endWidth == 1)) { if (yfa < yfb) for (int i = qFloor(yfa) + 1; i <= qFloor(yfb); i++) { accessor->moveTo(x, i); if (selectionAccessor) selectionAccessor->moveTo(x, i); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), c3); } } else for (int i = qFloor(yfa) + 1; i >= qFloor(yfb); i--) { accessor->moveTo(x, i); if (selectionAccessor) selectionAccessor->moveTo(x, i); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), c3); } } } yfa += grada; yfb += gradb; } } else { // vertical-ish lines if (y1 < y0) { int xt, yt, wt; xt = x1a; x1a = x0a; x0a = xt; yt = y1a; y1a = y0a; y0a = yt; xt = x1b; x1b = x0b; x0b = xt; yt = y1b; y1b = y0b; y0b = yt; xt = x1; x1 = x0; x0 = xt; yt = y1; y1 = y0; y0 = yt; KoColor tmp; tmp = c1; c1 = c2; c2 = tmp; wt = startWidth; startWidth = endWidth; endWidth = wt; } grada = dxa / dya; gradb = dxb / dyb; ix1 = x0; iy1 = y0; ix2 = x1; iy2 = y1; xfa = x0a + grada; xfb = x0b + gradb; for (y = iy1 + 1; y <= iy2 - 1; y++) { fraca = xfa - qFloor(xfa); b1a = 1 - fraca; b2a = fraca; fracb = xfb - qFloor(xfb); b1b = 1 - fracb; b2b = fracb; // color first pixel of left line opacity = ((y - iy1) / dstY) * c2.opacityF() + (1 - (y - iy1) / dstY) * c1.opacityF(); c3.setOpacity(opacity); accessor->moveTo(qFloor(xfa), y); if (selectionAccessor) selectionAccessor->moveTo(qFloor(xfa), y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = b1a * c3.opacityF() + (1 - b1a) * alpha; col1.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col1); } // color first pixel of right line if (!(startWidth == 1 && endWidth == 1)) { accessor->moveTo(qFloor(xfb), y); if (selectionAccessor) selectionAccessor->moveTo(qFloor(xfb), y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = b1b * c3.opacityF() + (1 - b1b) * alpha; col1.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col1); } } // color second pixel of left line if (grada != 0 && grada != 1) { // if not flat or exact diagonal accessor->moveTo(qFloor(xfa) + 1, y); if (selectionAccessor) selectionAccessor->moveTo(qFloor(xfa) + 1, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = b2a * c3.opacityF() + (1 - b2a) * alpha; col2.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col2); } } // color second pixel of right line if (gradb != 0 && gradb != 1 && !(startWidth == 1 && endWidth == 1)) { accessor->moveTo(qFloor(xfb) + 1, y); if (selectionAccessor) selectionAccessor->moveTo(qFloor(xfb) + 1, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = b2b * c3.opacityF() + (1 - b2b) * alpha; col2.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col2); } } // fill remaining pixels between current xfa,xfb if (!(startWidth == 1 && endWidth == 1)) { if (xfa < xfb) for (int i = qFloor(xfa) + 1; i <= qFloor(xfb); i++) { accessor->moveTo(i, y); if (selectionAccessor) selectionAccessor->moveTo(i, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), c3); } } else for (int i = qFloor(xfb); i <= qFloor(xfa) + 1; i++) { accessor->moveTo(i, y); if (selectionAccessor) selectionAccessor->moveTo(i, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), c3); } } } xfa += grada; xfb += gradb; } } } void KisPainter::setProgress(KoUpdater * progressUpdater) { d->progressUpdater = progressUpdater; } const KisPaintDeviceSP KisPainter::device() const { return d->device; } KisPaintDeviceSP KisPainter::device() { return d->device; } void KisPainter::setChannelFlags(QBitArray channelFlags) { // Q_ASSERT(channelFlags.isEmpty() || quint32(channelFlags.size()) == d->colorSpace->channelCount()); // Now, if all bits in the channelflags are true, pass an empty channel flags bitarray // because otherwise the compositeops cannot optimize. d->paramInfo.channelFlags = channelFlags; if (!channelFlags.isEmpty() && channelFlags == QBitArray(channelFlags.size(), true)) { d->paramInfo.channelFlags = QBitArray(); } } QBitArray KisPainter::channelFlags() { return d->paramInfo.channelFlags; } void KisPainter::setPattern(const KoPatternSP pattern) { d->pattern = pattern; } const KoPatternSP KisPainter::pattern() const { return d->pattern; } void KisPainter::setPaintColor(const KoColor& color) { d->paintColor = color; if (d->device) { d->paintColor.convertTo(d->device->compositionSourceColorSpace()); } } const KoColor &KisPainter::paintColor() const { return d->paintColor; } void KisPainter::setBackgroundColor(const KoColor& color) { d->backgroundColor = color; if (d->device) { d->backgroundColor.convertTo(d->device->compositionSourceColorSpace()); } } const KoColor &KisPainter::backgroundColor() const { return d->backgroundColor; } void KisPainter::setGenerator(KisFilterConfigurationSP generator) { d->generator = generator; } const KisFilterConfigurationSP KisPainter::generator() const { return d->generator; } void KisPainter::setFillStyle(FillStyle fillStyle) { d->fillStyle = fillStyle; } KisPainter::FillStyle KisPainter::fillStyle() const { return d->fillStyle; } +void KisPainter::setPatternTransform(QTransform transform) +{ + d->patternTransform = transform; +} + +QTransform KisPainter::patternTransform() +{ + return d->patternTransform; +} + void KisPainter::setAntiAliasPolygonFill(bool antiAliasPolygonFill) { d->antiAliasPolygonFill = antiAliasPolygonFill; } bool KisPainter::antiAliasPolygonFill() { return d->antiAliasPolygonFill; } void KisPainter::setStrokeStyle(KisPainter::StrokeStyle strokeStyle) { d->strokeStyle = strokeStyle; } KisPainter::StrokeStyle KisPainter::strokeStyle() const { return d->strokeStyle; } void KisPainter::setFlow(quint8 flow) { d->paramInfo.flow = float(flow) / 255.0f; } quint8 KisPainter::flow() const { return quint8(d->paramInfo.flow * 255.0f); } void KisPainter::setOpacityUpdateAverage(quint8 opacity) { d->isOpacityUnit = opacity == OPACITY_OPAQUE_U8; d->paramInfo.updateOpacityAndAverage(float(opacity) / 255.0f); } void KisPainter::setAverageOpacity(qreal averageOpacity) { d->paramInfo.setOpacityAndAverage(d->paramInfo.opacity, averageOpacity); } qreal KisPainter::blendAverageOpacity(qreal opacity, qreal averageOpacity) { const float exponent = 0.1; return averageOpacity < opacity ? opacity : exponent * opacity + (1.0 - exponent) * (averageOpacity); } void KisPainter::setOpacity(quint8 opacity) { d->isOpacityUnit = opacity == OPACITY_OPAQUE_U8; d->paramInfo.opacity = float(opacity) / 255.0f; } quint8 KisPainter::opacity() const { return quint8(d->paramInfo.opacity * 255.0f); } void KisPainter::setCompositeOp(const KoCompositeOp * op) { d->compositeOp = op; } const KoCompositeOp * KisPainter::compositeOp() { return d->compositeOp; } /** * TODO: Rename this setCompositeOpId(). See KoCompositeOpRegistry.h */ void KisPainter::setCompositeOp(const QString& op) { d->compositeOp = d->colorSpace->compositeOp(op); } void KisPainter::setSelection(KisSelectionSP selection) { d->selection = selection; } KisSelectionSP KisPainter::selection() { return d->selection; } KoUpdater * KisPainter::progressUpdater() { return d->progressUpdater; } void KisPainter::setGradient(const KoAbstractGradientSP gradient) { d->gradient = gradient; } const KoAbstractGradientSP KisPainter::gradient() const { return d->gradient; } void KisPainter::setPaintOpPreset(KisPaintOpPresetSP preset, KisNodeSP node, KisImageSP image) { d->paintOpPreset = preset; KisPaintOp *paintop = KisPaintOpRegistry::instance()->paintOp(preset, this, node, image); Q_ASSERT(paintop); if (paintop) { delete d->paintOp; d->paintOp = paintop; } else { warnKrita << "Could not create paintop for preset " << preset->name(); } } KisPaintOpPresetSP KisPainter::preset() const { return d->paintOpPreset; } KisPaintOp* KisPainter::paintOp() const { return d->paintOp; } void KisPainter::setMirrorInformation(const QPointF& axesCenter, bool mirrorHorizontally, bool mirrorVertically) { d->axesCenter = axesCenter; d->mirrorHorizontally = mirrorHorizontally; d->mirrorVertically = mirrorVertically; } void KisPainter::copyMirrorInformationFrom(const KisPainter *other) { d->axesCenter = other->d->axesCenter; d->mirrorHorizontally = other->d->mirrorHorizontally; d->mirrorVertically = other->d->mirrorVertically; } bool KisPainter::hasMirroring() const { return d->mirrorHorizontally || d->mirrorVertically; } bool KisPainter::hasHorizontalMirroring() const { return d->mirrorHorizontally; } bool KisPainter::hasVerticalMirroring() const { return d->mirrorVertically; } void KisPainter::setMaskImageSize(qint32 width, qint32 height) { d->maskImageWidth = qBound(1, width, 256); d->maskImageHeight = qBound(1, height, 256); d->fillPainter = 0; d->polygonMaskImage = QImage(); } //void KisPainter::setLockAlpha(bool protect) //{ // if(d->paramInfo.channelFlags.isEmpty()) { // d->paramInfo.channelFlags = d->colorSpace->channelFlags(true, true); // } // QBitArray switcher = // d->colorSpace->channelFlags(protect, !protect); // if(protect) { // d->paramInfo.channelFlags &= switcher; // } // else { // d->paramInfo.channelFlags |= switcher; // } // Q_ASSERT(quint32(d->paramInfo.channelFlags.size()) == d->colorSpace->channelCount()); //} //bool KisPainter::alphaLocked() const //{ // QBitArray switcher = d->colorSpace->channelFlags(false, true); // return !(d->paramInfo.channelFlags & switcher).count(true); //} void KisPainter::setRenderingIntent(KoColorConversionTransformation::Intent intent) { d->renderingIntent = intent; } void KisPainter::setColorConversionFlags(KoColorConversionTransformation::ConversionFlags conversionFlags) { d->conversionFlags = conversionFlags; } void KisPainter::setRunnableStrokeJobsInterface(KisRunnableStrokeJobsInterface *interface) { d->runnableStrokeJobsInterface = interface; } KisRunnableStrokeJobsInterface *KisPainter::runnableStrokeJobsInterface() const { if (!d->runnableStrokeJobsInterface) { if (!d->fakeRunnableStrokeJobsInterface) { d->fakeRunnableStrokeJobsInterface.reset(new KisFakeRunnableStrokeJobsExecutor()); } return d->fakeRunnableStrokeJobsInterface.data(); } return d->runnableStrokeJobsInterface; } void KisPainter::renderMirrorMaskSafe(QRect rc, KisFixedPaintDeviceSP dab, bool preserveDab) { if (!d->mirrorHorizontally && !d->mirrorVertically) return; KisFixedPaintDeviceSP dabToProcess = dab; if (preserveDab) { dabToProcess = new KisFixedPaintDevice(*dab); } renderMirrorMask(rc, dabToProcess); } void KisPainter::renderMirrorMaskSafe(QRect rc, KisPaintDeviceSP dab, int sx, int sy, KisFixedPaintDeviceSP mask, bool preserveMask) { if (!d->mirrorHorizontally && !d->mirrorVertically) return; KisFixedPaintDeviceSP maskToProcess = mask; if (preserveMask) { maskToProcess = new KisFixedPaintDevice(*mask); } renderMirrorMask(rc, dab, sx, sy, maskToProcess); } void KisPainter::renderMirrorMask(QRect rc, KisFixedPaintDeviceSP dab) { int x = rc.topLeft().x(); int y = rc.topLeft().y(); KisLodTransform t(d->device); QPoint effectiveAxesCenter = t.map(d->axesCenter).toPoint(); int mirrorX = -((x+rc.width()) - effectiveAxesCenter.x()) + effectiveAxesCenter.x(); int mirrorY = -((y+rc.height()) - effectiveAxesCenter.y()) + effectiveAxesCenter.y(); if (d->mirrorHorizontally && d->mirrorVertically){ dab->mirror(true, false); bltFixed(mirrorX, y, dab, 0,0,rc.width(),rc.height()); dab->mirror(false,true); bltFixed(mirrorX, mirrorY, dab, 0,0,rc.width(),rc.height()); dab->mirror(true, false); bltFixed(x, mirrorY, dab, 0,0,rc.width(),rc.height()); } else if (d->mirrorHorizontally){ dab->mirror(true, false); bltFixed(mirrorX, y, dab, 0,0,rc.width(),rc.height()); } else if (d->mirrorVertically){ dab->mirror(false, true); bltFixed(x, mirrorY, dab, 0,0,rc.width(),rc.height()); } } void KisPainter::renderMirrorMask(QRect rc, KisFixedPaintDeviceSP dab, KisFixedPaintDeviceSP mask) { int x = rc.topLeft().x(); int y = rc.topLeft().y(); KisLodTransform t(d->device); QPoint effectiveAxesCenter = t.map(d->axesCenter).toPoint(); int mirrorX = -((x+rc.width()) - effectiveAxesCenter.x()) + effectiveAxesCenter.x(); int mirrorY = -((y+rc.height()) - effectiveAxesCenter.y()) + effectiveAxesCenter.y(); if (d->mirrorHorizontally && d->mirrorVertically){ dab->mirror(true, false); mask->mirror(true, false); bltFixedWithFixedSelection(mirrorX,y, dab, mask, rc.width() ,rc.height() ); dab->mirror(false,true); mask->mirror(false, true); bltFixedWithFixedSelection(mirrorX,mirrorY, dab, mask, rc.width() ,rc.height() ); dab->mirror(true, false); mask->mirror(true, false); bltFixedWithFixedSelection(x,mirrorY, dab, mask, rc.width() ,rc.height() ); }else if (d->mirrorHorizontally){ dab->mirror(true, false); mask->mirror(true, false); bltFixedWithFixedSelection(mirrorX,y, dab, mask, rc.width() ,rc.height() ); }else if (d->mirrorVertically){ dab->mirror(false, true); mask->mirror(false, true); bltFixedWithFixedSelection(x,mirrorY, dab, mask, rc.width() ,rc.height() ); } } void KisPainter::renderMirrorMask(QRect rc, KisPaintDeviceSP dab){ if (d->mirrorHorizontally || d->mirrorVertically){ KisFixedPaintDeviceSP mirrorDab(new KisFixedPaintDevice(dab->colorSpace())); QRect dabRc( QPoint(0,0), QSize(rc.width(),rc.height()) ); mirrorDab->setRect(dabRc); mirrorDab->lazyGrowBufferWithoutInitialization(); dab->readBytes(mirrorDab->data(),rc); renderMirrorMask( QRect(rc.topLeft(),dabRc.size()), mirrorDab); } } void KisPainter::renderMirrorMask(QRect rc, KisPaintDeviceSP dab, int sx, int sy, KisFixedPaintDeviceSP mask) { if (d->mirrorHorizontally || d->mirrorVertically){ KisFixedPaintDeviceSP mirrorDab(new KisFixedPaintDevice(dab->colorSpace())); QRect dabRc( QPoint(0,0), QSize(rc.width(),rc.height()) ); mirrorDab->setRect(dabRc); mirrorDab->lazyGrowBufferWithoutInitialization(); dab->readBytes(mirrorDab->data(),QRect(QPoint(sx,sy),rc.size())); renderMirrorMask(rc, mirrorDab, mask); } } void KisPainter::renderDabWithMirroringNonIncremental(QRect rc, KisPaintDeviceSP dab) { QVector rects; int x = rc.topLeft().x(); int y = rc.topLeft().y(); KisLodTransform t(d->device); QPoint effectiveAxesCenter = t.map(d->axesCenter).toPoint(); int mirrorX = -((x+rc.width()) - effectiveAxesCenter.x()) + effectiveAxesCenter.x(); int mirrorY = -((y+rc.height()) - effectiveAxesCenter.y()) + effectiveAxesCenter.y(); rects << rc; if (d->mirrorHorizontally && d->mirrorVertically){ rects << QRect(mirrorX, y, rc.width(), rc.height()); rects << QRect(mirrorX, mirrorY, rc.width(), rc.height()); rects << QRect(x, mirrorY, rc.width(), rc.height()); } else if (d->mirrorHorizontally) { rects << QRect(mirrorX, y, rc.width(), rc.height()); } else if (d->mirrorVertically) { rects << QRect(x, mirrorY, rc.width(), rc.height()); } Q_FOREACH (const QRect &rc, rects) { d->device->clear(rc); } QRect resultRect = dab->extent() | rc; bool intersects = false; for (int i = 1; i < rects.size(); i++) { if (rects[i].intersects(resultRect)) { intersects = true; break; } } /** * If there are no cross-intersections, we can use a fast path * and do no cycling recompositioning */ if (!intersects) { rects.resize(1); } Q_FOREACH (const QRect &rc, rects) { bitBlt(rc.topLeft(), dab, rc); } Q_FOREACH (const QRect &rc, rects) { renderMirrorMask(rc, dab); } } bool KisPainter::hasDirtyRegion() const { return !d->dirtyRects.isEmpty(); } void KisPainter::mirrorRect(Qt::Orientation direction, QRect *rc) const { KisLodTransform t(d->device); QPoint effectiveAxesCenter = t.map(d->axesCenter).toPoint(); KritaUtils::mirrorRect(direction, effectiveAxesCenter, rc); } void KisPainter::mirrorDab(Qt::Orientation direction, KisRenderedDab *dab, bool skipMirrorPixels) const { KisLodTransform t(d->device); QPoint effectiveAxesCenter = t.map(d->axesCenter).toPoint(); KritaUtils::mirrorDab(direction, effectiveAxesCenter, dab, skipMirrorPixels); } namespace { inline void mirrorOneObject(Qt::Orientation dir, const QPoint ¢er, QRect *rc) { KritaUtils::mirrorRect(dir, center, rc); } inline void mirrorOneObject(Qt::Orientation dir, const QPoint ¢er, QPointF *pt) { KritaUtils::mirrorPoint(dir, center, pt); } inline void mirrorOneObject(Qt::Orientation dir, const QPoint ¢er, QPair *pair) { KritaUtils::mirrorPoint(dir, center, &pair->first); KritaUtils::mirrorPoint(dir, center, &pair->second); } } template QVector KisPainter::Private::calculateMirroredObjects(const T &object) { QVector result; KisLodTransform t(this->device); const QPoint effectiveAxesCenter = t.map(this->axesCenter).toPoint(); T baseObject = object; result << baseObject; if (this->mirrorHorizontally && this->mirrorVertically){ mirrorOneObject(Qt::Horizontal, effectiveAxesCenter, &baseObject); result << baseObject; mirrorOneObject(Qt::Vertical, effectiveAxesCenter, &baseObject); result << baseObject; mirrorOneObject(Qt::Horizontal, effectiveAxesCenter, &baseObject); result << baseObject; } else if (this->mirrorHorizontally) { mirrorOneObject(Qt::Horizontal, effectiveAxesCenter, &baseObject); result << baseObject; } else if (this->mirrorVertically) { mirrorOneObject(Qt::Vertical, effectiveAxesCenter, &baseObject); result << baseObject; } return result; } const QVector KisPainter::calculateAllMirroredRects(const QRect &rc) { return d->calculateMirroredObjects(rc); } const QVector KisPainter::calculateAllMirroredPoints(const QPointF &pos) { return d->calculateMirroredObjects(pos); } const QVector> KisPainter::calculateAllMirroredPoints(const QPair &pair) { return d->calculateMirroredObjects(pair); } diff --git a/libs/image/kis_painter.h b/libs/image/kis_painter.h index 8ed68c311e..5b38797201 100644 --- a/libs/image/kis_painter.h +++ b/libs/image/kis_painter.h @@ -1,899 +1,905 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004 Clarence Dang * Copyright (c) 2008-2010 Lukáš Tvrdý * Copyright (c) 2010 José Luis Vergara Toloza * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public 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_PAINTER_H_ #define KIS_PAINTER_H_ #include #include #include #include #include #include #include "kundo2magicstring.h" #include "kis_types.h" #include #include class QPen; class KUndo2Command; class QRect; class QRectF; class QBitArray; class QPainterPath; class KoUpdater; class KoColor; class KoCompositeOp; class KisUndoAdapter; class KisPostExecutionUndoAdapter; class KisTransaction; class KisPaintInformation; class KisPaintOp; class KisDistanceInformation; struct KisRenderedDab; class KisRunnableStrokeJobsInterface; /** * KisPainter contains the graphics primitives necessary to draw on a * KisPaintDevice. This is the same kind of abstraction as used in Qt * itself, where you have QPainter and QPaintDevice. * * However, KisPainter works on a tiled image and supports different * color models, and that's a lot more complicated. * * KisPainter supports transactions that can group various paint operations * in one undoable step. * * For more complex operations, you might want to have a look at the subclasses * of KisPainter: KisConvolutionPainter, KisFillPainter and KisGradientPainter * * KisPainter sets a number of default values, like COMPOSITE_OVER for compositeop, * OPACITY_OPAQUE for opacity and no selection for selection. */ class KRITAIMAGE_EXPORT KisPainter { public: /// Construct painter without a device KisPainter(); /// Construct a painter, and begin painting on the device KisPainter(KisPaintDeviceSP device); /// Construct a painter, and begin painting on the device. All actions will be masked by the given selection. KisPainter(KisPaintDeviceSP device, KisSelectionSP selection); virtual ~KisPainter(); public: static void copyAreaOptimized(const QPoint &dstPt, KisPaintDeviceSP src, KisPaintDeviceSP dst, const QRect &originalSrcRect); static void copyAreaOptimizedOldData(const QPoint &dstPt, KisPaintDeviceSP src, KisPaintDeviceSP dst, const QRect &originalSrcRect); static void copyAreaOptimized(const QPoint &dstPt, KisPaintDeviceSP src, KisPaintDeviceSP dst, const QRect &originalSrcRect, KisSelectionSP selection); static KisPaintDeviceSP convertToAlphaAsAlpha(KisPaintDeviceSP src); static KisPaintDeviceSP convertToAlphaAsGray(KisPaintDeviceSP src); /** * creates a paint device with only alpha values from src */ static KisPaintDeviceSP convertToAlphaAsPureAlpha(KisPaintDeviceSP src); static bool checkDeviceHasTransparency(KisPaintDeviceSP dev); /** * Start painting on the specified device. Not undoable. */ void begin(KisPaintDeviceSP device); /** * Start painting on the specified paint device. All actions will be masked by the given selection. */ void begin(KisPaintDeviceSP device, KisSelectionSP selection); /** * Finish painting on the current device */ void end(); /** * If set, the painter action is cancelable, if the action supports that. */ void setProgress(KoUpdater * progressUpdater); /// Begin an undoable paint operation void beginTransaction(const KUndo2MagicString& transactionName = KUndo2MagicString(),int timedID = -1); /// Cancel all the changes made by the painter void revertTransaction(); /// Finish the undoable paint operation void endTransaction(KisUndoAdapter *undoAdapter); /** * Finish transaction and load it to a special adapter for strokes */ void endTransaction(KisPostExecutionUndoAdapter *undoAdapter); /** * Finishes a transaction and returns a pointer to its undo command */ KUndo2Command* endAndTakeTransaction(); /** * Finish the transaction and delete it's undo information. * NOTE: Be careful, because all the previous transactions * will become non-undoable after execution of this method. */ void deleteTransaction(); /// continue a transaction started somewhere else void putTransaction(KisTransaction* transaction); /// take transaction out of the reach of KisPainter KisTransaction* takeTransaction(); /// Returns the current paint device. const KisPaintDeviceSP device() const; KisPaintDeviceSP device(); /** * Blast a region of srcWidth @param srcWidth and srcHeight @param srcHeight from @param * srcDev onto the current paint device. @param srcX and @param srcY set the x and y * positions of the origin top-left corner, @param dstX and @param dstY those of * the destination. * Any pixel read outside the limits of @param srcDev will return the * default pixel, this is a property of \ref KisPaintDevice. * * @param dstX the destination x-coordinate * @param dstY the destination y-coordinate * @param srcDev the source device * @param srcX the source x-coordinate * @param srcY the source y-coordinate * @param srcWidth the width of the region to be manipulated * @param srcHeight the height of the region to be manipulated */ void bitBlt(qint32 dstX, qint32 dstY, const KisPaintDeviceSP srcDev, qint32 srcX, qint32 srcY, qint32 srcWidth, qint32 srcHeight); /** * Convenience method that uses QPoint and QRect. * * @param pos the destination coordinate, it replaces @p dstX and @p dstY. * @param srcDev the source device. * @param srcRect the rectangle describing the area to blast from @p srcDev into the current paint device. * @p srcRect replaces @p srcX, @p srcY, @p srcWidth and @p srcHeight. * */ void bitBlt(const QPoint & pos, const KisPaintDeviceSP srcDev, const QRect & srcRect); /** * The same as @ref bitBlt() but reads data from oldData() part of the device * * @param dstX the destination x-coordinate * @param dstY the destination y-coordinate * @param srcDev the source device * @param srcX the source x-coordinate * @param srcY the source y-coordinate * @param srcWidth the width of the region to be manipulated * @param srcHeight the height of the region to be manipulated */ void bitBltOldData(qint32 dstX, qint32 dstY, const KisPaintDeviceSP srcDev, qint32 srcX, qint32 srcY, qint32 srcWidth, qint32 srcHeight); /** * Convenience method that uses QPoint and QRect. * * @param pos the destination coordinate, it replaces @p dstX and @p dstY. * @param srcDev the source device. * @param srcRect the rectangle describing the area to blast from @param srcDev into the current paint device. * @p srcRect replaces @p srcX, @p srcY, @p srcWidth and @p srcHeight. * */ void bitBltOldData(const QPoint & pos, const KisPaintDeviceSP srcDev, const QRect & srcRect); /** * Blasts a @param selection of srcWidth @param srcWidth and srcHeight @param srcHeight * of @param srcDev on the current paint device. There is parameters * to control where the area begins in each distinct device, explained below. * @param selection can be used as a mask to shape @param srcDev to * something interesting in the same step it is rendered to the current * paint device. @param selection 's colorspace must be alpha8 (the * colorspace for selections/transparency), the rectangle formed by * @param selX, @param selY, @param srcWidth and @param srcHeight must not go * beyond its limits, and they must be different from zero. * @param selection and KisPainter's selection (the user selection) are * fused together through the composite operation COMPOSITE_MULT. * Any pixel read outside the limits of @param srcDev will return the * default pixel, this is a property of \ref KisPaintDevice. * * @param dstX the destination x-coordinate * @param dstY the destination y-coordinate * @param srcDev the source device * @param selection the custom selection to apply on the source device * @param selX the selection x-coordinate * @param selY the selection y-coordinate * @param srcX the source x-coordinate * @param srcY the source y-coordinate * @param srcWidth the width of the region to be manipulated * @param srcHeight the height of the region to be manipulated * */ void bitBltWithFixedSelection(qint32 dstX, qint32 dstY, const KisPaintDeviceSP srcDev, const KisFixedPaintDeviceSP selection, qint32 selX, qint32 selY, qint32 srcX, qint32 srcY, qint32 srcWidth, qint32 srcHeight); /** * Convenience method that assumes @p selX, @p selY, @p srcX and @p srcY are * equal to 0. Best used when @p selection and the desired area of @p srcDev have exactly * the same dimensions and are specially made for each other. * * @param dstX the destination x-coordinate * @param dstY the destination y-coordinate * @param srcDev the source device * @param selection the custom selection to apply on the source device * @param srcWidth the width of the region to be manipulated * @param srcHeight the height of the region to be manipulated */ void bitBltWithFixedSelection(qint32 dstX, qint32 dstY, const KisPaintDeviceSP srcDev, const KisFixedPaintDeviceSP selection, qint32 srcWidth, qint32 srcHeight); /** * Blast a region of srcWidth @p srcWidth and srcHeight @p srcHeight from @p srcDev onto the current * paint device. @p srcX and @p srcY set the x and y positions of the * origin top-left corner, @p dstX and @p dstY those of the destination. * @p srcDev is a @ref KisFixedPaintDevice : this means that @p srcDev must have the same * colorspace as the destination device. * * @param dstX the destination x-coordinate * @param dstY the destination y-coordinate * @param srcDev the source device * @param srcX the source x-coordinate * @param srcY the source y-coordinate * @param srcWidth the width of the region to be manipulated * @param srcHeight the height of the region to be manipulated */ void bltFixed(qint32 dstX, qint32 dstY, const KisFixedPaintDeviceSP srcDev, qint32 srcX, qint32 srcY, qint32 srcWidth, qint32 srcHeight); /** * Render the area \p rc from \p srcDevices on the destination device. * If \p rc doesn't cross the device's rect, then the device is not * rendered at all. */ void bltFixed(const QRect &rc, const QList allSrcDevices); /** * Convenience method that uses QPoint and QRect. * * @param pos the destination coordinate, it replaces @p dstX and @p dstY. * @param srcDev the source device. * @param srcRect the rectangle describing the area to blast from @p srcDev into the current paint device. * @param srcRect replaces @p srcX, @p srcY, @p srcWidth and @p srcHeight. * */ void bltFixed(const QPoint & pos, const KisFixedPaintDeviceSP srcDev, const QRect & srcRect); /** * Blasts a @p selection of srcWidth @p srcWidth and srcHeight @p srcHeight * of @p srcDev on the current paint device. There is parameters to control * the top-left corner of the area in each respective paint device (@p dstX, * @p dstY, @p srcX, @p srcY). * @p selection can be used as a mask to shape @p srcDev to something * interesting in the same step it is rendered to the current paint device. * @p srcDev is a @ref KisFixedPaintDevice : this means that @p srcDev * must have the same colorspace as the destination device. * @p selection 's colorspace must be alpha8 (the colorspace for * selections/transparency). * The rectangle formed by the respective top-left coordinates of each device * and @p srcWidth and @p srcHeight must not go beyond their limits, and * they must be different from zero. * @p selection and KisPainter's selection (the user selection) are * fused together through the composite operation COMPOSITE_MULT. * * @param dstX the destination x-coordinate * @param dstY the destination y-coordinate * @param srcDev the source device * @param selection the selection stored in fixed device * @param selX the selection x-coordinate * @param selY the selection y-coordinate * @param srcX the source x-coordinate * @param srcY the source y-coordinate * @param srcWidth the width of the region to be manipulated * @param srcHeight the height of the region to be manipulated */ void bltFixedWithFixedSelection(qint32 dstX, qint32 dstY, const KisFixedPaintDeviceSP srcDev, const KisFixedPaintDeviceSP selection, qint32 selX, qint32 selY, qint32 srcX, qint32 srcY, quint32 srcWidth, quint32 srcHeight); /** * Convenience method that assumes @p selX, @p selY, @p srcX and @p srcY are * equal to 0. Best used when @p selection and @p srcDev have exactly the same * dimensions and are specially made for each other. * * @param dstX the destination x-coordinate * @param dstY the destination y-coordinate * @param srcDev the source device * @param selection the custom selection to apply on the source device * @param srcWidth the width of the region to be manipulated * @param srcHeight the height of the region to be manipulated */ void bltFixedWithFixedSelection(qint32 dstX, qint32 dstY, const KisFixedPaintDeviceSP srcDev, const KisFixedPaintDeviceSP selection, quint32 srcWidth, quint32 srcHeight); /** * fills a region of width @p width and height @p height of the current * paint device with the color @p color. @p x and @p y set the x and y positions of the * origin top-left corner. * * @param x the destination x-coordinate * @param y the destination y-coordinate * @param width the width of the region to be manipulated * @param height the height of the region to be manipulated * @param color the color the area is filled with */ void fill(qint32 x, qint32 y, qint32 width, qint32 height, const KoColor& color); /** * First you need to setup the painter with setMirrorInformation, * then these set of methods provide way to render the devices mirrored * according the axesCenter vertically or horizontally or both. * * @param rc rectangle area covered by dab * @param dab this device will be mirrored in-place, it means that it will be changed */ void renderMirrorMask(QRect rc, KisFixedPaintDeviceSP dab); void renderMirrorMask(QRect rc, KisFixedPaintDeviceSP dab, KisFixedPaintDeviceSP mask); void renderMirrorMask(QRect rc, KisPaintDeviceSP dab); void renderMirrorMask(QRect rc, KisPaintDeviceSP dab, int sx, int sy, KisFixedPaintDeviceSP mask); /** * Convenience method for renderMirrorMask(), allows to choose whether * we need to preserve out dab or do the transformations in-place. * * @param rc rectangle area covered by dab * @param dab the device to render * @param preserveDab states whether a temporary device should be * created to do the transformations */ void renderMirrorMaskSafe(QRect rc, KisFixedPaintDeviceSP dab, bool preserveDab); /** * Convenience method for renderMirrorMask(), allows to choose whether * we need to preserve our fixed mask or do the transformations in-place. * * @param rc rectangular area covered by dab * @param dab the device to render * @param sx x coordinate of the top left corner of the area * @param sy y coordinate of the top left corner of the area * @param mask mask to use for rendering * @param preserveMask states whether a temporary device should be * created to do the transformations */ void renderMirrorMaskSafe(QRect rc, KisPaintDeviceSP dab, int sx, int sy, KisFixedPaintDeviceSP mask, bool preserveMask); /** * A complex method that re-renders a dab on an \p rc area. * The \p rc area and all the dedicated mirroring areas are cleared * before the painting, so this method should be used by paintops * which do not update the canvas incrementally, but instead * regenerate some internal cache \p dab with the COMPOSITE_COPY op. * * \see KisExperimentPaintOp */ void renderDabWithMirroringNonIncremental(QRect rc, KisPaintDeviceSP dab); /** * @return true if the painter has some rects marked as dirty * @see takeDirtyRegion(), addDirtyRect() */ bool hasDirtyRegion() const; /** * The methods in this class do not tell the paintdevice to update, but they calculate the * dirty area. This method returns this dirty area and resets it. */ QVector takeDirtyRegion(); /** * Paint a line that connects the dots in points */ void paintPolyline(const QVector &points, int index = 0, int numPoints = -1); /** * Draw a line between pos1 and pos2 using the currently set brush and color. * If savedDist is less than zero, the brush is painted at pos1 before being * painted along the line using the spacing setting. * @return the drag distance, that is the remains of the distance between p1 and p2 not covered * because the currently set brush has a spacing greater than that distance. */ void paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance); /** * Draw a Bezier curve between @p pi1 and @p pi2 using control points @p control1 and @p control2. * If savedDist is less than zero, the brush is painted at pos1 before being * painted along the curve using the spacing setting. * @return the drag distance, that is the remains of the distance between @p pi1 and @p pi2 not covered * because the currently set brush has a spacing greater than that distance. */ void paintBezierCurve(const KisPaintInformation &pi1, const QPointF &control1, const QPointF &control2, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance); /** * Fill the given vector points with the points needed to draw the Bezier curve between * @p pos1 and @p pos2 using control points @p control1 and @p control2, excluding the final pos2. */ void getBezierCurvePoints(const QPointF &pos1, const QPointF &control1, const QPointF &control2, const QPointF &pos2, vQPointF& points) const; /** * Paint a rectangle. * @param rect the rectangle to paint. */ void paintRect(const QRectF &rect); /** * Paint a rectangle. * * @param x x coordinate of the top-left corner * @param y y coordinate of the top-left corner * @param w the rectangle width * @param h the rectangle height */ void paintRect(const qreal x, const qreal y, const qreal w, const qreal h); /** * Paint the ellipse that fills the given rectangle. * * @param rect the rectangle containing the ellipse to paint. */ void paintEllipse(const QRectF &rect); /** * Paint the ellipse that fills the given rectangle. * * @param x x coordinate of the top-left corner * @param y y coordinate of the top-left corner * @param w the rectangle width * @param h the rectangle height */ void paintEllipse(const qreal x, const qreal y, const qreal w, const qreal h); /** * Paint the polygon with the points given in points. It automatically closes the polygon * by drawing the line from the last point to the first. */ void paintPolygon(const vQPointF& points); /** Draw a spot at pos using the currently set paint op, brush and color */ void paintAt(const KisPaintInformation &pos, KisDistanceInformation *savedDist); /** * Stroke the given QPainterPath. */ void paintPainterPath(const QPainterPath& path); /** * Fills the area enclosed by the given QPainterPath * Convenience method for fillPainterPath(path, rect) */ void fillPainterPath(const QPainterPath& path); /** * Fills the portion of an area enclosed by the given QPainterPath * * \param path the portion of the path to fill * \param requestedRect the rectangle containing the area */ void fillPainterPath(const QPainterPath& path, const QRect &requestedRect); /** * Draw the path using the Pen * * if \p requestedRect is null, the entire path is painted */ void drawPainterPath(const QPainterPath& path, const QPen& pen, const QRect &requestedRect); // convenience overload void drawPainterPath(const QPainterPath& path, const QPen& pen); /** * paint an unstroked one-pixel wide line from specified start position to the * specified end position. * */ void drawLine(const QPointF & start, const QPointF & end); /** * paint an unstroked line with thickness from specified start position to the * specified end position. Scanline algorithm is used. */ void drawLine(const QPointF &start, const QPointF &end, qreal width, bool antialias); /** * paints an unstroked, aliased one-pixel line using the DDA algorithm from specified start position to the * specified end position. * */ void drawDDALine(const QPointF & start, const QPointF & end); /** * Paint an unstroked, wobbly one-pixel wide line from the specified start to the specified * end position. * */ void drawWobblyLine(const QPointF & start, const QPointF & end); /** * Paint an unstroked, anti-aliased one-pixel wide line from the specified start to the specified * end position using the Wu algorithm */ void drawWuLine(const QPointF & start, const QPointF & end); /** * Paint an unstroked wide line from the specified start to the specified * end position with width varying from @p start at the start to @p end at * the end. * * XXX: the width should be set in doubles, not integers. */ void drawThickLine(const QPointF & start, const QPointF & end, int startWidth, int endWidth); /** * Set the channelflags: a bit array where true means that the * channel corresponding in position with the bit will be read * by the operation, and false means that it will not be affected. * * An empty channelFlags parameter means that all channels are * affected. * * @param channelFlags the bit array that masks the source channels; only * the channels where the corresponding bit is true will will be * composited onto the destination device. */ void setChannelFlags(QBitArray channelFlags); /// @return the channel flags QBitArray channelFlags(); /** * Set the paintop preset to use. If @p image is given, * the paintop will be created using this image as parameter. * Some paintops really want to know about the image they work * for, e.g. the clone paintop. */ void setPaintOpPreset(KisPaintOpPresetSP preset, KisNodeSP node, KisImageSP image); /// Return the paintop preset KisPaintOpPresetSP preset() const; /** * Return the active paintop (which is created based on the specified preset and * will be deleted as soon as the KisPainter instance dies). */ KisPaintOp* paintOp() const; void setMirrorInformation(const QPointF &axesCenter, bool mirrorHorizontally, bool mirrorVertically); void copyMirrorInformationFrom(const KisPainter *other); /** * Returns whether the mirroring methods will do any * work when called */ bool hasMirroring() const; /** * Indicates if horizontal mirroring mode is activated */ bool hasHorizontalMirroring() const; /** * Indicates if vertical mirroring mode is activated */ bool hasVerticalMirroring() const; /** * Mirror \p rc in the requested \p direction around the center point defined * in the painter. */ void mirrorRect(Qt::Orientation direction, QRect *rc) const; /** * Mirror \p dab in the requested direction around the center point defined * in the painter. The dab's offset is adjusted automatically. */ void mirrorDab(Qt::Orientation direction, KisRenderedDab *dab, bool skipMirrorPixels = false) const; /** * Calculate the list of the mirrored rects that will be painted on the * the canvas when calling renderMirrorMask() at al */ const QVector calculateAllMirroredRects(const QRect &rc); /** * Calculate the list of the mirrored points according to the current * mirroring configuration. */ const QVector calculateAllMirroredPoints(const QPointF &pos); /** * Calculate the list of the mirrored point pairs according to the current * mirroring configuration. */ const QVector> calculateAllMirroredPoints(const QPair &pair); /// Set the current pattern void setPattern(const KoPatternSP pattern); /// Returns the currently set pattern const KoPatternSP pattern() const; /** * Set the color that will be used to paint with, and convert it * to the color space of the current paint device. */ void setPaintColor(const KoColor& color); /// Returns the color that will be used to paint with const KoColor &paintColor() const; /** * Set the current background color, and convert it * to the color space of the current paint device. */ void setBackgroundColor(const KoColor& color); /// Returns the current background color const KoColor &backgroundColor() const; /// Set the current generator (a generator can be used to fill an area void setGenerator(KisFilterConfigurationSP generator); /// @return the current generator configuration const KisFilterConfigurationSP generator() const; /// This enum contains the styles with which we can fill things like polygons and ellipses enum FillStyle { FillStyleNone, FillStyleForegroundColor, FillStyleBackgroundColor, FillStylePattern, FillStyleGenerator }; /// Set the current style with which to fill void setFillStyle(FillStyle fillStyle); /// Returns the current fill style FillStyle fillStyle() const; + /// Set the transform on the pattern. + void setPatternTransform(QTransform transform); + + /// get the current transform on the pattern. + QTransform patternTransform(); + /// Set whether a polygon's filled area should be anti-aliased or not. The default is true. void setAntiAliasPolygonFill(bool antiAliasPolygonFill); /// Return whether a polygon's filled area should be anti-aliased or not bool antiAliasPolygonFill(); /// The style of the brush stroke around polygons and so enum StrokeStyle { StrokeStyleNone, StrokeStyleBrush }; /// Set the current brush stroke style void setStrokeStyle(StrokeStyle strokeStyle); /// Returns the current brush stroke style StrokeStyle strokeStyle() const; void setFlow(quint8 flow); quint8 flow() const; /** * Sets the opacity of the painting and recalculates the * mean opacity of the stroke. This mean value is used to * make ALPHA_DARKEN painting look correct */ void setOpacityUpdateAverage(quint8 opacity); /** * Sets average opacity, that is used to make ALPHA_DARKEN painting look correct */ void setAverageOpacity(qreal averageOpacity); /** * Calculate average opacity value after painting a single dab with \p opacity */ static qreal blendAverageOpacity(qreal opacity, qreal averageOpacity); /// Set the opacity which is used in painting (like filling polygons) void setOpacity(quint8 opacity); /// Returns the opacity that is used in painting quint8 opacity() const; /// Set the composite op for this painter void setCompositeOp(const KoCompositeOp * op); const KoCompositeOp * compositeOp(); /// Set the composite op for this painter by string. /// Note: the colorspace must be set previously! void setCompositeOp(const QString& op); /** * Add \p r to the current set of dirty rects */ void addDirtyRect(const QRect &r); /** * Add \p rects to the current set of dirty rects */ void addDirtyRects(const QVector &rects); /** * Reset the selection to the given selection. All painter actions will be * masked by the specified selection. */ void setSelection(KisSelectionSP selection); /** * @return the selection set on this painter. */ KisSelectionSP selection(); void setGradient(const KoAbstractGradientSP gradient); const KoAbstractGradientSP gradient() const; /** * Set the size of the tile in fillPainterPath, useful when optimizing the use of fillPainterPath * e.g. Spray paintop uses more small tiles, although selections uses bigger tiles. QImage::fill * is quite expensive so with smaller images you can save instructions * Default and maximum size is 256x256 image */ void setMaskImageSize(qint32 width, qint32 height); // /** // * If the alpha channel is locked, the alpha values of the paint device we are painting on // * will not change. // */ // void setLockAlpha(bool protect); // bool alphaLocked() const; /** * set the rendering intent in case pixels need to be converted before painting */ void setRenderingIntent(KoColorConversionTransformation::Intent intent); /** * set the conversion flags in case pixels need to be converted before painting */ void setColorConversionFlags(KoColorConversionTransformation::ConversionFlags conversionFlags); /** * Set interface for running asynchronous jobs by paintops. * * NOTE: the painter does *not* own the interface device. It is the responsibility * of the caller to ensure that the interface object is alive during the lifetime * of the painter. */ void setRunnableStrokeJobsInterface(KisRunnableStrokeJobsInterface *interface); /** * Get the interface for running asynchronous jobs. It is used by paintops mostly. */ KisRunnableStrokeJobsInterface* runnableStrokeJobsInterface() const; protected: /// Initialize, set everything to '0' or defaults void init(); /// Fill the polygon defined by points with the fillStyle void fillPolygon(const vQPointF& points, FillStyle fillStyle); private: KisPainter(const KisPainter&); KisPainter& operator=(const KisPainter&); float frac(float value) { float tmp = 0; return modff(value , &tmp); } float invertFrac(float value) { float tmp = 0; return 1.0f - modff(value , &tmp); } protected: KoUpdater * progressUpdater(); private: template void bitBltImpl(qint32 dstX, qint32 dstY, const KisPaintDeviceSP srcDev, qint32 srcX, qint32 srcY, qint32 srcWidth, qint32 srcHeight); inline void compositeOnePixel(quint8 *dst, const KoColor &color); private: struct Private; Private* const d; }; #endif // KIS_PAINTER_H_ diff --git a/libs/image/kis_painter_p.h b/libs/image/kis_painter_p.h index 27b95019af..6f67175165 100644 --- a/libs/image/kis_painter_p.h +++ b/libs/image/kis_painter_p.h @@ -1,107 +1,108 @@ /* * Copyright (c) 2017 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KISPAINTERPRIVATE_H #define KISPAINTERPRIVATE_H #include #include #include #include #include "kis_paintop.h" #include "kis_selection.h" #include "kis_fill_painter.h" #include "kis_painter.h" #include "kis_paintop_preset.h" #include struct Q_DECL_HIDDEN KisPainter::Private { Private(KisPainter *_q) : q(_q) {} Private(KisPainter *_q, const KoColorSpace *cs) : q(_q), paintColor(cs), backgroundColor(cs) {} KisPainter *q; KisPaintDeviceSP device; KisSelectionSP selection; KisTransaction* transaction; KoUpdater* progressUpdater; QVector dirtyRects; KisPaintOp* paintOp; KoColor paintColor; KoColor backgroundColor; KoColor customColor; KisFilterConfigurationSP generator; KisPaintLayer* sourceLayer; FillStyle fillStyle; StrokeStyle strokeStyle; bool antiAliasPolygonFill; KoPatternSP pattern; QPointF duplicateOffset; quint32 pixelSize; const KoColorSpace* colorSpace; KoColorProfile* profile; const KoCompositeOp* compositeOp; KoAbstractGradientSP gradient; KisPaintOpPresetSP paintOpPreset; QImage polygonMaskImage; QPainter* maskPainter; KisFillPainter* fillPainter; KisPaintDeviceSP polygon; qint32 maskImageWidth; qint32 maskImageHeight; QPointF axesCenter; bool mirrorHorizontally; bool mirrorVertically; bool isOpacityUnit; // TODO: move into ParameterInfo KoCompositeOp::ParameterInfo paramInfo; KoColorConversionTransformation::Intent renderingIntent; KoColorConversionTransformation::ConversionFlags conversionFlags; KisRunnableStrokeJobsInterface *runnableStrokeJobsInterface = 0; QScopedPointer fakeRunnableStrokeJobsInterface; + QTransform patternTransform; bool tryReduceSourceRect(const KisPaintDevice *srcDev, QRect *srcRect, qint32 *srcX, qint32 *srcY, qint32 *srcWidth, qint32 *srcHeight, qint32 *dstX, qint32 *dstY); void fillPainterPathImpl(const QPainterPath& path, const QRect &requestedRect); void applyDevice(const QRect &applyRect, const KisRenderedDab &dab, KisRandomAccessorSP dstIt, const KoColorSpace *srcColorSpace, KoCompositeOp::ParameterInfo &localParamInfo); void applyDeviceWithSelection(const QRect &applyRect, const KisRenderedDab &dab, KisRandomAccessorSP dstIt, KisRandomConstAccessorSP maskIt, const KoColorSpace *srcColorSpace, KoCompositeOp::ParameterInfo &localParamInfo); template QVector calculateMirroredObjects(const T &object); }; #endif // KISPAINTERPRIVATE_H diff --git a/libs/image/kis_perspectivetransform_worker.cpp b/libs/image/kis_perspectivetransform_worker.cpp index ef1e5724b7..eb03d96ad8 100644 --- a/libs/image/kis_perspectivetransform_worker.cpp +++ b/libs/image/kis_perspectivetransform_worker.cpp @@ -1,192 +1,205 @@ /* * This file is part of Krita * * Copyright (c) 2006 Cyrille Berger * Copyright (c) 2009 Edward Apap * Copyright (c) 2010 Marc Pegon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public 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_perspectivetransform_worker.h" #include #include #include #include #include #include #include #include "kis_paint_device.h" #include "kis_perspective_math.h" #include "kis_random_accessor_ng.h" #include "kis_random_sub_accessor.h" #include "kis_selection.h" #include #include "krita_utils.h" #include "kis_progress_update_helper.h" #include "kis_painter.h" #include "kis_image.h" KisPerspectiveTransformWorker::KisPerspectiveTransformWorker(KisPaintDeviceSP dev, QPointF center, double aX, double aY, double distance, KoUpdaterPtr progress) : m_dev(dev), m_progressUpdater(progress) { QMatrix4x4 m; m.rotate(180. * aX / M_PI, QVector3D(1, 0, 0)); m.rotate(180. * aY / M_PI, QVector3D(0, 1, 0)); QTransform project = m.toTransform(distance); QTransform t = QTransform::fromTranslate(center.x(), center.y()); QTransform forwardTransform = t.inverted() * project * t; init(forwardTransform); } KisPerspectiveTransformWorker::KisPerspectiveTransformWorker(KisPaintDeviceSP dev, const QTransform &transform, KoUpdaterPtr progress) : m_dev(dev), m_progressUpdater(progress) { init(transform); } void KisPerspectiveTransformWorker::fillParams(const QRectF &srcRect, const QRect &dstBaseClipRect, KisRegion *dstRegion, QPolygonF *dstClipPolygon) { QPolygonF bounds = srcRect; QPolygonF newBounds = m_forwardTransform.map(bounds); newBounds = newBounds.intersected(QRectF(dstBaseClipRect)); QPainterPath path; path.addPolygon(newBounds); *dstRegion = KritaUtils::splitPath(path); *dstClipPolygon = newBounds; } void KisPerspectiveTransformWorker::init(const QTransform &transform) { m_isIdentity = transform.isIdentity(); m_forwardTransform = transform; m_backwardTransform = transform.inverted(); if (m_dev) { m_srcRect = m_dev->exactBounds(); QPolygonF dstClipPolygonUnused; fillParams(m_srcRect, m_dev->defaultBounds()->bounds(), &m_dstRegion, &dstClipPolygonUnused); } } KisPerspectiveTransformWorker::~KisPerspectiveTransformWorker() { } void KisPerspectiveTransformWorker::setForwardTransform(const QTransform &transform) { init(transform); } void KisPerspectiveTransformWorker::run() { KIS_ASSERT_RECOVER_RETURN(m_dev); if (m_isIdentity) return; KisPaintDeviceSP cloneDevice = new KisPaintDevice(*m_dev.data()); // Clear the destination device, since all the tiles are already // shared with cloneDevice m_dev->clear(); KIS_ASSERT_RECOVER_NOOP(!m_isIdentity); KisProgressUpdateHelper progressHelper(m_progressUpdater, 100, m_dstRegion.rectCount()); KisRandomSubAccessorSP srcAcc = cloneDevice->createRandomSubAccessor(); KisRandomAccessorSP accessor = m_dev->createRandomAccessorNG(); Q_FOREACH (const QRect &rect, m_dstRegion.rects()) { for (int y = rect.y(); y < rect.y() + rect.height(); ++y) { for (int x = rect.x(); x < rect.x() + rect.width(); ++x) { QPointF dstPoint(x, y); QPointF srcPoint = m_backwardTransform.map(dstPoint); if (m_srcRect.contains(srcPoint)) { accessor->moveTo(dstPoint.x(), dstPoint.y()); srcAcc->moveTo(srcPoint.x(), srcPoint.y()); srcAcc->sampledOldRawData(accessor->rawData()); } } } progressHelper.step(); } } void KisPerspectiveTransformWorker::runPartialDst(KisPaintDeviceSP srcDev, KisPaintDeviceSP dstDev, const QRect &dstRect) { - if (m_isIdentity) { - KisPainter::copyAreaOptimizedOldData(dstRect.topLeft(), srcDev, dstDev, dstRect); - return; - } QRectF srcClipRect = srcDev->exactBounds(); if (srcClipRect.isEmpty()) return; + if (m_isIdentity) { + + if (srcDev->defaultBounds()->wrapAroundMode()) { + KisProgressUpdateHelper progressHelper(m_progressUpdater, 100, dstRect.height()/ srcClipRect.height()); + for (int y = dstRect.y(); y < dstRect.y() + dstRect.height(); y+=srcClipRect.height()) { + for (int x = dstRect.x(); x < dstRect.x() + dstRect.width(); x+=srcClipRect.width()) { + KisPainter::copyAreaOptimizedOldData(QPoint(x, y), srcDev, dstDev, srcClipRect.toRect()); + } + progressHelper.step(); + } + return; + } else { + KisPainter::copyAreaOptimizedOldData(dstRect.topLeft(), srcDev, dstDev, dstRect); + return; + } + } + KisProgressUpdateHelper progressHelper(m_progressUpdater, 100, dstRect.height()); KisRandomSubAccessorSP srcAcc = srcDev->createRandomSubAccessor(); KisRandomAccessorSP accessor = dstDev->createRandomAccessorNG(); for (int y = dstRect.y(); y < dstRect.y() + dstRect.height(); ++y) { for (int x = dstRect.x(); x < dstRect.x() + dstRect.width(); ++x) { QPointF dstPoint(x, y); QPointF srcPoint = m_backwardTransform.map(dstPoint); - if (srcClipRect.contains(srcPoint)) { + if (srcClipRect.contains(srcPoint) || srcDev->defaultBounds()->wrapAroundMode()) { accessor->moveTo(dstPoint.x(), dstPoint.y()); srcAcc->moveTo(srcPoint.x(), srcPoint.y()); srcAcc->sampledOldRawData(accessor->rawData()); } } progressHelper.step(); } } QTransform KisPerspectiveTransformWorker::forwardTransform() const { return m_forwardTransform; } QTransform KisPerspectiveTransformWorker::backwardTransform() const { return m_backwardTransform; } diff --git a/libs/image/kis_processing_applicator.cpp b/libs/image/kis_processing_applicator.cpp index 01e94b1161..0f41217b76 100644 --- a/libs/image/kis_processing_applicator.cpp +++ b/libs/image/kis_processing_applicator.cpp @@ -1,362 +1,366 @@ /* * Copyright (c) 2011 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_processing_applicator.h" #include "kis_image.h" #include "kis_paint_layer.h" #include "kis_node.h" #include "kis_clone_layer.h" #include "kis_processing_visitor.h" #include "commands_new/kis_processing_command.h" #include "kis_stroke_strategy_undo_command_based.h" #include "kis_layer_utils.h" #include "kis_command_utils.h" #include "kis_image_signal_router.h" class DisableUIUpdatesCommand : public KisCommandUtils::FlipFlopCommand { public: DisableUIUpdatesCommand(KisImageWSP image, bool finalUpdate) : FlipFlopCommand(finalUpdate), m_image(image) { } void partA() override { m_image->disableUIUpdates(); } void partB() override { m_image->enableUIUpdates(); } private: KisImageWSP m_image; }; class UpdateCommand : public KisCommandUtils::FlipFlopCommand { public: UpdateCommand(KisImageWSP image, KisNodeSP node, KisProcessingApplicator::ProcessingFlags flags, - bool finalUpdate, bool multiframeApplication) - : FlipFlopCommand(finalUpdate), + State initialState, QSharedPointer sharedAllFramesToken) + : FlipFlopCommand(initialState), m_image(image), m_node(node), m_flags(flags), - m_multiframeApplication(multiframeApplication) + m_sharedAllFramesToken(sharedAllFramesToken) { } private: void partA() override { /** * We disable all non-centralized updates here. Everything * should be done by this command's explicit updates. * * If you still need third-party updates work, please add a * flag to the applicator. */ m_image->disableDirtyRequests(); } void partB() override { m_image->enableDirtyRequests(); - if (m_multiframeApplication) { + if (*m_sharedAllFramesToken) { KisLayerUtils::recursiveApplyNodes(m_image->root(), [](KisNodeSP node){ KisPaintLayer* paintLayer = qobject_cast(node.data()); if (paintLayer && paintLayer->onionSkinEnabled()) { paintLayer->flushOnionSkinCache(); } }); } if (!m_flags.testFlag(KisProcessingApplicator::NO_IMAGE_UPDATES)) { if(m_flags.testFlag(KisProcessingApplicator::RECURSIVE)) { m_image->refreshGraphAsync(m_node); } m_node->setDirty(m_image->bounds()); updateClones(m_node); } } void updateClones(KisNodeSP node) { // simple tail-recursive iteration KisNodeSP prevNode = node->lastChild(); while(prevNode) { updateClones(prevNode); prevNode = prevNode->prevSibling(); } KisLayer *layer = qobject_cast(m_node.data()); if(layer && layer->hasClones()) { Q_FOREACH (KisCloneLayerSP clone, layer->registeredClones()) { if(!clone) continue; QPoint offset(clone->x(), clone->y()); QRegion dirtyRegion(m_image->bounds()); dirtyRegion -= m_image->bounds().translated(offset); clone->setDirty(KisRegion::fromQRegion(dirtyRegion)); } } } private: KisImageWSP m_image; KisNodeSP m_node; KisProcessingApplicator::ProcessingFlags m_flags; - bool m_multiframeApplication; + QSharedPointer m_sharedAllFramesToken; }; class EmitImageSignalsCommand : public KisCommandUtils::FlipFlopCommand { public: EmitImageSignalsCommand(KisImageWSP image, KisImageSignalVector emitSignals, bool finalUpdate) : FlipFlopCommand(finalUpdate), m_image(image), m_emitSignals(emitSignals) { } void partB() override { if (getState() == State::FINALIZING) { doUpdate(m_emitSignals); } else { KisImageSignalVector reverseSignals; KisImageSignalVector::iterator i = m_emitSignals.end(); while (i != m_emitSignals.begin()) { --i; reverseSignals.append(i->inverted()); } doUpdate(reverseSignals); } } private: void doUpdate(KisImageSignalVector emitSignals) { Q_FOREACH (KisImageSignalType type, emitSignals) { m_image->signalRouter()->emitNotification(type); } } private: KisImageWSP m_image; KisImageSignalVector m_emitSignals; }; KisProcessingApplicator::KisProcessingApplicator(KisImageWSP image, KisNodeSP node, ProcessingFlags flags, KisImageSignalVector emitSignals, const KUndo2MagicString &name, KUndo2CommandExtraData *extraData, int macroId) : m_image(image), m_node(node), m_flags(flags), m_emitSignals(emitSignals), m_finalSignalsEmitted(false), - m_appliedVisitorToAllFrames(false) + m_sharedAllFramesToken(new bool(false)) { KisStrokeStrategyUndoCommandBased *strategy = new KisStrokeStrategyUndoCommandBased(name, false, m_image.data()); if (m_flags.testFlag(SUPPORTS_WRAPAROUND_MODE)) { strategy->setSupportsWrapAroundMode(true); } if (extraData) { strategy->setCommandExtraData(extraData); } strategy->setMacroId(macroId); m_strokeId = m_image->startStroke(strategy); if(!m_emitSignals.isEmpty()) { applyCommand(new EmitImageSignalsCommand(m_image, m_emitSignals, false), KisStrokeJobData::BARRIER); } if(m_flags.testFlag(NO_UI_UPDATES)) { applyCommand(new DisableUIUpdatesCommand(m_image, false), KisStrokeJobData::BARRIER); } if (m_node) { - applyCommand(new UpdateCommand(m_image, m_node, m_flags, false, true)); + applyCommand(new UpdateCommand(m_image, m_node, m_flags, + UpdateCommand::INITIALIZING, + m_sharedAllFramesToken)); } } KisProcessingApplicator::~KisProcessingApplicator() { } void KisProcessingApplicator::applyVisitor(KisProcessingVisitorSP visitor, KisStrokeJobData::Sequentiality sequentiality, KisStrokeJobData::Exclusivity exclusivity) { KUndo2Command *initCommand = visitor->createInitCommand(); if (initCommand) { applyCommand(initCommand, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::NORMAL); } if(!m_flags.testFlag(RECURSIVE)) { applyCommand(new KisProcessingCommand(visitor, m_node), sequentiality, exclusivity); } else { visitRecursively(m_node, visitor, sequentiality, exclusivity); } } void KisProcessingApplicator::applyVisitorAllFrames(KisProcessingVisitorSP visitor, KisStrokeJobData::Sequentiality sequentiality, KisStrokeJobData::Exclusivity exclusivity) { - m_appliedVisitorToAllFrames = true; + *m_sharedAllFramesToken = true; KUndo2Command *initCommand = visitor->createInitCommand(); if (initCommand) { applyCommand(initCommand, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::NORMAL); } KisLayerUtils::FrameJobs jobs; // TODO: implement a nonrecursive case when !m_flags.testFlag(RECURSIVE) // (such case is not yet used anywhere) KIS_SAFE_ASSERT_RECOVER_NOOP(m_flags.testFlag(RECURSIVE)); KisLayerUtils::updateFrameJobsRecursive(&jobs, m_node); if (jobs.isEmpty()) { applyVisitor(visitor, sequentiality, exclusivity); return; } KisLayerUtils::FrameJobs::const_iterator it = jobs.constBegin(); KisLayerUtils::FrameJobs::const_iterator end = jobs.constEnd(); KisLayerUtils::SwitchFrameCommand::SharedStorageSP switchFrameStorage( new KisLayerUtils::SwitchFrameCommand::SharedStorage()); for (; it != end; ++it) { const int frame = it.key(); const QSet &nodes = it.value(); applyCommand(new KisLayerUtils::SwitchFrameCommand(m_image, frame, false, switchFrameStorage), KisStrokeJobData::BARRIER, KisStrokeJobData::EXCLUSIVE); foreach (KisNodeSP node, nodes) { applyCommand(new KisProcessingCommand(visitor, node), sequentiality, exclusivity); } applyCommand(new KisLayerUtils::SwitchFrameCommand(m_image, frame, true, switchFrameStorage), KisStrokeJobData::BARRIER, KisStrokeJobData::EXCLUSIVE); } } void KisProcessingApplicator::visitRecursively(KisNodeSP node, KisProcessingVisitorSP visitor, KisStrokeJobData::Sequentiality sequentiality, KisStrokeJobData::Exclusivity exclusivity) { // simple tail-recursive iteration KisNodeSP prevNode = node->lastChild(); while(prevNode) { visitRecursively(prevNode, visitor, sequentiality, exclusivity); prevNode = prevNode->prevSibling(); } applyCommand(new KisProcessingCommand(visitor, node), sequentiality, exclusivity); } void KisProcessingApplicator::applyCommand(KUndo2Command *command, KisStrokeJobData::Sequentiality sequentiality, KisStrokeJobData::Exclusivity exclusivity) { /* * One should not add commands after the final signals have been * emitted, only end or cancel the stroke */ KIS_ASSERT_RECOVER_RETURN(!m_finalSignalsEmitted); m_image->addJob(m_strokeId, new KisStrokeStrategyUndoCommandBased::Data(KUndo2CommandSP(command), false, sequentiality, exclusivity)); } void KisProcessingApplicator::explicitlyEmitFinalSignals() { KIS_ASSERT_RECOVER_RETURN(!m_finalSignalsEmitted); if (m_node) { - applyCommand(new UpdateCommand(m_image, m_node, m_flags, true, m_appliedVisitorToAllFrames)); + applyCommand(new UpdateCommand(m_image, m_node, m_flags, + UpdateCommand::FINALIZING, + m_sharedAllFramesToken)); } if(m_flags.testFlag(NO_UI_UPDATES)) { applyCommand(new DisableUIUpdatesCommand(m_image, true), KisStrokeJobData::BARRIER); } if(!m_emitSignals.isEmpty()) { applyCommand(new EmitImageSignalsCommand(m_image, m_emitSignals, true), KisStrokeJobData::BARRIER); } // simple consistency check m_finalSignalsEmitted = true; } void KisProcessingApplicator::end() { if (!m_finalSignalsEmitted) { explicitlyEmitFinalSignals(); } m_image->endStroke(m_strokeId); } void KisProcessingApplicator::cancel() { m_image->cancelStroke(m_strokeId); } void KisProcessingApplicator::runSingleCommandStroke(KisImageSP image, KUndo2Command *cmd, KisStrokeJobData::Sequentiality sequentiality, KisStrokeJobData::Exclusivity exclusivity) { KisProcessingApplicator applicator(image, 0, KisProcessingApplicator::NONE, KisImageSignalVector() << ModifiedSignal, cmd->text()); applicator.applyCommand(cmd, sequentiality, exclusivity); applicator.end(); } diff --git a/libs/image/kis_processing_applicator.h b/libs/image/kis_processing_applicator.h index 0695d6ac07..8f6b2f12f5 100644 --- a/libs/image/kis_processing_applicator.h +++ b/libs/image/kis_processing_applicator.h @@ -1,114 +1,114 @@ /* * 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_PROCESSING_APPLICATOR_H #define __KIS_PROCESSING_APPLICATOR_H #include "kritaimage_export.h" #include "kis_types.h" #include "kis_stroke_job_strategy.h" #include "KisImageSignals.h" #include "kundo2magicstring.h" #include "kundo2commandextradata.h" class KRITAIMAGE_EXPORT KisProcessingApplicator { public: enum ProcessingFlag { NONE = 0x0, RECURSIVE = 0x1, NO_UI_UPDATES = 0x2, SUPPORTS_WRAPAROUND_MODE = 0x4, NO_IMAGE_UPDATES = 0x8 }; Q_DECLARE_FLAGS(ProcessingFlags, ProcessingFlag) public: KisProcessingApplicator(KisImageWSP image, KisNodeSP node, ProcessingFlags flags = NONE, KisImageSignalVector emitSignals = KisImageSignalVector(), const KUndo2MagicString &name = KUndo2MagicString(), KUndo2CommandExtraData *extraData = 0, int macroId = -1); ~KisProcessingApplicator(); void applyVisitor(KisProcessingVisitorSP visitor, KisStrokeJobData::Sequentiality sequentiality = KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::Exclusivity exclusivity = KisStrokeJobData::NORMAL); void applyCommand(KUndo2Command *command, KisStrokeJobData::Sequentiality sequentiality = KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::Exclusivity exclusivity = KisStrokeJobData::NORMAL); void applyVisitorAllFrames(KisProcessingVisitorSP visitor, KisStrokeJobData::Sequentiality sequentiality = KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::Exclusivity exclusivity = KisStrokeJobData::NORMAL); /** * This method emits all the final update signals of the stroke * without actually ending the stroke. This can be used for * long-running strokes which are kept open to implement preview * of the actions. * * WARNING: you cannot add new commands/processings after the * final signals has been emitted. You should either call end() or * cancel(). */ void explicitlyEmitFinalSignals(); void end(); void cancel(); /** * @brief runSingleCommandStroke creates a stroke and runs \p cmd in it. * The text() field fo \p cmd is used as a title of the stroke. * @param image the image to run the stroke on * @param cmd the command to be executed * @param sequentiality sequentiality property of the command being executed (see strokes documentation) * @param exclusivity sequentiality property of the command being executed (see strokes documentation) */ static void runSingleCommandStroke(KisImageSP image, KUndo2Command *cmd, KisStrokeJobData::Sequentiality sequentiality = KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::Exclusivity exclusivity = KisStrokeJobData::NORMAL); private: void visitRecursively(KisNodeSP node, KisProcessingVisitorSP visitor, KisStrokeJobData::Sequentiality sequentiality, KisStrokeJobData::Exclusivity exclusivity); private: KisImageWSP m_image; KisNodeSP m_node; ProcessingFlags m_flags; KisImageSignalVector m_emitSignals; KisStrokeId m_strokeId; bool m_finalSignalsEmitted; - bool m_appliedVisitorToAllFrames; + QSharedPointer m_sharedAllFramesToken; }; Q_DECLARE_OPERATORS_FOR_FLAGS(KisProcessingApplicator::ProcessingFlags) #endif /* __KIS_PROCESSING_APPLICATOR_H */ diff --git a/libs/image/kis_selection.cc b/libs/image/kis_selection.cc index 59dc9c3bd8..5594822c3b 100644 --- a/libs/image/kis_selection.cc +++ b/libs/image/kis_selection.cc @@ -1,361 +1,485 @@ /* * 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" +#include "kis_simple_stroke_strategy.h" +#include "KisDeleteLaterWrapper.h" +#include "kis_command_utils.h" struct Q_DECL_HIDDEN KisSelection::Private { Private(KisSelection *q) : isVisible(true), shapeSelection(0), updateCompressor(q) { } + static void safeDeleteShapeSelection(KisSelectionComponent *shapeSelection, KisSelection *selection); + // 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; }; +void KisSelection::Private::safeDeleteShapeSelection(KisSelectionComponent *shapeSelection, KisSelection *selection) +{ + struct ShapeSelectionReleaseStroke : public KisSimpleStrokeStrategy { + ShapeSelectionReleaseStroke(KisSelectionComponent *shapeSelection) + : KisSimpleStrokeStrategy(QLatin1String("ShapeSelectionReleaseStroke")), + m_shapeSelection(shapeSelection) + { + setRequestsOtherStrokesToEnd(false); + setClearsRedoOnStart(false); + setNeedsExplicitCancel(true); + + this->enableJob(JOB_FINISH, true, KisStrokeJobData::BARRIER); + this->enableJob(JOB_CANCEL, true, KisStrokeJobData::BARRIER); + } + + void finishStrokeCallback() { + makeKisDeleteLaterWrapper(m_shapeSelection)->deleteLater(); + } + + void cancelStrokeCallback() { + finishStrokeCallback(); + } + + private: + KisSelectionComponent *m_shapeSelection = 0; + }; + + + + KisImageSP image = 0; + + KisNodeSP parentNode = selection->parentNode(); + if (parentNode) { + image = parentNode->image(); + } + + if (image) { + KisStrokeId strokeId = image->startStroke(new ShapeSelectionReleaseStroke(shapeSelection)); + image->endStroke(strokeId); + } else { + makeKisDeleteLaterWrapper(shapeSelection)->deleteLater(); + } +} + +struct KisSelection::ChangeShapeSelectionCommand : public KUndo2Command +{ + ChangeShapeSelectionCommand(KisSelection *selection, KisSelectionComponent *shapeSelection) + : m_selection(selection), + m_shapeSelection(shapeSelection) + { + m_isFlatten = !shapeSelection; + } + + ~ChangeShapeSelectionCommand() { + if (m_shapeSelection) { + Private::safeDeleteShapeSelection(m_shapeSelection, m_selection); + } + } + + void undo() override + { + if (m_reincarnationCommand) { + m_reincarnationCommand->undo(); + } + + std::swap(m_selection->m_d->shapeSelection, m_shapeSelection); + + if (!m_isFlatten) { + m_selection->requestCompressedProjectionUpdate(QRect()); + } + } + + void redo() override + { + if (m_firstRedo) { + if (bool(m_selection->m_d->shapeSelection) != bool(m_shapeSelection)) { + m_reincarnationCommand.reset( + m_selection->m_d->pixelSelection->reincarnateWithDetachedHistory(m_isFlatten)); + } + m_firstRedo = false; + + } + + if (m_reincarnationCommand) { + m_reincarnationCommand->redo(); + } + + std::swap(m_selection->m_d->shapeSelection, m_shapeSelection); + + if (!m_isFlatten) { + m_selection->requestCompressedProjectionUpdate(QRect()); + } + } + +private: + KisSelection *m_selection = 0; + KisSelectionComponent *m_shapeSelection = 0; + QScopedPointer m_reincarnationCommand; + bool m_firstRedo = true; + bool m_isFlatten = false; +}; + KisSelection::KisSelection(KisDefaultBoundsBaseSP defaultBounds) : m_d(new Private(this)) { if (!defaultBounds) { defaultBounds = new KisSelectionEmptyBounds(0); } m_d->defaultBounds = defaultBounds; m_d->pixelSelection = new KisPixelSelection(m_d->defaultBounds, this); m_d->pixelSelection->setParentNode(m_d->parentNode); } KisSelection::KisSelection(const KisSelection& rhs) : KisShared(), m_d(new Private(this)) { copyFrom(rhs); } KisSelection::KisSelection(const KisPaintDeviceSP source, KritaUtils::DeviceCopyMode copyMode, KisDefaultBoundsBaseSP defaultBounds) : m_d(new Private(this)) { if (!defaultBounds) { defaultBounds = new KisSelectionEmptyBounds(0); } m_d->defaultBounds = defaultBounds; m_d->pixelSelection = new KisPixelSelection(source, copyMode); m_d->pixelSelection->setParentSelection(this); m_d->pixelSelection->setParentNode(m_d->parentNode); m_d->pixelSelection->setDefaultBounds(m_d->defaultBounds); } KisSelection &KisSelection::operator=(const KisSelection &rhs) { if (&rhs != this) { copyFrom(rhs); } return *this; } void KisSelection::copyFrom(const KisSelection &rhs) { m_d->isVisible = rhs.m_d->isVisible; m_d->defaultBounds = rhs.m_d->defaultBounds; m_d->parentNode = 0; // not supposed to be shared Q_ASSERT(rhs.m_d->pixelSelection); m_d->pixelSelection = new KisPixelSelection(*rhs.m_d->pixelSelection, KritaUtils::CopyAllFrames); m_d->pixelSelection->setParentSelection(this); - - if (rhs.m_d->shapeSelection) { + if (rhs.m_d->shapeSelection && !rhs.m_d->shapeSelection->isEmpty()) { m_d->shapeSelection = rhs.m_d->shapeSelection->clone(this); - Q_ASSERT(m_d->shapeSelection); - Q_ASSERT(m_d->shapeSelection != rhs.m_d->shapeSelection); + KIS_SAFE_ASSERT_RECOVER_NOOP(m_d->shapeSelection); + KIS_SAFE_ASSERT_RECOVER(m_d->shapeSelection && + m_d->shapeSelection != rhs.m_d->shapeSelection) { + m_d->shapeSelection = 0; + } } else { - m_d->shapeSelection = 0; + if (m_d->shapeSelection) { + Private::safeDeleteShapeSelection(m_d->shapeSelection, this); + 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() || + return m_d->shapeSelection || m_d->pixelSelection->outlineCacheValid(); } QPainterPath KisSelection::outlineCache() const { QPainterPath outline; - if (hasShapeSelection()) { + if (m_d->shapeSelection) { 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()) { + if (m_d->shapeSelection) { 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 +bool KisSelection::hasNonEmptyPixelSelection() const { return m_d->pixelSelection && !m_d->pixelSelection->isEmpty(); } -bool KisSelection::hasShapeSelection() const +bool KisSelection::hasNonEmptyShapeSelection() const { return m_d->shapeSelection && !m_d->shapeSelection->isEmpty(); } +bool KisSelection::hasShapeSelection() const +{ + return m_d->shapeSelection; +} + KisPixelSelectionSP KisSelection::pixelSelection() const { return m_d->pixelSelection; } KisSelectionComponent* KisSelection::shapeSelection() const { return m_d->shapeSelection; } -void KisSelection::setShapeSelection(KisSelectionComponent* shapeSelection) +void KisSelection::convertToVectorSelectionNoUndo(KisSelectionComponent* shapeSelection) { - const bool needsNotification = shapeSelection != m_d->shapeSelection; - - m_d->shapeSelection = shapeSelection; + QScopedPointer cmd(new ChangeShapeSelectionCommand(this, shapeSelection)); + cmd->redo(); +} - if (needsNotification) { - requestCompressedProjectionUpdate(QRect()); - } +KUndo2Command *KisSelection::convertToVectorSelection(KisSelectionComponent *shapeSelection) +{ + KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!m_d->shapeSelection, nullptr); + return new ChangeShapeSelectionCommand(this, shapeSelection); } KisPixelSelectionSP KisSelection::projection() const { return m_d->pixelSelection; } void KisSelection::updateProjection(const QRect &rc) { - if(hasShapeSelection()) { + if(m_d->shapeSelection) { m_d->shapeSelection->renderToProjection(m_d->pixelSelection, rc); m_d->pixelSelection->setOutlineCache(m_d->shapeSelection->outlineCache()); } } void KisSelection::updateProjection() { - if(hasShapeSelection()) { + if(m_d->shapeSelection) { 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; + if (m_d->shapeSelection) { + Private::safeDeleteShapeSelection(m_d->shapeSelection, this); + m_d->shapeSelection = 0; + } m_d->pixelSelection->clear(); } KUndo2Command* KisSelection::flatten() { KUndo2Command *command = 0; - if (hasShapeSelection()) { + if (m_d->shapeSelection) { command = m_d->shapeSelection->resetToEmpty(); + + if (command) { + KisCommandUtils::CompositeCommand *cmd = new KisCommandUtils::CompositeCommand(); + cmd->addCommand(command); + cmd->addCommand(new ChangeShapeSelectionCommand(this, nullptr)); + command = cmd; + } else { + command = new ChangeShapeSelectionCommand(this, nullptr); + } } return command; } void KisSelection::notifySelectionChanged() { KisNodeWSP parentNode; if (!(parentNode = this->parentNode())) return; KisNodeGraphListener *listener; if (!(listener = parentNode->graphListener())) return; listener->notifySelectionChanged(); } void KisSelection::requestCompressedProjectionUpdate(const QRect &rc) { m_d->updateCompressor->requestUpdate(rc); } -void KisSelection::notifyShapeSelectionBecameEmpty() -{ - m_d->pixelSelection->clear(); -} - quint8 KisSelection::selected(qint32 x, qint32 y) const { KisHLineConstIteratorSP iter = m_d->pixelSelection->createHLineConstIteratorNG(x, y, 1); const quint8 *pix = iter->oldRawData(); return *pix; } + diff --git a/libs/image/kis_selection.h b/libs/image/kis_selection.h index d8908185a1..8d455d3856 100644 --- a/libs/image/kis_selection.h +++ b/libs/image/kis_selection.h @@ -1,227 +1,242 @@ /* * Copyright (c) 2004 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_SELECTION_H_ #define KIS_SELECTION_H_ #include #include "kis_types.h" #include "kritaimage_export.h" #include "kis_default_bounds.h" #include "kis_image.h" #include "KisSelectionTags.h" #include "kis_pixel_selection.h" class KisSelectionComponent; class QPainterPath; /** * KisSelection is a composite object. It may contain an instance * of KisPixelSelection and a KisShapeSelection object. Both these * selections are merged into a projection of the KisSelection. * * Every pixel in the paint device can indicate a degree of selectedness, varying * between MIN_SELECTED and MAX_SELECTED. * * The projection() paint device itself is only a projection: you can * read from it, but not write to it. You need to keep track of * the need for updating the projection yourself: there is no * automatic updating after changing the contents of one or more * of the selection components. */ class KRITAIMAGE_EXPORT KisSelection : public KisShared { +private: + struct ChangeShapeSelectionCommand; public: /** * Create a new KisSelection. * * @param defaultBounds defines the bounds of the selection when * Select All is initiated. */ KisSelection(KisDefaultBoundsBaseSP defaultBounds = KisDefaultBoundsBaseSP()); /** * Copy the selection. The selection components are copied, too. */ KisSelection(const KisSelection& rhs); KisSelection& operator=(const KisSelection &rhs); /** * Delete the selection. The shape selection component is deleted, the * pixel selection component is contained in a shared pointer, so that * may still be valid. */ virtual ~KisSelection(); /** * Create a new selection using the content of copySource as the mask. */ KisSelection(const KisPaintDeviceSP copySource, KritaUtils::DeviceCopyMode copyMode, KisDefaultBoundsBaseSP defaultBounds); /** * The paint device of the pixel selection should report * about it's setDirty events to its parent. The creator * should set the parent manually if it wants to get the * signals */ void setParentNode(KisNodeWSP node); - bool hasPixelSelection() const; + bool hasNonEmptyPixelSelection() const; + bool hasNonEmptyShapeSelection() const; bool hasShapeSelection() const; bool outlineCacheValid() const; QPainterPath outlineCache() const; void recalculateOutlineCache(); /** * Tells whether the cached thumbnail of the selection is still valid */ bool thumbnailImageValid() const; /** * Recalculates the thumbnail of the selection */ void recalculateThumbnailImage(const QColor &maskColor); /** * Returns the thumbnail of the selection. */ QImage thumbnailImage() const; /** * Returns the transformation which should be applied to the thumbnail before * being painted over the image */ QTransform thumbnailImageTransform() const; /** * return the pixel selection component of this selection. Pixel * selection component is always present in the selection. In case * the user wants a vector selection, pixel selection will store * the pixelated version of it. * * NOTE: use pixelSelection() for changing the selection only. For * reading the selection and passing the data to bitBlt function use * projection(). Although projection() and pixelSelection() currently * point ot the same paint device, this behavior may change in the * future. */ KisPixelSelectionSP pixelSelection() const; /** * return the vector selection component of this selection or zero * if hasShapeSelection() returns false. */ KisSelectionComponent* shapeSelection() const; - void setShapeSelection(KisSelectionComponent* shapeSelection); + /** + * @brief converts shape selection into the vector state + * + * The selection must not have any shape selection active. It should + * be checked by calling hasShapeSelection() in advance. + * + * @param shapeSelection new shape selection object that should be + * attached to the selection + * @return undo command that exectes and undos the conversion + */ + KUndo2Command* convertToVectorSelection(KisSelectionComponent* shapeSelection); + + /** + * @see convertToVectorSelection() + */ + void convertToVectorSelectionNoUndo(KisSelectionComponent* shapeSelection); /** * Returns the projection of the selection. It may be the same * as pixel selection. You must read selection data from this * paint device only */ KisPixelSelectionSP projection() const; /** * Updates the projection of the selection. You should call this * method after the every change of the selection components. * There is no automatic updates framework present */ void updateProjection(const QRect& rect); void updateProjection(); void setVisible(bool visible); bool isVisible(); /** * Convenience functions. Just call the corresponding methods * of the underlying projection */ bool isTotallyUnselected(const QRect & r) const; QRect selectedRect() const; /** * @brief Slow, but exact way of determining the rectangle * that encloses the selection. * * Default pixel of the selection device may vary and you would get wrong bounds. * selectedExactRect() handles all these cases. * */ QRect selectedExactRect() const; void setX(qint32 x); void setY(qint32 y); qint32 x() const; qint32 y() const; void setDefaultBounds(KisDefaultBoundsBaseSP bounds); void clear(); /** * @brief flatten creates a new pixel selection component from the shape selection * and throws away the shape selection. This has no effect if there is no * shape selection. */ KUndo2Command* flatten(); void notifySelectionChanged(); /** * Request rerendering of the shape selection component in a * compressed way. Usually, you don't need to call it manually, * because all the work is done by KisShapeSelectionModel. */ void requestCompressedProjectionUpdate(const QRect &rc); - void notifyShapeSelectionBecameEmpty(); - /// XXX: This method was marked KDE_DEPRECATED but without information on what to /// replace it with. Undeprecate, therefore. quint8 selected(qint32 x, qint32 y) const; KisNodeWSP parentNode() const; private: friend class KisSelectionTest; friend class KisMaskTest; friend class KisAdjustmentLayerTest; friend class KisUpdateSelectionJob; friend class KisSelectionUpdateCompressor; friend class KisDeselectActiveSelectionCommand; - void copyFrom(const KisSelection &rhs); private: struct Private; Private * const m_d; }; #endif // KIS_SELECTION_H_ diff --git a/libs/image/kis_selection_based_layer.cpp b/libs/image/kis_selection_based_layer.cpp index 3bad0c4d41..bcd3bdfa87 100644 --- a/libs/image/kis_selection_based_layer.cpp +++ b/libs/image/kis_selection_based_layer.cpp @@ -1,358 +1,358 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2005 C. Boemann * Copyright (c) 2009 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_selection_based_layer.h" #include #include "kis_debug.h" #include #include "kis_image.h" #include "kis_painter.h" #include "kis_default_bounds.h" #include "kis_selection.h" #include "kis_pixel_selection.h" #include "filter/kis_filter_configuration.h" #include "filter/kis_filter_registry.h" #include "filter/kis_filter.h" #include "kis_raster_keyframe_channel.h" struct Q_DECL_HIDDEN KisSelectionBasedLayer::Private { public: Private() : useSelectionInProjection(true) {} Private(const Private &rhs) : useSelectionInProjection(rhs.useSelectionInProjection) {} KisSelectionSP selection; KisPaintDeviceSP paintDevice; bool useSelectionInProjection; }; KisSelectionBasedLayer::KisSelectionBasedLayer(KisImageWSP image, const QString &name, KisSelectionSP selection, KisFilterConfigurationSP filterConfig) : KisLayer(image.data(), name, OPACITY_OPAQUE_U8), KisNodeFilterInterface(filterConfig), m_d(new Private()) { if (!selection) { initSelection(); } else { setInternalSelection(selection); } KisImageSP imageSP = image.toStrongRef(); if (!imageSP) { return; } m_d->paintDevice = KisPaintDeviceSP(new KisPaintDevice(this, imageSP->colorSpace(), KisDefaultBoundsSP(new KisDefaultBounds(image)))); connect(imageSP.data(), SIGNAL(sigSizeChanged(QPointF,QPointF)), SLOT(slotImageSizeChanged())); } KisSelectionBasedLayer::KisSelectionBasedLayer(const KisSelectionBasedLayer& rhs) : KisLayer(rhs) , KisIndirectPaintingSupport() , KisNodeFilterInterface(rhs) , m_d(new Private(*rhs.m_d)) { setInternalSelection(rhs.m_d->selection); m_d->paintDevice = new KisPaintDevice(*rhs.m_d->paintDevice.data()); } KisSelectionBasedLayer::~KisSelectionBasedLayer() { delete m_d; } void KisSelectionBasedLayer::initSelection() { m_d->selection = KisSelectionSP(new KisSelection(KisDefaultBoundsSP(new KisDefaultBounds(image())))); m_d->selection->pixelSelection()->setDefaultPixel(KoColor(Qt::white, m_d->selection->pixelSelection()->colorSpace())); m_d->selection->setParentNode(this); m_d->selection->updateProjection(); } void KisSelectionBasedLayer::slotImageSizeChanged() { if (m_d->selection) { /** * Make sure exactBounds() of the selection got recalculated after * the image changed */ m_d->selection->pixelSelection()->setDirty(); setDirty(); } } void KisSelectionBasedLayer::setImage(KisImageWSP image) { m_d->paintDevice->setDefaultBounds(KisDefaultBoundsSP(new KisDefaultBounds(image))); m_d->selection->pixelSelection()->setDefaultBounds(KisDefaultBoundsSP(new KisDefaultBounds(image))); KisLayer::setImage(image); connect(image.data(), SIGNAL(sigSizeChanged(QPointF,QPointF)), SLOT(slotImageSizeChanged())); } bool KisSelectionBasedLayer::allowAsChild(KisNodeSP node) const { return node->inherits("KisMask"); } KisPaintDeviceSP KisSelectionBasedLayer::original() const { return m_d->paintDevice; } KisPaintDeviceSP KisSelectionBasedLayer::paintDevice() const { return m_d->selection->pixelSelection(); } bool KisSelectionBasedLayer::needProjection() const { return m_d->selection; } void KisSelectionBasedLayer::setUseSelectionInProjection(bool value) const { m_d->useSelectionInProjection = value; } KisSelectionSP KisSelectionBasedLayer::fetchComposedInternalSelection(const QRect &rect) const { if (!m_d->selection) return KisSelectionSP(); m_d->selection->updateProjection(rect); KisSelectionSP tempSelection = m_d->selection; KisIndirectPaintingSupport::ReadLocker l(this); if (hasTemporaryTarget()) { /** * WARNING: we don't try to clone the selection entirely, because * it might be unsafe for shape selections. * * TODO: make cloning of vector selections safe! See a comment in * KisShapeSelection::clone(). */ tempSelection = new KisSelection(); KisPainter::copyAreaOptimized(rect.topLeft(), m_d->selection->pixelSelection(), tempSelection->pixelSelection(), rect); KisPainter gc2(tempSelection->pixelSelection()); setupTemporaryPainter(&gc2); gc2.bitBlt(rect.topLeft(), temporaryTarget(), rect); } return tempSelection; } void KisSelectionBasedLayer::copyOriginalToProjection(const KisPaintDeviceSP original, KisPaintDeviceSP projection, const QRect& rect) const { KisSelectionSP tempSelection; if (m_d->useSelectionInProjection) { tempSelection = fetchComposedInternalSelection(rect); /** * When we paint with a selection, the deselected areas will *not* be * overwritten by copyAreaOptimized(), so we need to clear them beforehand */ projection->clear(rect); } KisPainter::copyAreaOptimized(rect.topLeft(), original, projection, rect, tempSelection); } QRect KisSelectionBasedLayer::cropChangeRectBySelection(const QRect &rect) const { return m_d->selection ? rect & m_d->selection->selectedRect() : rect; } QRect KisSelectionBasedLayer::needRect(const QRect &rect, PositionToFilthy pos) const { Q_UNUSED(pos); return rect; } void KisSelectionBasedLayer::resetCache() { KisImageSP imageSP = image().toStrongRef(); if (!imageSP) { return; } if (!m_d->paintDevice || *m_d->paintDevice->colorSpace() != *imageSP->colorSpace()) { m_d->paintDevice = KisPaintDeviceSP(new KisPaintDevice(KisNodeWSP(this), imageSP->colorSpace(), new KisDefaultBounds(image()))); } else { m_d->paintDevice->clear(); } } KisSelectionSP KisSelectionBasedLayer::internalSelection() const { return m_d->selection; } void KisSelectionBasedLayer::setInternalSelection(KisSelectionSP selection) { if (selection) { m_d->selection = new KisSelection(*selection.data()); m_d->selection->setParentNode(this); m_d->selection->setDefaultBounds(new KisDefaultBounds(image())); m_d->selection->updateProjection(); KisPixelSelectionSP pixelSelection = m_d->selection->pixelSelection(); if (pixelSelection->framesInterface()) { addKeyframeChannel(pixelSelection->keyframeChannel()); enableAnimation(); } KisImageSP imageSP = image().toStrongRef(); KIS_SAFE_ASSERT_RECOVER_RETURN(imageSP); if (m_d->selection->pixelSelection()->defaultBounds()->bounds() != imageSP->bounds()) { qWarning() << "WARNING: KisSelectionBasedLayer::setInternalSelection" << "New selection has suspicious default bounds"; qWarning() << "WARNING:" << ppVar(m_d->selection->pixelSelection()->defaultBounds()->bounds()); qWarning() << "WARNING:" << ppVar(imageSP->bounds()); } } else { m_d->selection = 0; } } qint32 KisSelectionBasedLayer::x() const { return m_d->selection ? m_d->selection->x() : 0; } qint32 KisSelectionBasedLayer::y() const { return m_d->selection ? m_d->selection->y() : 0; } void KisSelectionBasedLayer::setX(qint32 x) { if (m_d->selection) { m_d->selection->setX(x); } } void KisSelectionBasedLayer::setY(qint32 y) { if (m_d->selection) { m_d->selection->setY(y); } } KisKeyframeChannel *KisSelectionBasedLayer::requestKeyframeChannel(const QString &id) { if (id == KisKeyframeChannel::Content.id()) { KisRasterKeyframeChannel *contentChannel = m_d->selection->pixelSelection()->createKeyframeChannel(KisKeyframeChannel::Content); contentChannel->setFilenameSuffix(".pixelselection"); return contentChannel; } return KisLayer::requestKeyframeChannel(id); } void KisSelectionBasedLayer::setDirty() { Q_ASSERT(image()); KisImageSP imageSP = image().toStrongRef(); if (!imageSP) { return; } setDirty(imageSP->bounds()); } QRect KisSelectionBasedLayer::extent() const { QRect resultRect; if (m_d->selection) { resultRect = m_d->selection->selectedRect(); // copy for thread safety! KisPaintDeviceSP temporaryTarget = this->temporaryTarget(); if (temporaryTarget) { resultRect |= temporaryTarget->extent(); } } else { KisImageSP image = this->image().toStrongRef(); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(image, QRect()); resultRect = image->bounds(); } return resultRect; } QRect KisSelectionBasedLayer::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 { KisImageSP image = this->image().toStrongRef(); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(image, QRect()); resultRect = image->bounds(); } return resultRect; } -QImage KisSelectionBasedLayer::createThumbnail(qint32 w, qint32 h) +QImage KisSelectionBasedLayer::createThumbnail(qint32 w, qint32 h, Qt::AspectRatioMode aspectRatioMode) { KisSelectionSP originalSelection = internalSelection(); KisPaintDeviceSP originalDevice = original(); return originalDevice && originalSelection ? - originalDevice->createThumbnail(w, h, 1, + originalDevice->createThumbnail(w, h, aspectRatioMode, 1, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()) : QImage(); } diff --git a/libs/image/kis_selection_based_layer.h b/libs/image/kis_selection_based_layer.h index 80a8eefdef..57604c0e44 100644 --- a/libs/image/kis_selection_based_layer.h +++ b/libs/image/kis_selection_based_layer.h @@ -1,211 +1,211 @@ /* * Copyright (c) 2006 Boudewijn Rempt * (c) 2009 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ #ifndef KIS_SELECTION_BASED_LAYER_H_ #define KIS_SELECTION_BASED_LAYER_H_ #include #include "kis_types.h" #include "kis_layer.h" #include "kis_indirect_painting_support.h" #include #include "kis_node_filter_interface.h" class KisFilterConfiguration; /** * @class KisSelectionBasedLayer * @brief Describes base behaviour for * selection base classes like KisAdjustmentLayer and KisGeneratorLayer. * These classes should have a persistent selection that controls * the area where filter/generators are applied. The area outside * this selection is not affected by the layer */ class KRITAIMAGE_EXPORT KisSelectionBasedLayer : public KisLayer, public KisIndirectPaintingSupport, public KisNodeFilterInterface { Q_OBJECT public: /** * creates a new layer with the given selection. * Note that the selection will be _copied_ (with COW, though). * @param image the image to set this layer to * @param name name of the layer * @param selection is a mask used by the layer to know * where to apply the filter/generator. */ KisSelectionBasedLayer(KisImageWSP image, const QString &name, KisSelectionSP selection, KisFilterConfigurationSP filterConfig); KisSelectionBasedLayer(const KisSelectionBasedLayer& rhs); ~KisSelectionBasedLayer() override; /** * tells whether the @node can be a child of this layer * @param node to be connected node * @return tells if to be connected is a child of KisMask */ bool allowAsChild(KisNodeSP node) const override; void setImage(KisImageWSP image) override; KisPaintDeviceSP original() const override; KisPaintDeviceSP paintDevice() const override; bool needProjection() const override; /** * resets cached projection of lower layer to a new device * @return void */ virtual void resetCache(); /** * for KisLayer::setDirty(const KisRegion&) */ using KisLayer::setDirty; /** * Mark a layer as dirty. We can't use KisLayer's one * as our extent() function doesn't fit for this */ void setDirty() override; public: /** * Returns the selection of the layer * * Do not mix it with selection() which returns * the currently active selection of the image */ KisSelectionSP internalSelection() const; /** * sets the selection of this layer to a copy of * selection * @param selection the selection to set * @return void */ void setInternalSelection(KisSelectionSP selection); /** * When painted in indirect painting mode, the internal selection * might not contain actual selection, because a part of it is * stored on an indirect painting device. This method returns the * merged copy of the real selection. The area in \p rect only is * guaranteed to be prepared. The content of the rest of the * selection is undefined. */ KisSelectionSP fetchComposedInternalSelection(const QRect &rect) const; /** * gets this layer's x coordinate, taking selection into account * @return x-coordinate value */ qint32 x() const override; /** * gets this layer's y coordinate, taking selection into account * @return y-coordinate value */ qint32 y() const override; /** * sets this layer's y coordinate, taking selection into account * @param x x coordinate */ void setX(qint32 x) override; /** * sets this layer's y coordinate, taking selection into account * @param y y coordinate */ void setY(qint32 y) override; public: /** * gets an approximation of where the bounds on actual data * are in this layer, taking selection into account */ QRect extent() const override; /** * returns the exact bounds of where the actual data resides * in this layer, taking selection into account */ QRect exactBounds() const override; /** * copies the image and reformats it to thumbnail size * and returns the new thumbnail image. * @param w width of the thumbnail to create * @param h height of the thumbnail to create * @return the thumbnail image created. */ - QImage createThumbnail(qint32 w, qint32 h) override; + QImage createThumbnail(qint32 w, qint32 h, Qt::AspectRatioMode aspectRatioMode = Qt::IgnoreAspectRatio) override; protected: // override from KisLayer void copyOriginalToProjection(const KisPaintDeviceSP original, KisPaintDeviceSP projection, const QRect& rect) const override; // override from KisNode QRect needRect(const QRect &rect, PositionToFilthy pos = N_FILTHY) const override; protected: void initSelection(); QRect cropChangeRectBySelection(const QRect &rect) const; /** * Sets if the selection should be used in * copyOriginalToProjection() method. * * Default value is 'true'. The descendants should override it to * get desired behaviour. * * Must be called only once in the child's constructor */ void setUseSelectionInProjection(bool value) const; KisKeyframeChannel *requestKeyframeChannel(const QString &id) override; public Q_SLOTS: void slotImageSizeChanged(); /** * gets this layer. Overriddes function in * KisIndirectPaintingSupport * @return this AdjustmentLayer */ KisLayer* layer() { return this; } private: struct Private; Private * const m_d; }; #endif /* KIS_SELECTION_BASED_LAYER_H_ */ diff --git a/libs/image/kis_transaction_data.cpp b/libs/image/kis_transaction_data.cpp index d40648f44e..0e0db683df 100644 --- a/libs/image/kis_transaction_data.cpp +++ b/libs/image/kis_transaction_data.cpp @@ -1,324 +1,343 @@ /* * 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_transaction_data.h" #include "kis_pixel_selection.h" #include "kis_paint_device.h" #include "kis_paint_device_frames_interface.h" #include "kis_datamanager.h" #include "kis_image.h" #include "KoColor.h" //#define DEBUG_TRANSACTIONS #ifdef DEBUG_TRANSACTIONS # define DEBUG_ACTION(action) dbgKrita << action << "for" << m_d->device->dataManager() #else # define DEBUG_ACTION(action) #endif class Q_DECL_HIDDEN KisTransactionData::Private { public: KisPaintDeviceSP device; KisMementoSP memento; bool firstRedo; bool transactionFinished; QPoint oldOffset; QPoint newOffset; KoColor oldDefaultPixel; bool defaultPixelChanged = false; bool savedOutlineCacheValid; QPainterPath savedOutlineCache; - KUndo2Command *flattenUndoCommand; + QScopedPointer flattenUndoCommand; bool resetSelectionOutlineCache; int transactionTime; int transactionFrameId; KisDataManagerSP savedDataManager; KUndo2Command newFrameCommand; void possiblySwitchCurrentTime(); KisDataManagerSP dataManager(); void moveDevice(const QPoint newOffset); void tryCreateNewFrame(KisPaintDeviceSP device, int time); }; KisTransactionData::KisTransactionData(const KUndo2MagicString& name, KisPaintDeviceSP device, bool resetSelectionOutlineCache, KUndo2Command* parent) : KUndo2Command(name, parent) , m_d(new Private()) { m_d->resetSelectionOutlineCache = resetSelectionOutlineCache; setTimedID(-1); + + possiblyFlattenSelection(device); init(device); saveSelectionOutlineCache(); } #include "kis_raster_keyframe_channel.h" #include "kis_image_config.h" void KisTransactionData::Private::tryCreateNewFrame(KisPaintDeviceSP device, int time) { if (!device->framesInterface()) return; KisImageConfig cfg(true); if (!cfg.lazyFrameCreationEnabled()) return; KisRasterKeyframeChannel *channel = device->keyframeChannel(); KIS_ASSERT_RECOVER(channel) { return; } KisKeyframeSP keyframe = channel->keyframeAt(time); if (!keyframe) { keyframe = channel->activeKeyframeAt(time); KisKeyframeSP newKeyframe = channel->copyKeyframe(keyframe, time, &newFrameCommand); newKeyframe->setColorLabel(KisImageConfig(true).defaultFrameColorLabel()); } } void KisTransactionData::init(KisPaintDeviceSP device) { m_d->device = device; DEBUG_ACTION("Transaction started"); m_d->oldOffset = QPoint(device->x(), device->y()); m_d->oldDefaultPixel = device->defaultPixel(); m_d->firstRedo = true; m_d->transactionFinished = false; - m_d->flattenUndoCommand = 0; m_d->transactionTime = device->defaultBounds()->currentTime(); m_d->tryCreateNewFrame(m_d->device, m_d->transactionTime); m_d->transactionFrameId = device->framesInterface() ? device->framesInterface()->currentFrameId() : -1; m_d->savedDataManager = m_d->transactionFrameId >= 0 ? m_d->device->framesInterface()->frameDataManager(m_d->transactionFrameId) : m_d->device->dataManager(); m_d->memento = m_d->savedDataManager->getMemento(); } KisTransactionData::~KisTransactionData() { Q_ASSERT(m_d->memento); m_d->savedDataManager->purgeHistory(m_d->memento); delete m_d; } void KisTransactionData::Private::moveDevice(const QPoint newOffset) { if (transactionFrameId >= 0) { device->framesInterface()->setFrameOffset(transactionFrameId, newOffset); } else { device->moveTo(newOffset); } } void KisTransactionData::endTransaction() { if(!m_d->transactionFinished) { // make sure the time didn't change during the transaction KIS_ASSERT_RECOVER_RETURN( m_d->transactionTime == m_d->device->defaultBounds()->currentTime()); DEBUG_ACTION("Transaction ended"); m_d->transactionFinished = true; m_d->savedDataManager->commit(); m_d->newOffset = QPoint(m_d->device->x(), m_d->device->y()); m_d->defaultPixelChanged = m_d->oldDefaultPixel != m_d->device->defaultPixel(); } } void KisTransactionData::startUpdates() { if (m_d->transactionFrameId == -1 || m_d->transactionFrameId == m_d->device->framesInterface()->currentFrameId()) { QRect rc; QRect mementoExtent = m_d->memento->extent(); if (m_d->newOffset == m_d->oldOffset) { rc = mementoExtent.translated(m_d->device->x(), m_d->device->y()); } else { QRect totalExtent = m_d->savedDataManager->extent() | mementoExtent; rc = totalExtent.translated(m_d->oldOffset) | totalExtent.translated(m_d->newOffset); } if (m_d->defaultPixelChanged) { rc |= m_d->device->defaultBounds()->bounds(); } m_d->device->setDirty(rc); } else { m_d->device->framesInterface()->invalidateFrameCache(m_d->transactionFrameId); } } void KisTransactionData::possiblyNotifySelectionChanged() { KisPixelSelectionSP pixelSelection = dynamic_cast(m_d->device.data()); KisSelectionSP selection; if (pixelSelection && (selection = pixelSelection->parentSelection())) { selection->notifySelectionChanged(); } } void KisTransactionData::possiblyResetOutlineCache() { KisPixelSelectionSP pixelSelection; if (m_d->resetSelectionOutlineCache && (pixelSelection = dynamic_cast(m_d->device.data()))) { pixelSelection->invalidateOutlineCache(); } } +void KisTransactionData::possiblyFlattenSelection(KisPaintDeviceSP device) +{ + KisPixelSelectionSP pixelSelection = + dynamic_cast(device.data()); + + if (pixelSelection) { + KisSelection *selection = pixelSelection->parentSelection().data(); + if (selection) { + m_d->flattenUndoCommand.reset(selection->flatten()); + + if (m_d->flattenUndoCommand) { + m_d->flattenUndoCommand->redo(); + } + } + } +} + +void KisTransactionData::doFlattenUndoRedo(bool undo) +{ + KisPixelSelectionSP pixelSelection = + dynamic_cast(m_d->device.data()); + + if (pixelSelection) { + if (m_d->flattenUndoCommand) { + if (undo) { + m_d->flattenUndoCommand->undo(); + } else { + m_d->flattenUndoCommand->redo(); + } + } + } +} + void KisTransactionData::Private::possiblySwitchCurrentTime() { if (device->defaultBounds()->currentTime() == transactionTime) return; qWarning() << "WARNING: undo command has been executed, when another frame has been active. That shouldn't have happened."; device->requestTimeSwitch(transactionTime); } void KisTransactionData::redo() { //KUndo2QStack calls redo(), so the first call needs to be blocked if (m_d->firstRedo) { m_d->firstRedo = false; possiblyResetOutlineCache(); possiblyNotifySelectionChanged(); return; } - + doFlattenUndoRedo(false); restoreSelectionOutlineCache(false); m_d->newFrameCommand.redo(); DEBUG_ACTION("Redo()"); Q_ASSERT(m_d->memento); m_d->savedDataManager->rollforward(m_d->memento); if (m_d->newOffset != m_d->oldOffset) { m_d->moveDevice(m_d->newOffset); } m_d->possiblySwitchCurrentTime(); startUpdates(); possiblyNotifySelectionChanged(); } void KisTransactionData::undo() { DEBUG_ACTION("Undo()"); Q_ASSERT(m_d->memento); m_d->savedDataManager->rollback(m_d->memento); if (m_d->newOffset != m_d->oldOffset) { m_d->moveDevice(m_d->oldOffset); } restoreSelectionOutlineCache(true); + doFlattenUndoRedo(true); m_d->possiblySwitchCurrentTime(); startUpdates(); possiblyNotifySelectionChanged(); m_d->newFrameCommand.undo(); } void KisTransactionData::saveSelectionOutlineCache() { m_d->savedOutlineCacheValid = false; KisPixelSelectionSP pixelSelection = dynamic_cast(m_d->device.data()); if (pixelSelection) { m_d->savedOutlineCacheValid = pixelSelection->outlineCacheValid(); if (m_d->savedOutlineCacheValid) { m_d->savedOutlineCache = pixelSelection->outlineCache(); possiblyResetOutlineCache(); } - - KisSelectionSP selection = pixelSelection->parentSelection(); - if (selection) { - m_d->flattenUndoCommand = selection->flatten(); - if (m_d->flattenUndoCommand) { - m_d->flattenUndoCommand->redo(); - } - } } } void KisTransactionData::restoreSelectionOutlineCache(bool undo) { KisPixelSelectionSP pixelSelection = dynamic_cast(m_d->device.data()); if (pixelSelection) { bool savedOutlineCacheValid; QPainterPath savedOutlineCache; savedOutlineCacheValid = pixelSelection->outlineCacheValid(); if (savedOutlineCacheValid) { savedOutlineCache = pixelSelection->outlineCache(); } if (m_d->savedOutlineCacheValid) { pixelSelection->setOutlineCache(m_d->savedOutlineCache); } else { pixelSelection->invalidateOutlineCache(); } m_d->savedOutlineCacheValid = savedOutlineCacheValid; if (m_d->savedOutlineCacheValid) { m_d->savedOutlineCache = savedOutlineCache; } - - if (m_d->flattenUndoCommand) { - if (undo) { - m_d->flattenUndoCommand->undo(); - } else { - m_d->flattenUndoCommand->redo(); - } - } } } diff --git a/libs/image/kis_transaction_data.h b/libs/image/kis_transaction_data.h index 74a82b6861..9af33ec17b 100644 --- a/libs/image/kis_transaction_data.h +++ b/libs/image/kis_transaction_data.h @@ -1,64 +1,66 @@ /* * 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 KIS_TRANSACTION_DATA_H_ #define KIS_TRANSACTION_DATA_H_ #include #include "kis_types.h" #include /** * A tile based undo command. * * Ordinary KUndo2Command subclasses store parameters and apply the action in * the redo() command, however, Krita doesn't work like this. Undo replaces * the current tiles in a paint device with the old tiles, redo replaces them * again with the new tiles without actually executing the command that changed * the image data again. */ class KRITAIMAGE_EXPORT KisTransactionData : public KUndo2Command { public: KisTransactionData(const KUndo2MagicString& name, KisPaintDeviceSP device, bool resetSelectionOutlineCache, KUndo2Command* parent); ~KisTransactionData() override; public: void redo() override; void undo() override; virtual void endTransaction(); protected: virtual void saveSelectionOutlineCache(); virtual void restoreSelectionOutlineCache(bool undo); private: void init(KisPaintDeviceSP device); void startUpdates(); void possiblyNotifySelectionChanged(); void possiblyResetOutlineCache(); + void possiblyFlattenSelection(KisPaintDeviceSP device); + void doFlattenUndoRedo(bool undo); private: class Private; Private * const m_d; }; #endif /* KIS_TRANSACTION_DATA_H_ */ diff --git a/libs/image/layerstyles/kis_ls_utils.cpp b/libs/image/layerstyles/kis_ls_utils.cpp index 4b562898fb..283ee7db9b 100644 --- a/libs/image/layerstyles/kis_ls_utils.cpp +++ b/libs/image/layerstyles/kis_ls_utils.cpp @@ -1,592 +1,592 @@ /* * 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; } void selectionFromAlphaChannel(KisPaintDeviceSP srcDevice, KisSelectionSP dstSelection, const QRect &srcRect) { const KoColorSpace *cs = srcDevice->colorSpace(); KisPixelSelectionSP selection = dstSelection->pixelSelection(); 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); } } 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, BORDER_IGNORE); } 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, KisLayerStyleFilterEnvironment *env) { Q_UNUSED(context); const QRect overlayRect = kisGrowRect(applyRect, noiseNeedBorder); KisPixelSelectionSP randomSelection = env->cachedRandomSelection(overlayRect); KisCachedSelection::Guard s1(*env->cachedSelection()); KisPixelSelectionSP randomOverlay = s1.selection()->pixelSelection(); KisSequentialConstIterator noiseIt(randomSelection, overlayRect); KisSequentialConstIterator srcIt(selection, overlayRect); KisRandomAccessorSP dstIt = randomOverlay->createRandomAccessorNG(); 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, KoPatternSP 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); + QTransform transform; + transform.translate(-patternOffset.x(), -patternOffset.y()); + qreal scaleNorm = qreal(scale*0.01); + transform.scale(scaleNorm, scaleNorm); + gc.fillRect(fillRect, pattern, transform); 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()); 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 = 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/lazybrush/kis_colorize_mask.cpp b/libs/image/lazybrush/kis_colorize_mask.cpp index 698a26479c..16731c941e 100644 --- a/libs/image/lazybrush/kis_colorize_mask.cpp +++ b/libs/image/lazybrush/kis_colorize_mask.cpp @@ -1,1173 +1,1173 @@ /* * 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()->alpha8())), 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; 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(const QString name) : KisEffectMask(name) , 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); emit m_node->sigKeyStrokesListChanged(); } 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); emit m_node->sigKeyStrokesListChanged(); } 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, KUndo2Command *parentCommand) { m_d->fakePaintDevice->setProfile(profile, parentCommand); m_d->coloringProjection->setProfile(profile, parentCommand); 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. + // but when it arrives, the parent doesn't exist 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); 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); } } 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 { 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); } } } 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); 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); } } 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/tests/CMakeLists.txt b/libs/image/tests/CMakeLists.txt index fddafbc361..351b7b374c 100644 --- a/libs/image/tests/CMakeLists.txt +++ b/libs/image/tests/CMakeLists.txt @@ -1,192 +1,187 @@ # cmake in some versions for some not yet known reasons fails to run automoc # on random targets (changing target names already has an effect) # As temporary workaround skipping build of tests on these versions for now # See https://mail.kde.org/pipermail/kde-buildsystem/2015-June/010819.html # extend range of affected cmake versions as needed if(NOT ${CMAKE_VERSION} VERSION_LESS 3.1.3 AND NOT ${CMAKE_VERSION} VERSION_GREATER 3.2.3) message(WARNING "Skipping krita/image/tests, CMake in at least versions 3.1.3 - 3.2.3 seems to have a problem with automoc. \n(FRIENDLY REMINDER: PLEASE DON'T BREAK THE TESTS!)") set (HAVE_FAILING_CMAKE TRUE) else() set (HAVE_FAILING_CMAKE FALSE) endif() include_directories( ${CMAKE_BINARY_DIR}/libs/image/ ${CMAKE_SOURCE_DIR}/libs/image/ ${CMAKE_SOURCE_DIR}/libs/image/brushengine ${CMAKE_SOURCE_DIR}/libs/image/tiles3 ${CMAKE_SOURCE_DIR}/libs/image/tiles3/swap ${CMAKE_SOURCE_DIR}/sdk/tests ) include_Directories(SYSTEM ${EIGEN3_INCLUDE_DIR} ) if(HAVE_VC) include_directories(${Vc_INCLUDE_DIR}) endif() include(ECMAddTests) macro_add_unittest_definitions() set(KisRandomGeneratorDemoSources kis_random_generator_demo.cpp kimageframe.cpp) ki18n_wrap_ui(KisRandomGeneratorDemoSources kis_random_generator_demo.ui) add_executable(KisRandomGeneratorDemo ${KisRandomGeneratorDemoSources}) target_link_libraries(KisRandomGeneratorDemo kritaimage) ecm_mark_as_test(KisRandomGeneratorDemo) ecm_add_tests( kis_base_node_test.cpp kis_fast_math_test.cpp kis_node_test.cpp kis_node_facade_test.cpp kis_fixed_paint_device_test.cpp kis_layer_test.cpp kis_effect_mask_test.cpp kis_iterator_test.cpp kis_painter_test.cpp kis_count_visitor_test.cpp kis_projection_test.cpp kis_properties_configuration_test.cpp kis_transaction_test.cpp kis_pixel_selection_test.cpp kis_group_layer_test.cpp kis_paint_layer_test.cpp kis_adjustment_layer_test.cpp kis_annotation_test.cpp kis_clone_layer_test.cpp kis_convolution_painter_test.cpp kis_crop_processing_visitor_test.cpp kis_processing_applicator_test.cpp kis_datamanager_test.cpp kis_fill_painter_test.cpp kis_filter_configuration_test.cpp kis_filter_test.cpp kis_filter_processing_information_test.cpp kis_filter_registry_test.cpp kis_filter_strategy_test.cpp kis_gradient_painter_test.cpp kis_image_commands_test.cpp kis_image_test.cpp kis_image_signal_router_test.cpp kis_iterators_ng_test.cpp kis_iterator_benchmark.cpp kis_updater_context_test.cpp kis_simple_update_queue_test.cpp kis_stroke_test.cpp kis_simple_stroke_strategy_test.cpp kis_stroke_strategy_undo_command_based_test.cpp kis_strokes_queue_test.cpp kis_mask_test.cpp kis_math_toolbox_test.cpp kis_name_server_test.cpp kis_node_commands_test.cpp kis_node_graph_listener_test.cpp kis_node_visitor_test.cpp kis_paint_information_test.cpp kis_distance_information_test.cpp kis_paintop_test.cpp kis_pattern_test.cpp kis_selection_mask_test.cpp kis_shared_ptr_test.cpp kis_bsplines_test.cpp kis_warp_transform_worker_test.cpp kis_liquify_transform_worker_test.cpp kis_transparency_mask_test.cpp kis_types_test.cpp kis_vec_test.cpp kis_filter_config_widget_test.cpp kis_mask_generator_test.cpp kis_cubic_curve_test.cpp kis_fixed_point_maths_test.cpp kis_node_query_path_test.cpp kis_filter_weights_buffer_test.cpp kis_filter_weights_applicator_test.cpp kis_fill_interval_test.cpp kis_fill_interval_map_test.cpp kis_scanline_fill_test.cpp kis_psd_layer_style_test.cpp kis_layer_style_projection_plane_test.cpp kis_lod_capable_layer_offset_test.cpp kis_algebra_2d_test.cpp kis_marker_painter_test.cpp kis_lazy_brush_test.cpp kis_mask_similarity_test.cpp KisMaskGeneratorTest.cpp kis_layer_style_filter_environment_test.cpp kis_asl_parser_test.cpp KisPerStrokeRandomSourceTest.cpp KisWatershedWorkerTest.cpp kis_dom_utils_test.cpp kis_transform_worker_test.cpp kis_cs_conversion_test.cpp kis_projection_leaf_test.cpp kis_histogram_test.cpp kis_onion_skin_compositor_test.cpp kis_queues_progress_updater_test.cpp kis_image_animation_interface_test.cpp kis_walkers_test.cpp kis_cage_transform_worker_test.cpp kis_random_generator_test.cpp kis_keyframing_test.cpp kis_filter_mask_test.cpp kis_asl_layer_style_serializer_test.cpp TestAslStorage.cpp kis_async_merger_test.cpp + kis_selection_test.cpp LINK_LIBRARIES kritaimage Qt5::Test NAME_PREFIX "libs-image-" ) include(KritaAddBrokenUnitTest) krita_add_broken_unit_test( kis_transform_mask_test.cpp TEST_NAME kis_transform_mask_test LINK_LIBRARIES kritaimage Qt5::Test NAME_PREFIX "libs-image-" ) krita_add_broken_unit_test( kis_layer_styles_test.cpp TEST_NAME kis_layer_styles_test LINK_LIBRARIES kritaimage Qt5::Test NAME_PREFIX "libs-image-" ) krita_add_broken_unit_test( kis_update_scheduler_test.cpp TEST_NAME kis_update_scheduler_test LINK_LIBRARIES kritaimage Qt5::Test NAME_PREFIX "libs-image-" ) krita_add_broken_unit_test( kis_paint_device_test.cpp TEST_NAME kis_paint_device_test LINK_LIBRARIES kritaimage Qt5::Test NAME_PREFIX "libs-image-" ) krita_add_broken_unit_test( kis_colorize_mask_test.cpp TEST_NAME kis_colorize_mask_test LINK_LIBRARIES kritaimage Qt5::Test NAME_PREFIX "libs-image-" ) -krita_add_broken_unit_test( kis_selection_test.cpp - TEST_NAME kis_selection_test - LINK_LIBRARIES kritaimage Qt5::Test - NAME_PREFIX "libs-image-" -) - krita_add_broken_unit_test( kis_processings_test.cpp TEST_NAME kis_processings_test LINK_LIBRARIES kritaimage Qt5::Test NAME_PREFIX "libs-image-" ) krita_add_broken_unit_test( kis_perspective_transform_worker_test.cpp TEST_NAME kis_perspective_transform_worker_test LINK_LIBRARIES kritaimage Qt5::Test NAME_PREFIX "libs-image-" ) diff --git a/libs/image/tests/kis_selection_test.cpp b/libs/image/tests/kis_selection_test.cpp index 1fc12397a7..83f43e7214 100644 --- a/libs/image/tests/kis_selection_test.cpp +++ b/libs/image/tests/kis_selection_test.cpp @@ -1,340 +1,341 @@ /* * 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->hasNonEmptyPixelSelection(), false); + QCOMPARE(selection->hasNonEmptyShapeSelection(), false); QCOMPARE(selection->shapeSelection(), (void*)0); selection->pixelSelection()->select(QRect(10,10,10,10)); - QCOMPARE(selection->hasPixelSelection(), true); + QCOMPARE(selection->hasNonEmptyPixelSelection(), true); QCOMPARE(selection->selectedExactRect(), QRect(10,10,10,10)); } void KisSelectionTest::testSelectionActions() { KisSelectionSP selection = new KisSelection(); - QVERIFY(selection->hasPixelSelection() == false); - QVERIFY(selection->hasShapeSelection() == false); + QVERIFY(selection->hasNonEmptyPixelSelection() == false); + QVERIFY(selection->hasNonEmptyShapeSelection() == 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->setDefaultBounds(new KisDefaultBounds(image)); device->fill(referenceDeviceRect, KoColor(Qt::white, cs)); QCOMPARE(device->exactBounds(), referenceDeviceRect); 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/libkis/CMakeLists.txt b/libs/libkis/CMakeLists.txt index d69ff46038..958104aaaf 100644 --- a/libs/libkis/CMakeLists.txt +++ b/libs/libkis/CMakeLists.txt @@ -1,50 +1,51 @@ set(kritalibkis_LIB_SRCS Canvas.cpp Channel.cpp DockWidget.cpp DockWidgetFactoryBase.cpp Document.cpp Filter.cpp InfoObject.cpp Krita.cpp ManagedColor.cpp Node.cpp Notifier.cpp PresetChooser Palette.cpp PaletteView.cpp + Scratchpad.cpp Swatch.cpp Resource.cpp Selection.cpp View.cpp Extension.cpp Window.cpp GroupLayer.cpp CloneLayer.cpp FileLayer.cpp FilterLayer.cpp FillLayer.cpp VectorLayer.cpp FilterMask.cpp SelectionMask.cpp Shape.cpp GroupShape.cpp LibKisUtils.cpp ) add_library(kritalibkis SHARED ${kritalibkis_LIB_SRCS} ) generate_export_header(kritalibkis) target_link_libraries(kritalibkis kritaui kritaimage kritaversion) target_link_libraries(kritalibkis LINK_INTERFACE_LIBRARIES kritaimage kritaui) set_target_properties(kritalibkis PROPERTIES VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION} ) install(TARGETS kritalibkis ${INSTALL_TARGETS_DEFAULT_ARGS}) add_subdirectory(tests) diff --git a/libs/libkis/Document.cpp b/libs/libkis/Document.cpp index c95b38e34e..3257d9cc78 100644 --- a/libs/libkis/Document.cpp +++ b/libs/libkis/Document.cpp @@ -1,1010 +1,1026 @@ /* * 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; bool ownsDocument {false}; }; Document::Document(KisDocument *document, bool ownsDocument, QObject *parent) : QObject(parent) , d(new Private) { d->document = document; d->ownsDocument = ownsDocument; } Document::~Document() { if (d->ownsDocument && d->document) { KisPart::instance()->removeDocument(d->document); delete d->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()->waitForDone(); 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()->waitForDone(); 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); + KisFilterStrategy *strategy = KisFilterStrategyRegistry::instance()->get("Bicubic"); + KIS_SAFE_ASSERT_RECOVER_RETURN(strategy); + + image->scaleImage(image->size(), value / 72.0, value / 72.0, strategy); + image->waitForDone(); } 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()); + KisImageSP image = d->document->image(); + if (!image) return; + + KisFilterStrategy *strategy = KisFilterStrategyRegistry::instance()->get("Bicubic"); + KIS_SAFE_ASSERT_RECOVER_RETURN(strategy); + + image->scaleImage(image->size(), xRes / 72.0, image->yRes(), strategy); + image->waitForDone(); } 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); + KisImageSP image = d->document->image(); + if (!image) return; + + KisFilterStrategy *strategy = KisFilterStrategyRegistry::instance()->get("Bicubic"); + KIS_SAFE_ASSERT_RECOVER_RETURN(strategy); + + image->scaleImage(image->size(), image->xRes(), yRes / 72.0, strategy); + image->waitForDone(); } 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->ownsDocument); if (d->ownsDocument) { delete 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); image->waitForDone(); } 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); d->document->image()->waitForDone(); } 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); image->waitForDone(); } 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); image->waitForDone(); } void Document::rotateImage(double radians) { if (!d->document) return; KisImageSP image = d->document->image(); if (!image) return; image->rotateImage(radians); image->waitForDone(); } void Document::shearImage(double angleX, double angleY) { if (!d->document) return; KisImageSP image = d->document->image(); if (!image) return; image->shear(angleX, angleY); image->waitForDone(); } 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(name)); } else if (nodeType.toLower() == "filtermask") { node = new Node(image, new KisFilterMask(name)); } else if (nodeType.toLower() == "transformmask") { node = new Node(image, new KisTransformMask(name)); } else if (nodeType.toLower() == "selectionmask") { node = new Node(image, new KisSelectionMask(image, name)); } 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->factoryConfiguration(KisGlobalResourcesInterface::instance()); 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(); } 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 * newDocument = new Document(clone, d->ownsDocument); clone->setParent(newDocument); // It's owned by the document, not KisPart return newDocument; } 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; } void Document::setOwnsDocument(bool ownsDocument) { d->ownsDocument = ownsDocument; } /* 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/Krita.cpp b/libs/libkis/Krita.cpp index e2739c91bc..a8ce842b8e 100644 --- a/libs/libkis/Krita.cpp +++ b/libs/libkis/Krita.cpp @@ -1,417 +1,422 @@ /* * 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 "Krita.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 "View.h" #include "Document.h" #include "Window.h" #include "Extension.h" #include "DockWidgetFactoryBase.h" #include "Filter.h" #include "InfoObject.h" #include "Resource.h" Krita* Krita::s_instance = 0; struct Krita::Private { Private() {} QList extensions; bool batchMode {false}; Notifier *notifier{new Notifier()}; }; Krita::Krita(QObject *parent) : QObject(parent) , d(new Private) { qRegisterMetaType(); connect(KisPart::instance(), SIGNAL(sigMainWindowIsBeingCreated(KisMainWindow*)), SLOT(mainWindowIsBeingCreated(KisMainWindow*))); } Krita::~Krita() { qDeleteAll(d->extensions); delete d->notifier; delete d; } QList Krita::actions() const { KisMainWindow *mainWindow = KisPart::instance()->currentMainwindow(); if (!mainWindow) { return QList(); } KActionCollection *actionCollection = mainWindow->actionCollection(); return actionCollection->actions(); } QAction *Krita::action(const QString &name) const { KisMainWindow *mainWindow = KisPart::instance()->currentMainwindow(); if (!mainWindow) { return 0; } KActionCollection *actionCollection = mainWindow->actionCollection(); QAction *action = actionCollection->action(name); return action; } Document* Krita::activeDocument() const { KisMainWindow *mainWindow = KisPart::instance()->currentMainwindow(); if (!mainWindow) { return 0; } KisView *view = mainWindow->activeView(); if (!view) { return 0; } KisDocument *document = view->document(); Document *d = new Document(document, false); return d; } void Krita::setActiveDocument(Document* value) { Q_FOREACH(KisView *view, KisPart::instance()->views()) { if (view->document() == value->document().data()) { view->activateWindow(); break; } } } bool Krita::batchmode() const { return d->batchMode; } void Krita::setBatchmode(bool value) { d->batchMode = value; } QList Krita::documents() const { QList ret; foreach(QPointer doc, KisPart::instance()->documents()) { ret << new Document(doc, false); } return ret; } QStringList Krita::filters() const { QStringList ls = KisFilterRegistry::instance()->keys(); std::sort(ls.begin(), ls.end()); return ls; } Filter *Krita::filter(const QString &name) const { if (!filters().contains(name)) return 0; Filter *filter = new Filter(); filter->setName(name); KisFilterSP f = KisFilterRegistry::instance()->value(name); KisFilterConfigurationSP fc = f->defaultConfiguration(KisGlobalResourcesInterface::instance()); InfoObject *info = new InfoObject(fc); filter->setConfiguration(info); return filter; } QStringList Krita::colorModels() const { QSet colorModelsIds; QList ids = KoColorSpaceRegistry::instance()->colorModelsList(KoColorSpaceRegistry::AllColorSpaces); Q_FOREACH(KoID id, ids) { colorModelsIds << id.id(); } #if QT_VERSION >= QT_VERSION_CHECK(5,14,0) return QStringList(colorModelsIds.begin(), colorModelsIds.end()); #else return QStringList::fromSet(colorModelsIds); #endif } QStringList Krita::colorDepths(const QString &colorModel) const { QSet colorDepthsIds; QList ids = KoColorSpaceRegistry::instance()->colorDepthList(colorModel, KoColorSpaceRegistry::AllColorSpaces); Q_FOREACH(KoID id, ids) { colorDepthsIds << id.id(); } #if QT_VERSION >= QT_VERSION_CHECK(5,14,0) return QStringList(colorDepthsIds.begin(), colorDepthsIds.end()); #else return QStringList::fromSet(colorDepthsIds); #endif } QStringList Krita::filterStrategies() const { return KisFilterStrategyRegistry::instance()->keys(); } QStringList Krita::profiles(const QString &colorModel, const QString &colorDepth) const { QSet profileNames; QString id = KoColorSpaceRegistry::instance()->colorSpaceId(colorModel, colorDepth); QList profiles = KoColorSpaceRegistry::instance()->profilesFor(id); Q_FOREACH(const KoColorProfile *profile, profiles) { profileNames << profile->name(); } #if QT_VERSION >= QT_VERSION_CHECK(5,14,0) QStringList r(profileNames.begin(), profileNames.end()); #else QStringList r = QStringList::fromSet(profileNames); #endif r.sort(); return r; } bool Krita::addProfile(const QString &profilePath) { KoColorSpaceEngine *iccEngine = KoColorSpaceEngineRegistry::instance()->get("icc"); return iccEngine->addProfile(profilePath); } Notifier* Krita::notifier() const { return d->notifier; } QString Krita::version() const { return KritaVersionWrapper::versionString(true); } QList Krita::views() const { QList ret; foreach(QPointer view, KisPart::instance()->views()) { ret << new View(view); } return ret; } Window *Krita::activeWindow() const { KisMainWindow *mainWindow = KisPart::instance()->currentMainwindow(); if (!mainWindow) { return 0; } return new Window(mainWindow); } QList Krita::windows() const { QList ret; foreach(QPointer mainWin, KisPart::instance()->mainWindows()) { ret << new Window(mainWin); } return ret; } QMap Krita::resources(const QString &type) const { QMap resources; KisResourceModel *resourceModel = KisResourceModelProvider::resourceModel(type); for (int i = 0; i < resourceModel->rowCount(); ++i) { QModelIndex idx = resourceModel->index(i, 0); int id = resourceModel->data(idx, Qt::UserRole + KisResourceModel::Id).toInt(); QString name = resourceModel->data(idx, Qt::UserRole + KisResourceModel::Name).toString(); QString filename = resourceModel->data(idx, Qt::UserRole + KisResourceModel::Filename).toString(); QImage image = resourceModel->data(idx, Qt::UserRole + KisResourceModel::Thumbnail).value(); resources[name] = new Resource(id, type, name, filename, image, 0); } return resources; } QStringList Krita::recentDocuments() const { KConfigGroup grp = KSharedConfig::openConfig()->group(QString("RecentFiles")); QStringList keys = grp.keyList(); QStringList recentDocuments; for(int i = 0; i <= keys.filter("File").count(); i++) recentDocuments << grp.readEntry(QString("File%1").arg(i), QString("")); return recentDocuments; } Document* Krita::createDocument(int width, int height, const QString &name, const QString &colorModel, const QString &colorDepth, const QString &profile, double resolution) { KisDocument *document = KisPart::instance()->createDocument(); document->setObjectName(name); KisPart::instance()->addDocument(document, false); const KoColorSpace *cs = KoColorSpaceRegistry::instance()->colorSpace(colorModel, colorDepth, profile); Q_ASSERT(cs); QColor qc(Qt::white); qc.setAlpha(0); KoColor bgColor(qc, cs); if (!document->newImage(name, width, height, cs, bgColor, KisConfig::RASTER_LAYER, 1, "", double(resolution / 72) )) { return 0; } Q_ASSERT(document->image()); Document *doc = new Document(document, true); return doc; } Document* Krita::openDocument(const QString &filename) { KisDocument *document = KisPart::instance()->createDocument(); document->setFileBatchMode(this->batchmode()); KisPart::instance()->addDocument(document); document->openUrl(QUrl::fromLocalFile(filename), KisDocument::DontAddToRecent); document->setFileBatchMode(false); return new Document(document, true); } Window* Krita::openWindow() { KisMainWindow *mw = KisPart::instance()->createMainWindow(); return new Window(mw); } void Krita::addExtension(Extension* extension) { d->extensions.append(extension); } QList< Extension* > Krita::extensions() { return d->extensions; } void Krita::writeSetting(const QString &group, const QString &name, const QString &value) { KConfigGroup grp = KSharedConfig::openConfig()->group(group); grp.writeEntry(name, value); } QString Krita::readSetting(const QString &group, const QString &name, const QString &defaultValue) { KConfigGroup grp = KSharedConfig::openConfig()->group(group); return grp.readEntry(name, defaultValue); } QIcon Krita::icon(QString &iconName) const { return KisIconUtils::loadIcon(iconName); } void Krita::addDockWidgetFactory(DockWidgetFactoryBase* factory) { KoDockRegistry::instance()->add(factory); } Krita* Krita::instance() { if (!s_instance) { s_instance = new Krita; } return s_instance; } /** * Scripter.fromVariant(variant) * variant is a QVariant * returns instance of QObject-subclass * * This is a helper method for PyQt because PyQt cannot cast a variant to a QObject or QWidget */ QObject *Krita::fromVariant(const QVariant& v) { if (v.canConvert< QWidget* >()) { QObject* obj = qvariant_cast< QWidget* >(v); return obj; } else if (v.canConvert< QObject* >()) { QObject* obj = qvariant_cast< QObject* >(v); return obj; } else return 0; } QString Krita::krita_i18n(const QString &text) { return i18n(text.toUtf8().constData()); } +QString Krita::krita_i18nc(const QString &context, const QString &text) +{ + return i18nc(context.toUtf8().constData(), text.toUtf8().constData()); +} + void Krita::mainWindowIsBeingCreated(KisMainWindow *kisWindow) { Q_FOREACH(Extension *extension, d->extensions) { Window window(kisWindow); extension->createActions(&window); } } diff --git a/libs/libkis/Krita.h b/libs/libkis/Krita.h index c7e2df94a3..dc75730349 100644 --- a/libs/libkis/Krita.h +++ b/libs/libkis/Krita.h @@ -1,346 +1,347 @@ /* * 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_KRITA_H #define LIBKIS_KRITA_H #include #include #include "kritalibkis_export.h" #include "libkis.h" #include "Extension.h" #include "Document.h" #include "Window.h" #include "View.h" #include "Notifier.h" /** * Krita is a singleton class that offers the root access to the Krita object hierarchy. * * The Krita.instance() is aliased as two builtins: Scripter and Application. */ class KRITALIBKIS_EXPORT Krita : public QObject { Q_OBJECT public: explicit Krita(QObject *parent = 0); ~Krita() override; public Q_SLOTS: /** * @return the currently active document, if there is one. */ Document* activeDocument() const; /** * @brief setActiveDocument activates the first view that shows the given document * @param value the document we want to activate */ void setActiveDocument(Document* value); /** * @brief batchmode determines whether the script is run in batch mode. If batchmode * is true, scripts should now show messageboxes or dialog boxes. * * Note that this separate from Document.setBatchmode(), which determines whether * export/save option dialogs are shown. * * @return true if the script is run in batchmode */ bool batchmode() const; /** * @brief setBatchmode sets the batchmode to @param value; if true, scripts should * not show dialogs or messageboxes. */ void setBatchmode(bool value); /** * @return return a list of all actions for the currently active mainWindow. */ QList actions() const; /** * @return the action that has been registered under the given name, or 0 if no such action exists. */ QAction *action(const QString &name) const; /** * @return a list of all open Documents */ QList documents() const; /** * @brief Filters are identified by an internal name. This function returns a list * of all existing registered filters. * @return a list of all registered filters */ QStringList filters() const; /** * @brief filter construct a Filter object with a default configuration. * @param name the name of the filter. Use Krita.instance().filters() to get * a list of all possible filters. * @return the filter or None if there is no such filter. */ Filter *filter(const QString &name) const; /** * @brief colorModels creates a list with all color models id's registered. * @return a list of all color models or a empty list if there is no such color models. */ QStringList colorModels() const; /** * @brief colorDepths creates a list with the names of all color depths * compatible with the given color model. * @param colorModel the id of a color model. * @return a list of all color depths or a empty list if there is no such * color depths. */ QStringList colorDepths(const QString &colorModel) const; /** * @brief filterStrategies Retrieves all installed filter strategies. A filter * strategy is used when transforming (scaling, shearing, rotating) an image to * calculate the value of the new pixels. You can use th * @return the id's of all available filters. */ QStringList filterStrategies() const; /** * @brief profiles creates a list with the names of all color profiles compatible * with the given color model and color depth. * @param colorModel A string describing the color model of the image: *
    *
  • A: Alpha mask
  • *
  • RGBA: RGB with alpha channel (The actual order of channels is most often BGR!)
  • *
  • XYZA: XYZ with alpha channel
  • *
  • LABA: LAB with alpha channel
  • *
  • CMYKA: CMYK with alpha channel
  • *
  • GRAYA: Gray with alpha channel
  • *
  • YCbCrA: YCbCr with alpha channel
  • *
* @param colorDepth A string describing the color depth of the image: *
    *
  • U8: unsigned 8 bits integer, the most common type
  • *
  • U16: unsigned 16 bits integer
  • *
  • F16: half, 16 bits floating point. Only available if Krita was built with OpenEXR
  • *
  • F32: 32 bits floating point
  • *
* @return a list with valid names */ QStringList profiles(const QString &colorModel, const QString &colorDepth) const; /** * @brief addProfile load the given profile into the profile registry. * @param profilePath the path to the profile. * @return true if adding the profile succeeded. */ bool addProfile(const QString &profilePath); /** * @brief notifier the Notifier singleton emits signals when documents are opened and * closed, the configuration changes, views are opened and closed or windows are opened. * @return the notifier object */ Notifier *notifier() const; /** * @brief version Determine the version of Krita * * Usage: print(Application.version ()) * * @return the version string including git sha1 if Krita was built from git */ QString version() const; /** * @return a list of all views. A Document can be shown in more than one view. */ QList views() const; /** * @return the currently active window or None if there is no window */ Window *activeWindow() const; /** * @return a list of all windows */ QList windows() const; /** * @brief resources returns a list of Resource objects of the given type * @param type Valid types are: * *
    *
  • pattern
  • *
  • gradient
  • *
  • brush
  • *
  • preset
  • *
  • palette
  • *
  • workspace
  • *
*/ QMap resources(const QString &type) const; /** * @brief return all recent documents registered in the RecentFiles group of the kritarc */ QStringList recentDocuments() const; /** * @brief createDocument creates a new document and image and registers * the document with the Krita application. * * Unless you explicitly call Document::close() the document will remain * known to the Krita document registry. The document and its image will * only be deleted when Krita exits. * * The document will have one transparent layer. * * To create a new document and show it, do something like: @code from Krita import * def add_document_to_window(): d = Application.createDocument(100, 100, "Test", "RGBA", "U8", "", 120.0) Application.activeWindow().addView(d) add_document_to_window() @endcode * * @param width the width in pixels * @param height the height in pixels * @param name the name of the image (not the filename of the document) * @param colorModel A string describing the color model of the image: *
    *
  • A: Alpha mask
  • *
  • RGBA: RGB with alpha channel (The actual order of channels is most often BGR!)
  • *
  • XYZA: XYZ with alpha channel
  • *
  • LABA: LAB with alpha channel
  • *
  • CMYKA: CMYK with alpha channel
  • *
  • GRAYA: Gray with alpha channel
  • *
  • YCbCrA: YCbCr with alpha channel
  • *
* @param colorDepth A string describing the color depth of the image: *
    *
  • U8: unsigned 8 bits integer, the most common type
  • *
  • U16: unsigned 16 bits integer
  • *
  • F16: half, 16 bits floating point. Only available if Krita was built with OpenEXR
  • *
  • F32: 32 bits floating point
  • *
* @param profile The name of an icc profile that is known to Krita. If an empty string is passed, the default is * taken. * @param resolution the resolution in points per inch. * @return the created document. */ Document *createDocument(int width, int height, const QString &name, const QString &colorModel, const QString &colorDepth, const QString &profile, double resolution); /** * @brief openDocument creates a new Document, registers it with the Krita application and loads the given file. * @param filename the file to open in the document * @return the document */ Document *openDocument(const QString &filename); /** * @brief openWindow create a new main window. The window is not shown by default. */ Window *openWindow(); /** * @brief addExtension add the given plugin to Krita. There will be a single instance of each Extension in the Krita process. * @param extension the extension to add. */ void addExtension(Extension* extension); /** * return a list with all registered extension objects. */ QList extensions(); /** * @brief addDockWidgetFactory Add the given docker factory to the application. For scripts * loaded on startup, this means that every window will have one of the dockers created by the * factory. * @param factory The factory object. */ void addDockWidgetFactory(DockWidgetFactoryBase* factory ); /** * @brief writeSetting write the given setting under the given name to the kritarc file in * the given settings group. * @param group The group the setting belongs to. If empty, then the setting is written in the * general section * @param name The name of the setting * @param value The value of the setting. Script settings are always written as strings. */ void writeSetting(const QString &group, const QString &name, const QString &value); /** * @brief readSetting read the given setting value from the kritarc file. * @param group The group the setting is part of. If empty, then the setting is read from * the general group. * @param name The name of the setting * @param defaultValue The default value of the setting * @return a string representing the setting. */ QString readSetting(const QString &group, const QString &name, const QString &defaultValue); /** * @brief icon * This allows you to get icons from Krita's internal icons. * @param iconName name of the icon. * @return the icon related to this name. */ QIcon icon(QString &iconName) const; /** * @brief instance retrieve the singleton instance of the Application object. */ static Krita* instance(); // Internal only: for use with mikro.py static QObject *fromVariant(const QVariant& v); static QString krita_i18n(const QString &text); + static QString krita_i18nc(const QString &context, const QString &text); private Q_SLOTS: /// This is called from the constructor of the window, before the xmlgui file is loaded void mainWindowIsBeingCreated(KisMainWindow *window); private: struct Private; Private *const d; static Krita* s_instance; }; Q_DECLARE_METATYPE(Notifier*); #endif // LIBKIS_KRITA_H diff --git a/libs/libkis/Scratchpad.cpp b/libs/libkis/Scratchpad.cpp new file mode 100644 index 0000000000..a900d2b718 --- /dev/null +++ b/libs/libkis/Scratchpad.cpp @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2020 Scott Petrovic + * + * 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 "Scratchpad.h" +#include +#include +#include "kis_scratch_pad.h" +#include "Resource.h" +#include "View.h" +#include "Canvas.h" +#include +#include + +#include + +Scratchpad::Scratchpad(View *view, const QColor & defaultColor, QWidget *parent) + : KisScratchPad(parent) +{ + KisScratchPad::setupScratchPad(view->view()->resourceProvider(), defaultColor); + KisScratchPad::setMinimumSize(50, 50); +} + +Scratchpad::~Scratchpad() +{ +} + +void Scratchpad::setModeManually(bool value) +{ + KisScratchPad::setModeManually(value); +} + +void Scratchpad::setMode(QString modeType) +{ + KisScratchPad::setModeType(modeType); +} + +void Scratchpad::loadScratchpad(QImage image) +{ + KisScratchPad::loadScratchpadImage(image); +} + +QImage Scratchpad::copyScratchPadImage() +{ + return KisScratchPad::copyScratchpadImageData(); +} + +void Scratchpad::clear() +{ + // need ability to set color + KisScratchPad::fillDefault(); +} + +void Scratchpad::setFillColor(QColor color) +{ + KisScratchPad::setFillColor(color); +} diff --git a/libs/libkis/Scratchpad.h b/libs/libkis/Scratchpad.h new file mode 100644 index 0000000000..96c79e3fb2 --- /dev/null +++ b/libs/libkis/Scratchpad.h @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2020 Scott Petrovic + * + * 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_SCRATCHPAD_H +#define LIBKIS_SCRATCHPAD_H + +#include +#include +#include +#include "kritalibkis_export.h" +#include "libkis.h" +#include "kis_scratch_pad.h" +#include "View.h" + +class KoCanvasBase; +class Canvas; // This comes from Python. This would be maybe better +class KisView; + +/** + * @brief The Scratchpad class + * A scratchpad is a type of blank canvas area that can be painted on + * with the normal painting devices + * + */ +class KRITALIBKIS_EXPORT Scratchpad: public KisScratchPad +{ + Q_OBJECT +public: + Scratchpad(View *view, const QColor & defaultColor, QWidget *parent = 0); + ~Scratchpad(); + + +public Q_SLOTS: + + /** + * clears out scratchpad with color specfified set during setup + */ + void clear(); + + void setFillColor(QColor color); + + /** Switches between a GUI controlling the current mode and when mouse clicks control mode + * Setting to true allows GUI to control the mode with explicity setting mode + */ + void setModeManually(bool value); + + + /// Manually set what mode scratchpad is in. Ignored if "setModeManually is set to false + void setMode(QString modeName); + + /// load scratchpad + void loadScratchpad(QImage image); + + /// take what is on scratchpad area and grab image + QImage copyScratchPadImage(); + +}; + +#endif // LIBKIS_SCRATCHPAD_H + diff --git a/libs/libkis/Selection.cpp b/libs/libkis/Selection.cpp index 7c17819288..3c4989c758 100644 --- a/libs/libkis/Selection.cpp +++ b/libs/libkis/Selection.cpp @@ -1,334 +1,334 @@ /* * 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 "Selection.h" #include #include "kis_iterator_ng.h" #include #include #include #include #include #include #include #include struct Selection::Private { Private() {} KisSelectionSP selection; }; Selection::Selection(KisSelectionSP selection, QObject *parent) : QObject(parent) , d(new Private) { d->selection = selection; } Selection::Selection(QObject *parent) : QObject(parent) , d(new Private) { d->selection = new KisSelection(); } Selection::~Selection() { delete d; } bool Selection::operator==(const Selection &other) const { return (d->selection == other.d->selection); } bool Selection::operator!=(const Selection &other) const { return !(operator==(other)); } Selection *Selection::duplicate() const { return new Selection(d->selection ? new KisSelection(*d->selection) : new KisSelection()); } int Selection::width() const { if (!d->selection) return 0; return d->selection->selectedExactRect().width(); } int Selection::height() const { if (!d->selection) return 0; return d->selection->selectedExactRect().height(); } int Selection::x() const { if (!d->selection) return 0; int xPos = d->selection->x(); - if (d->selection->hasPixelSelection()) { + if (d->selection->hasNonEmptyPixelSelection()) { xPos = d->selection->selectedExactRect().x(); } return xPos; } int Selection::y() const { if (!d->selection) return 0; int yPos = d->selection->y(); - if (d->selection->hasPixelSelection()) { + if (d->selection->hasNonEmptyPixelSelection()) { yPos = d->selection->selectedExactRect().y(); } return yPos; } void Selection::move(int x, int y) { if (!d->selection) return; d->selection->pixelSelection()->moveTo(QPoint(x, y)); } void Selection::clear() { if (!d->selection) return; d->selection->clear(); } void Selection::contract(int value) { if (!d->selection) return; d->selection->pixelSelection()->select(QRect(x(), y(), width() - value, height() - value)); } void Selection::copy(Node *node) { if (!node) return; if (!d->selection) return; if (node->node()->exactBounds().isEmpty()) return; if (!node->node()->hasEditablePaintDevice()) return; KisPaintDeviceSP dev = node->node()->paintDevice(); KisPaintDeviceSP clip = new KisPaintDevice(dev->colorSpace()); KisPaintDeviceSP selectionProjection = d->selection->projection(); const KoColorSpace *cs = clip->colorSpace(); const KoColorSpace *selCs = d->selection->projection()->colorSpace(); QRect rc = d->selection->selectedExactRect(); KisPainter::copyAreaOptimized(QPoint(), dev, clip, rc); KisHLineIteratorSP layerIt = clip->createHLineIteratorNG(0, 0, rc.width()); KisHLineConstIteratorSP selectionIt = selectionProjection->createHLineIteratorNG(rc.x(), rc.y(), rc.width()); for (qint32 y = 0; y < rc.height(); y++) { for (qint32 x = 0; x < rc.width(); x++) { qreal dstAlpha = cs->opacityF(layerIt->rawData()); qreal sel = selCs->opacityF(selectionIt->oldRawData()); qreal newAlpha = sel * dstAlpha / (1.0 - dstAlpha + sel * dstAlpha); float mask = newAlpha / dstAlpha; cs->applyAlphaNormedFloatMask(layerIt->rawData(), &mask, 1); layerIt->nextPixel(); selectionIt->nextPixel(); } layerIt->nextRow(); selectionIt->nextRow(); } KisClipboard::instance()->setClip(clip, rc.topLeft()); } void Selection::cut(Node* node) { if (!node) return; if (!d->selection) return; if (node->node()->exactBounds().isEmpty()) return; if (!node->node()->hasEditablePaintDevice()) return; KisPaintDeviceSP dev = node->node()->paintDevice(); copy(node); dev->clearSelection(d->selection); node->node()->setDirty(d->selection->selectedExactRect()); } void Selection::paste(Node *destination, int x, int y) { if (!destination) return; if (!d->selection) return; if (!KisClipboard::instance()->hasClip()) return; KisPaintDeviceSP src = KisClipboard::instance()->clip(QRect(), false); KisPaintDeviceSP dst = destination->node()->paintDevice(); if (!dst) return; KisPainter::copyAreaOptimized(QPoint(x, y), src, dst, src->exactBounds(), d->selection); destination->node()->setDirty(); } void Selection::erode() { if (!d->selection) return; KisErodeSelectionFilter esf; QRect rc = esf.changeRect(d->selection->selectedExactRect(), d->selection->pixelSelection()->defaultBounds()); esf.process(d->selection->pixelSelection(), rc); } void Selection::dilate() { if (!d->selection) return; KisDilateSelectionFilter dsf; QRect rc = dsf.changeRect(d->selection->selectedExactRect(), d->selection->pixelSelection()->defaultBounds()); dsf.process(d->selection->pixelSelection(), rc); } void Selection::border(int xRadius, int yRadius) { if (!d->selection) return; KisBorderSelectionFilter sf(xRadius, yRadius); QRect rc = sf.changeRect(d->selection->selectedExactRect(), d->selection->pixelSelection()->defaultBounds()); sf.process(d->selection->pixelSelection(), rc); } void Selection::feather(int radius) { if (!d->selection) return; KisFeatherSelectionFilter fsf(radius); QRect rc = fsf.changeRect(d->selection->selectedExactRect(), d->selection->pixelSelection()->defaultBounds()); fsf.process(d->selection->pixelSelection(), rc); } void Selection::grow(int xradius, int yradius) { if (!d->selection) return; KisGrowSelectionFilter gsf(xradius, yradius); QRect rc = gsf.changeRect(d->selection->selectedExactRect(), d->selection->pixelSelection()->defaultBounds()); gsf.process(d->selection->pixelSelection(), rc); } void Selection::shrink(int xRadius, int yRadius, bool edgeLock) { if (!d->selection) return; KisShrinkSelectionFilter sf(xRadius, yRadius, edgeLock); QRect rc = sf.changeRect(d->selection->selectedExactRect(), d->selection->pixelSelection()->defaultBounds()); sf.process(d->selection->pixelSelection(), rc); } void Selection::smooth() { if (!d->selection) return; KisSmoothSelectionFilter sf; QRect rc = sf.changeRect(d->selection->selectedExactRect(), d->selection->pixelSelection()->defaultBounds()); sf.process(d->selection->pixelSelection(), rc); } void Selection::invert() { if (!d->selection) return; KisInvertSelectionFilter sf; QRect rc = sf.changeRect(d->selection->selectedExactRect(), d->selection->pixelSelection()->defaultBounds()); sf.process(d->selection->pixelSelection(), rc); } void Selection::resize(int w, int h) { if (!d->selection) return; d->selection->pixelSelection()->select(QRect(x(), y(), w, h)); } void Selection::select(int x, int y, int w, int h, int value) { if (!d->selection) return; d->selection->pixelSelection()->select(QRect(x, y, w, h), value); } void Selection::selectAll(Node *node, int value) { if (!d->selection) return; d->selection->pixelSelection()->select(node->node()->exactBounds(), value); } void Selection::replace(Selection *selection) { if (!d->selection) return; d->selection->pixelSelection()->applySelection(selection->selection()->pixelSelection(), SELECTION_REPLACE); } void Selection::add(Selection *selection) { if (!d->selection) return; d->selection->pixelSelection()->applySelection(selection->selection()->pixelSelection(), SELECTION_ADD); } void Selection::subtract(Selection *selection) { if (!d->selection) return; d->selection->pixelSelection()->applySelection(selection->selection()->pixelSelection(), SELECTION_SUBTRACT); } void Selection::intersect(Selection *selection) { if (!d->selection) return; d->selection->pixelSelection()->applySelection(selection->selection()->pixelSelection(), SELECTION_INTERSECT); } void Selection::symmetricdifference(Selection *selection) { if (!d->selection) return; d->selection->pixelSelection()->applySelection(selection->selection()->pixelSelection(), SELECTION_SYMMETRICDIFFERENCE); } QByteArray Selection::pixelData(int x, int y, int w, int h) const { QByteArray ba; if (!d->selection) return ba; KisPaintDeviceSP dev = d->selection->projection(); quint8 *data = new quint8[w * h]; dev->readBytes(data, x, y, w, h); ba = QByteArray((const char*)data, (int)(w * h)); delete[] data; return ba; } void Selection::setPixelData(QByteArray value, int x, int y, int w, int h) { if (!d->selection) return; KisPixelSelectionSP dev = d->selection->pixelSelection(); if (!dev) return; dev->writeBytes((const quint8*)value.constData(), x, y, w, h); } KisSelectionSP Selection::selection() const { return d->selection; } diff --git a/libs/libkis/View.cpp b/libs/libkis/View.cpp index c72221585e..6152d92113 100644 --- a/libs/libkis/View.cpp +++ b/libs/libkis/View.cpp @@ -1,279 +1,298 @@ /* * 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 "View.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "Document.h" #include "Canvas.h" +#include "Scratchpad.h" #include "Window.h" #include "Resource.h" #include "ManagedColor.h" #include "LibKisUtils.h" struct View::Private { Private() {} QPointer view; + + QList scratchpads; }; View::View(KisView* view, QObject *parent) : QObject(parent) , d(new Private) { d->view = view; } View::~View() { delete d; } bool View::operator==(const View &other) const { return (d->view == other.d->view); } bool View::operator!=(const View &other) const { return !(operator==(other)); } Window* View::window() const { if (!d->view) return 0; KisMainWindow *mainwin = d->view->mainWindow(); Window *win = new Window(mainwin); return win; } Document* View::document() const { if (!d->view) return 0; Document *doc = new Document(d->view->document(), false); return doc; } void View::setDocument(Document *document) { if (!d->view || !document || !document->document()) return; d->view = d->view->replaceBy(document->document()); } bool View::visible() const { if (!d->view) return false; return d->view->isVisible(); } void View::setVisible() { if (!d->view) return; KisMainWindow *mainwin = d->view->mainWindow(); mainwin->setActiveView(d->view); mainwin->subWindowActivated(); } Canvas* View::canvas() const { if (!d->view) return 0; Canvas *c = new Canvas(d->view->canvasBase()); return c; } KisView *View::view() { return d->view; } void View::activateResource(Resource *resource) { if (!d->view) return; if (!resource) return; KoResourceSP r = resource->resource(); if (!r) return; if (r.dynamicCast()) { QVariant v; v.setValue(r); d->view->canvasBase()->resourceManager()->setResource(KisCanvasResourceProvider::CurrentPattern, v); } else if (r.dynamicCast()) { QVariant v; v.setValue(r); d->view->canvasBase()->resourceManager()->setResource(KisCanvasResourceProvider::CurrentGradient, v); } else if (r.dynamicCast()) { d->view->viewManager()->paintOpBox()->resourceSelected(r); } } +Scratchpad *View::createScratchpad(QColor bgColor) +{ + if(view()) { + d->scratchpads.append(new Scratchpad(this->canvas()->view(), bgColor)); + } + return d->scratchpads.last(); +} + ManagedColor *View::foregroundColor() const { if (!d->view) return 0; return new ManagedColor(d->view->resourceProvider()->fgColor()); } void View::setForeGroundColor(ManagedColor *color) { if (!d->view) return; d->view->resourceProvider()->setFGColor(color->color()); } ManagedColor *View::backgroundColor() const { if (!d->view) return 0; return new ManagedColor(d->view->resourceProvider()->bgColor()); } void View::setBackGroundColor(ManagedColor *color) { if (!d->view) return; d->view->resourceProvider()->setBGColor(color->color()); } Resource *View::currentBrushPreset() const { if (!d->view) return 0; return new Resource(d->view->resourceProvider()->currentPreset(), ResourceType::PaintOpPresets); } void View::setCurrentBrushPreset(Resource *resource) { activateResource(resource); } Resource *View::currentPattern() const { if (!d->view) return 0; return new Resource(d->view->resourceProvider()->currentPattern(), ResourceType::Patterns); } void View::setCurrentPattern(Resource *resource) { activateResource(resource); } Resource *View::currentGradient() const { if (!d->view) return 0; return new Resource(d->view->resourceProvider()->currentGradient(), ResourceType::Gradients); } void View::setCurrentGradient(Resource *resource) { activateResource(resource); } QString View::currentBlendingMode() const { if (!d->view) return ""; return d->view->resourceProvider()->currentCompositeOp(); } void View::setCurrentBlendingMode(const QString &blendingMode) { if (!d->view) return; d->view->resourceProvider()->setCurrentCompositeOp(blendingMode); } float View::HDRExposure() const { if (!d->view) return 0.0; return d->view->resourceProvider()->HDRExposure(); } void View::setHDRExposure(float exposure) { if (!d->view) return; d->view->resourceProvider()->setHDRExposure(exposure); } float View::HDRGamma() const { if (!d->view) return 0.0; return d->view->resourceProvider()->HDRGamma(); } void View::setHDRGamma(float gamma) { if (!d->view) return; d->view->resourceProvider()->setHDRGamma(gamma); } qreal View::paintingOpacity() const { if (!d->view) return 0.0; return d->view->resourceProvider()->opacity(); } void View::setPaintingOpacity(qreal opacity) { if (!d->view) return; d->view->resourceProvider()->setOpacity(opacity); } qreal View::brushSize() const { if (!d->view) return 0.0; return d->view->resourceProvider()->size(); } void View::setBrushSize(qreal brushSize) { if (!d->view) return; d->view->resourceProvider()->setSize(brushSize); } qreal View::paintingFlow() const { if (!d->view) return 0.0; return d->view->resourceProvider()->flow(); } void View::setPaintingFlow(qreal flow) { if (!d->view) return; d->view->resourceProvider()->setFlow(flow); } QList View::selectedNodes() const { if (!d->view) return QList(); if (!d->view->viewManager()) return QList(); if (!d->view->viewManager()->nodeManager()) return QList(); KisNodeList selectedNodes = d->view->viewManager()->nodeManager()->selectedNodes(); return LibKisUtils::createNodeList(selectedNodes, d->view->image()); } + +QList View::scratchpads() const +{ + if (!d->view) return QList(); + if (!d->view->viewManager()) return QList(); + + return d->scratchpads; +} diff --git a/libs/libkis/View.h b/libs/libkis/View.h index 964aed2fee..a70950d5c7 100644 --- a/libs/libkis/View.h +++ b/libs/libkis/View.h @@ -1,159 +1,173 @@ /* * 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_VIEW_H #define LIBKIS_VIEW_H #include #include "kritalibkis_export.h" #include "libkis.h" class ManagedColor; class Resource; +class Scratchpad; class Node; class KisView; /** * View represents one view on a document. A document can be * shown in more than one view at a time. */ class KRITALIBKIS_EXPORT View : public QObject { Q_OBJECT Q_DISABLE_COPY(View) public: explicit View(KisView *view, QObject *parent = 0); ~View() override; bool operator==(const View &other) const; bool operator!=(const View &other) const; public Q_SLOTS: /** * @return the window this view is shown in. */ Window* window() const; /** * @return the document this view is showing. */ Document* document() const; /** * Reset the view to show @p document. */ void setDocument(Document *document); /** * @return true if the current view is visible, false if not. */ bool visible() const; /** * Make the current view visible. */ void setVisible(); /** * @return the canvas this view is showing. The canvas controls * things like zoom and rotation. */ Canvas* canvas() const; /** * @brief activateResource activates the given resource. * @param resource: a pattern, gradient or paintop preset */ void activateResource(Resource *resource); + /** + * @brief creates a scratchpad widget to draw on. + * It is stored in the scratchpad list for reference + */ + Scratchpad *createScratchpad(QColor bgColor); + /** * @brief foregroundColor allows access to the currently active color. * This is nominally per canvas/view, but in practice per mainwindow. * @code color = Application.activeWindow().activeView().foregroundColor() components = color.components() components[0] = 1.0 components[1] = 0.6 components[2] = 0.7 color.setComponents(components) Application.activeWindow().activeView().setForeGroundColor(color) * @endcode */ ManagedColor *foregroundColor() const; void setForeGroundColor(ManagedColor *color); ManagedColor *backgroundColor() const; void setBackGroundColor(ManagedColor *color); Resource *currentBrushPreset() const; void setCurrentBrushPreset(Resource *resource); Resource *currentPattern() const; void setCurrentPattern(Resource *resource); Resource *currentGradient() const; void setCurrentGradient(Resource *resource); QString currentBlendingMode() const; void setCurrentBlendingMode(const QString &blendingMode); float HDRExposure() const; void setHDRExposure(float exposure); float HDRGamma() const; void setHDRGamma(float gamma); qreal paintingOpacity() const; void setPaintingOpacity(qreal opacity); qreal brushSize() const; void setBrushSize(qreal brushSize); qreal paintingFlow() const; void setPaintingFlow(qreal flow); /** * @brief selectedNodes returns a list of Nodes that are selected in this view. * * @code from krita import * w = Krita.instance().activeWindow() v = w.activeView() selected_nodes = v.selectedNodes() print(selected_nodes) @endcode * * * @return a list of Node objects which may be empty. */ QList selectedNodes() const; + + /** + * @brief Stores scratchpad widgets to draw on + */ + QList scratchpads() const; + private: friend class Window; + friend class Scratchpad; KisView *view(); struct Private; Private *const d; }; #endif // LIBKIS_VIEW_H diff --git a/libs/libkis/libkis.h b/libs/libkis/libkis.h index fba3852456..b665944af7 100644 --- a/libs/libkis/libkis.h +++ b/libs/libkis/libkis.h @@ -1,36 +1,37 @@ #include #include #include #include #include #include class Canvas; class Channel; class ColorDepth; class ColorManager; class ColorModel; class ColorProfile; class DockWidget; class DockWidgetFactoryBase; class Document; class Filter; class InfoObject; class Krita; class Node; class Notifier; class Resource; +class Scratchpad; class Selection; class View; class Extension; class Window; class Shape; class GroupShape; class PaintLayer; class CloneLayer; class GroupLayer; class FilterLayer; class FillLayer; class FileLayer; class VectorLayer; diff --git a/libs/libqml/plugins/kritasketchplugin/models/LayerModel.cpp b/libs/libqml/plugins/kritasketchplugin/models/LayerModel.cpp index c9fe7bc192..b061f065c6 100644 --- a/libs/libqml/plugins/kritasketchplugin/models/LayerModel.cpp +++ b/libs/libqml/plugins/kritasketchplugin/models/LayerModel.cpp @@ -1,977 +1,977 @@ /* 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 "LayerModel.h" #include "LayerThumbProvider.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 "KisSelectionActionsAdapter.h" #include struct LayerModelMetaInfo { LayerModelMetaInfo() : canMoveUp(false) , canMoveRight(false) , canMoveDown(false) , canMoveLeft(false) , depth(-1) {} bool canMoveUp; bool canMoveRight; bool canMoveDown; bool canMoveLeft; int depth; }; class LayerModel::Private { public: Private(LayerModel* qq) : q(qq) , nodeModel(new KisNodeModel(qq)) , aboutToRemoveRoots(false) , canvas(0) , nodeManager(0) , image(0) , activeNode(0) , declarativeEngine(0) , thumbProvider(0) , updateActiveLayerWithNewFilterConfigTimer(new QTimer(qq)) , imageChangedTimer(new QTimer(qq)) { QList tmpFilters = KisFilterRegistry::instance()->values(); Q_FOREACH (const KisFilterSP& filter, tmpFilters) { filters[filter.data()->id()] = filter.data(); } updateActiveLayerWithNewFilterConfigTimer->setInterval(0); updateActiveLayerWithNewFilterConfigTimer->setSingleShot(true); connect(updateActiveLayerWithNewFilterConfigTimer, SIGNAL(timeout()), qq, SLOT(updateActiveLayerWithNewFilterConfig())); imageChangedTimer->setInterval(250); imageChangedTimer->setSingleShot(true); connect(imageChangedTimer, SIGNAL(timeout()), qq, SLOT(imageHasChanged())); } LayerModel* q; QList layers; QHash layerMeta; KisNodeModel* nodeModel; bool aboutToRemoveRoots; KisViewManager* view; KisCanvas2* canvas; QScopedPointer selectionActionsAdapter; QPointer nodeManager; KisImageWSP image; KisNodeSP activeNode; QQmlEngine* declarativeEngine; LayerThumbProvider* thumbProvider; QHash filters; KisFilterConfigurationSP newConfig; QTimer* updateActiveLayerWithNewFilterConfigTimer; QTimer* imageChangedTimer; static int counter() { static int count = 0; return count++; } static QStringList layerClassNames() { QStringList list; list << "KisGroupLayer"; list << "KisPaintLayer"; list << "KisFilterMask"; list << "KisAdjustmentLayer"; return list; } int deepChildCount(KisNodeSP layer) { quint32 childCount = layer->childCount(); QList children = layer->childNodes(layerClassNames(), KoProperties()); for(quint32 i = 0; i < childCount; ++i) childCount += deepChildCount(children.at(i)); return childCount; } void rebuildLayerList(KisNodeSP layer = 0) { bool refreshingFromRoot = false; if (!image) { layers.clear(); return; } if (layer == 0) { refreshingFromRoot = true; layers.clear(); layer = image->rootLayer(); } // implementation node: The root node is not a visible node, and so // is never added to the list of layers QList children = layer->childNodes(layerClassNames(), KoProperties()); if (children.count() == 0) return; for(quint32 i = children.count(); i > 0; --i) { layers << children.at(i-1); rebuildLayerList(children.at(i-1)); } if (refreshingFromRoot) refreshLayerMovementAbilities(); } void refreshLayerMovementAbilities() { layerMeta.clear(); if (layers.count() == 0) return; for(int i = 0; i < layers.count(); ++i) { const KisNodeSP layer = layers.at(i); LayerModelMetaInfo ability; if (i > 0) ability.canMoveUp = true; if (i < layers.count() - 1) ability.canMoveDown = true; KisNodeSP parent = layer; while(parent) { ++ability.depth; parent = parent->parent(); } if (ability.depth > 1) ability.canMoveLeft = true; if (i < layers.count() - 1 && qobject_cast(layers.at(i + 1).constData())) ability.canMoveRight = true; layerMeta[layer] = ability; } } }; LayerModel::LayerModel(QObject* parent) : QAbstractListModel(parent) , d(new Private(this)) { connect(d->nodeModel, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), this, SLOT(source_rowsAboutToBeInserted(QModelIndex,int,int))); connect(d->nodeModel, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(source_rowsInserted(QModelIndex,int,int))); connect(d->nodeModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), this, SLOT(source_rowsAboutToBeRemoved(QModelIndex,int,int))); connect(d->nodeModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(source_rowsRemoved(QModelIndex,int,int))); connect(d->nodeModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(source_dataChanged(QModelIndex,QModelIndex))); connect(d->nodeModel, SIGNAL(modelReset()), this, SLOT(source_modelReset())); connect(d->nodeModel, SIGNAL(layoutAboutToBeChanged()), this, SIGNAL(layoutAboutToBeChanged())); connect(d->nodeModel, SIGNAL(layoutChanged()), this, SIGNAL(layoutChanged())); } LayerModel::~LayerModel() { delete d; } QHash LayerModel::roleNames() const { QHash roles; roles[IconRole] = "icon"; roles[NameRole] = "name"; roles[ActiveLayerRole] = "activeLayer"; roles[OpacityRole] = "opacity"; roles[PercentOpacityRole] = "percentOpacity"; roles[VisibleRole] = "visible"; roles[CompositeDetailsRole] = "compositeDetails"; roles[FilterRole] = "filter"; roles[ChildCountRole] = "childCount"; roles[DeepChildCountRole] = "deepChildCount"; roles[DepthRole] = "depth"; roles[PreviousItemDepthRole] = "previousItemDepth"; roles[NextItemDepthRole] = "nextItemDepth"; roles[CanMoveDownRole] = "canMoveDown"; roles[CanMoveLeftRole] = "canMoveLeft"; roles[CanMoveRightRole] = "canMoveRight"; roles[CanMoveUpRole] = "canMoveUp"; return roles; } QObject* LayerModel::view() const { return d->view; } void LayerModel::setView(QObject *newView) { KisViewManager* view = qobject_cast(newView); // This has to happen very early, and we will be filling it back up again soon anyway... if (d->canvas) { d->canvas->disconnectCanvasObserver(this); disconnect(d->image, 0, this, 0); disconnect(d->nodeManager, 0, this, 0); disconnect(d->nodeModel, 0, d->nodeManager, 0); disconnect(d->nodeModel, SIGNAL(nodeActivated(KisNodeSP)), this, SLOT(currentNodeChanged(KisNodeSP))); d->image = 0; d->nodeManager = 0; d->layers.clear(); d->activeNode.clear(); d->canvas = 0; d->nodeModel->setDummiesFacade(0, 0, 0, 0, 0); d->selectionActionsAdapter.reset(); } d->view = view; if (!d->view) { return; } d->canvas = view->canvasBase(); d->thumbProvider = new LayerThumbProvider(); d->thumbProvider->setLayerModel(this); d->thumbProvider->setLayerID(Private::counter()); // QT5TODO: results in a crash d->declarativeEngine->addImageProvider(QString("layerthumb%1").arg(d->thumbProvider->layerID()), d->thumbProvider); if (d->canvas) { d->image = d->canvas->imageView()->image(); d->nodeManager = d->canvas->viewManager()->nodeManager(); KisDummiesFacadeBase *kritaDummiesFacade = dynamic_cast(d->canvas->imageView()->document()->shapeController()); KisShapeController *shapeController = dynamic_cast(d->canvas->imageView()->document()->shapeController()); d->selectionActionsAdapter.reset(new KisSelectionActionsAdapter(d->canvas->viewManager()->selectionManager())); d->nodeModel->setDummiesFacade(kritaDummiesFacade, d->image, shapeController, d->selectionActionsAdapter.data(), d->nodeManager); connect(d->image, SIGNAL(sigAboutToBeDeleted()), SLOT(notifyImageDeleted())); connect(d->image, SIGNAL(sigNodeChanged(KisNodeSP)), SLOT(nodeChanged(KisNodeSP))); connect(d->image, SIGNAL(sigImageUpdated(QRect)), SLOT(imageChanged())); connect(d->image, SIGNAL(sigRemoveNodeAsync(KisNodeSP)), SLOT(aboutToRemoveNode(KisNodeSP))); // cold start currentNodeChanged(d->nodeManager->activeNode()); // Connection KisNodeManager -> KisLayerBox connect(d->nodeManager, SIGNAL(sigUiNeedChangeActiveNode(KisNodeSP)), this, SLOT(currentNodeChanged(KisNodeSP))); d->rebuildLayerList(); beginResetModel(); endResetModel(); } } QObject* LayerModel::engine() const { return d->declarativeEngine; } void LayerModel::setEngine(QObject* newEngine) { d->declarativeEngine = qobject_cast(newEngine); emit engineChanged(); } QString LayerModel::fullImageThumbUrl() const { return QString("image://layerthumb%1/fullimage/%2").arg(d->thumbProvider->layerID()).arg(QDateTime::currentMSecsSinceEpoch()); } void LayerModel::currentNodeChanged(KisNodeSP newActiveNode) { if (!d->activeNode.isNull()) { QModelIndex oldIndex = d->nodeModel->indexFromNode(d->activeNode); source_dataChanged(oldIndex, oldIndex); } d->activeNode = newActiveNode; emitActiveChanges(); if (!d->activeNode.isNull()) { QModelIndex oldIndex = d->nodeModel->indexFromNode(d->activeNode); source_dataChanged(oldIndex, oldIndex); } } QVariant LayerModel::data(const QModelIndex& index, int role) const { QVariant data; if (index.isValid()) { index.internalPointer(); KisNodeSP node = d->layers.at(index.row()); if (node.isNull()) return data; KisNodeSP parent; switch(role) { case IconRole: if (dynamic_cast(node.constData())) data = QLatin1String("../images/svg/icon-layer_group-black.svg"); else if (dynamic_cast(node.constData())) data = QLatin1String("../images/svg/icon-layer_filter-black.svg"); else if (dynamic_cast(node.constData())) data = QLatin1String("../images/svg/icon-layer_filter-black.svg"); else // We add the currentMSecsSinceEpoch to ensure we force an update (even with cache turned // off, we still apparently get some caching behaviour on delegates in QML) data = QString("image://layerthumb%1/%2/%3").arg(d->thumbProvider->layerID()).arg(index.row()).arg(QDateTime::currentMSecsSinceEpoch()); break; case NameRole: data = node->name(); break; case ActiveLayerRole: data = (node == d->activeNode); break; case OpacityRole: data = node->opacity(); break; case PercentOpacityRole: data = node->percentOpacity(); break; case VisibleRole: data = node->visible(); break; case CompositeDetailsRole: // composite op goes here... if (node->compositeOp()) data = node->compositeOp()->description(); break; case FilterRole: break; case ChildCountRole: data = node->childNodes(d->layerClassNames(), KoProperties()).count(); break; case DeepChildCountRole: data = d->deepChildCount(d->layers.at(index.row())); break; case DepthRole: data = d->layerMeta[node.data()].depth; break; case PreviousItemDepthRole: if (index.row() == 0) data = -1; else data = d->layerMeta[d->layers[index.row() - 1].data()].depth; break; case NextItemDepthRole: if (index.row() == d->layers.count() - 1) data = -1; else data = d->layerMeta[d->layers[index.row() + 1].data()].depth; break; case CanMoveDownRole: data = (node == d->activeNode) && d->layerMeta[node.data()].canMoveDown; break; case CanMoveLeftRole: data = (node == d->activeNode) && d->layerMeta[node.data()].canMoveLeft; break; case CanMoveRightRole: data = (node == d->activeNode) && d->layerMeta[node.data()].canMoveRight; break; case CanMoveUpRole: data = (node == d->activeNode) && d->layerMeta[node.data()].canMoveUp; break; default: break; } } return data; } int LayerModel::rowCount(const QModelIndex& parent) const { if ( parent.isValid() ) { return 0; } return d->layers.count(); } QVariant LayerModel::headerData(int section, Qt::Orientation orientation, int role) const { return QAbstractItemModel::headerData(section, orientation, role); } void LayerModel::setActive(int index) { if (index > -1 && index < d->layers.count()) { KisNodeSP newNode = d->layers.at(index); d->nodeManager->slotUiActivatedNode(newNode); currentNodeChanged(newNode); } } void LayerModel::moveUp() { KisNodeSP node = d->nodeManager->activeNode(); KisNodeSP parent = node->parent(); KisNodeSP grandParent = parent->parent(); if (!d->nodeManager->activeNode()->nextSibling()) { //dbgKrita << "Active node apparently has no next sibling, however that has happened..."; if (!grandParent) return; //dbgKrita << "Node has grandparent"; if (!grandParent->parent() && node->inherits("KisMask")) return; //dbgKrita << "Node isn't a mask"; d->nodeManager->moveNodeAt(node, grandParent, grandParent->index(parent) + 1); } else { //dbgKrita << "Move node directly"; d->nodeManager->lowerNode(); } } void LayerModel::moveDown() { KisNodeSP node = d->nodeManager->activeNode(); KisNodeSP parent = node->parent(); KisNodeSP grandParent = parent->parent(); if (!d->nodeManager->activeNode()->prevSibling()) { //dbgKrita << "Active node apparently has no previous sibling, however that has happened..."; if (!grandParent) return; //dbgKrita << "Node has grandparent"; if (!grandParent->parent() && node->inherits("KisMask")) return; //dbgKrita << "Node isn't a mask"; d->nodeManager->moveNodeAt(node, grandParent, grandParent->index(parent)); } else { //dbgKrita << "Move node directly"; d->nodeManager->raiseNode(); } } void LayerModel::moveLeft() { KisNodeSP node = d->nodeManager->activeNode(); KisNodeSP parent = node->parent(); KisNodeSP grandParent = parent->parent(); quint16 nodeIndex = parent->index(node); if (!grandParent) return; if (!grandParent->parent() && node->inherits("KisMask")) return; if (nodeIndex <= parent->childCount() / 2) { d->nodeManager->moveNodeAt(node, grandParent, grandParent->index(parent)); } else { d->nodeManager->moveNodeAt(node, grandParent, grandParent->index(parent) + 1); } } void LayerModel::moveRight() { KisNodeSP node = d->nodeManager->activeNode(); KisNodeSP parent = d->nodeManager->activeNode()->parent(); KisNodeSP newParent; int nodeIndex = parent->index(node); int indexAbove = nodeIndex + 1; int indexBelow = nodeIndex - 1; if (parent->at(indexBelow) && parent->at(indexBelow)->allowAsChild(node)) { newParent = parent->at(indexBelow); d->nodeManager->moveNodeAt(node, newParent, newParent->childCount()); } else if (parent->at(indexAbove) && parent->at(indexAbove)->allowAsChild(node)) { newParent = parent->at(indexAbove); d->nodeManager->moveNodeAt(node, newParent, 0); } else { return; } } void LayerModel::clear() { d->canvas->viewManager()->selectionManager()->clear(); } void LayerModel::clone() { d->nodeManager->duplicateActiveNode(); } void LayerModel::setLocked(int index, bool newLocked) { if (index > -1 && index < d->layers.count()) { if(d->layers[index]->userLocked() == newLocked) return; d->layers[index]->setUserLocked(newLocked); QModelIndex idx = createIndex(index, 0); dataChanged(idx, idx); } } void LayerModel::setOpacity(int index, float newOpacity) { if (index > -1 && index < d->layers.count()) { if(qFuzzyCompare(d->layers[index]->opacity() + 1, newOpacity + 1)) return; d->layers[index]->setOpacity(newOpacity); d->layers[index]->setDirty(); QModelIndex idx = createIndex(index, 0); dataChanged(idx, idx); } } void LayerModel::setVisible(int index, bool newVisible) { if (index > -1 && index < d->layers.count()) { KisBaseNode::PropertyList props = d->layers[index]->sectionModelProperties(); if(props[0].state == newVisible) return; KisBaseNode::Property prop = props[0]; prop.state = newVisible; props[0] = prop; d->nodeModel->setData( d->nodeModel->indexFromNode(d->layers[index]), QVariant::fromValue(props), KisNodeModel::PropertiesRole ); d->layers[index]->setDirty(d->layers[index]->extent()); QModelIndex idx = createIndex(index, 0); dataChanged(idx, idx); } } QImage LayerModel::layerThumbnail(QString layerID) const { // So, yeah, this is a complete cheatery hack. However, it ensures // we actually get updates when we want them (every time the image is supposed // to be changed). Had hoped we could avoid it, but apparently not. int index = layerID.section(QChar('/'), 0, 0).toInt(); QImage thumb; if (index > -1 && index < d->layers.count()) { if (d->thumbProvider) - thumb = d->layers[index]->createThumbnail(120, 120); + thumb = d->layers[index]->createThumbnail(120, 120, Qt::KeepAspectRatio); } return thumb; } void LayerModel::deleteCurrentLayer() { d->activeNode.clear(); d->nodeManager->removeNode(); } void LayerModel::deleteLayer(int index) { if (index > -1 && index < d->layers.count()) { if (d->activeNode == d->layers.at(index)) d->activeNode.clear(); d->nodeManager->slotUiActivatedNode(d->layers.at(index)); d->nodeManager->removeNode(); d->rebuildLayerList(); beginResetModel(); endResetModel(); } } void LayerModel::addLayer(int layerType) { switch(layerType) { case 0: d->nodeManager->createNode("KisPaintLayer"); break; case 1: d->nodeManager->createNode("KisGroupLayer"); break; case 2: d->nodeManager->createNode("KisFilterMask", true); break; default: break; } } void LayerModel::source_rowsAboutToBeInserted(QModelIndex /*p*/, int /*from*/, int /*to*/) { beginResetModel(); } void LayerModel::source_rowsInserted(QModelIndex /*p*/, int, int) { d->rebuildLayerList(); emit countChanged(); endResetModel(); } void LayerModel::source_rowsAboutToBeRemoved(QModelIndex /*p*/, int /*from*/, int /*to*/) { beginResetModel(); } void LayerModel::source_rowsRemoved(QModelIndex, int, int) { d->rebuildLayerList(); emit countChanged(); endResetModel(); } void LayerModel::source_dataChanged(QModelIndex /*tl*/, QModelIndex /*br*/) { QModelIndex top = createIndex(0, 0); QModelIndex bottom = createIndex(d->layers.count() - 1, 0); dataChanged(top, bottom); } void LayerModel::source_modelReset() { beginResetModel(); d->rebuildLayerList(); d->activeNode.clear(); if (d->layers.count() > 0) { d->nodeManager->slotUiActivatedNode(d->layers.at(0)); currentNodeChanged(d->layers.at(0)); } emit countChanged(); endResetModel(); } void LayerModel::notifyImageDeleted() { } void LayerModel::nodeChanged(KisNodeSP node) { QModelIndex index = createIndex(d->layers.indexOf(node), 0); dataChanged(index, index); } void LayerModel::imageChanged() { d->imageChangedTimer->start(); } void LayerModel::imageHasChanged() { QModelIndex top = createIndex(0, 0); QModelIndex bottom = createIndex(d->layers.count() - 1, 0); dataChanged(top, bottom); } void LayerModel::aboutToRemoveNode(KisNodeSP node) { Q_UNUSED(node) QTimer::singleShot(0, this, SLOT(source_modelReset())); } void LayerModel::emitActiveChanges() { emit activeFilterConfigChanged(); emit activeNameChanged(); emit activeTypeChanged(); emit activeCompositeOpChanged(); emit activeOpacityChanged(); emit activeVisibleChanged(); emit activeLockedChanged(); emit activeRChannelActiveChanged(); emit activeGChannelActiveChanged(); emit activeBChannelActiveChanged(); emit activeAChannelActiveChanged(); } QString LayerModel::activeName() const { if (d->activeNode.isNull()) return QString(); return d->activeNode->name(); } void LayerModel::setActiveName(QString newName) { if (d->activeNode.isNull()) return; d->activeNode->setName(newName); emit activeNameChanged(); } QString LayerModel::activeType() const { return d->activeNode->metaObject()->className(); } int LayerModel::activeCompositeOp() const { if (d->activeNode.isNull()) return 0; KoID entry(d->activeNode->compositeOp()->id()); QModelIndex idx = KisCompositeOpListModel::sharedInstance()->indexOf(entry); if (idx.isValid()) return idx.row(); return 0; } void LayerModel::setActiveCompositeOp(int newOp) { if (d->activeNode.isNull()) return; KoID entry; if (KisCompositeOpListModel::sharedInstance()->entryAt(entry, KisCompositeOpListModel::sharedInstance()->index(newOp))) { d->activeNode->setCompositeOpId(entry.id()); d->activeNode->setDirty(); emit activeCompositeOpChanged(); } } int LayerModel::activeOpacity() const { if (d->activeNode.isNull()) return 0; return d->activeNode->opacity(); } void LayerModel::setActiveOpacity(int newOpacity) { d->activeNode->setOpacity(newOpacity); d->activeNode->setDirty(); emit activeOpacityChanged(); } bool LayerModel::activeVisible() const { if (d->activeNode.isNull()) return false; return d->activeNode->visible(); } void LayerModel::setActiveVisible(bool newVisible) { if (d->activeNode.isNull()) return; setVisible(d->layers.indexOf(d->activeNode), newVisible); emit activeVisibleChanged(); } bool LayerModel::activeLocked() const { if (d->activeNode.isNull()) return false; return d->activeNode->userLocked(); } void LayerModel::setActiveLocked(bool newLocked) { if (d->activeNode.isNull()) return; d->activeNode->setUserLocked(newLocked); emit activeLockedChanged(); } bool LayerModel::activeAChannelActive() const { KisLayer* layer = qobject_cast(d->activeNode.data()); bool state = false; if (layer) state = !layer->alphaChannelDisabled(); return state; } void LayerModel::setActiveAChannelActive(bool newActive) { KisLayer* layer = qobject_cast(d->activeNode.data()); if (layer) { layer->disableAlphaChannel(!newActive); layer->setDirty(); emit activeAChannelActiveChanged(); } } bool getActiveChannel(KisNodeSP node, int channelIndex) { KisLayer* layer = qobject_cast(node.data()); bool flag = false; if (layer) { QBitArray flags = layer->channelFlags(); if (channelIndex < flags.size()) { flag = flags[channelIndex]; } } return flag; } void setChannelActive(KisNodeSP node, int channelIndex, bool newActive) { KisLayer* layer = qobject_cast(node.data()); if (layer) { QBitArray flags = layer->channelFlags(); flags.setBit(channelIndex, newActive); layer->setChannelFlags(flags); layer->setDirty(); } } bool LayerModel::activeBChannelActive() const { return getActiveChannel(d->activeNode, 2); } void LayerModel::setActiveBChannelActive(bool newActive) { setChannelActive(d->activeNode, 2, newActive); emit activeBChannelActiveChanged(); } bool LayerModel::activeGChannelActive() const { return getActiveChannel(d->activeNode, 1); } void LayerModel::setActiveGChannelActive(bool newActive) { setChannelActive(d->activeNode, 1, newActive); emit activeGChannelActiveChanged(); } bool LayerModel::activeRChannelActive() const { return getActiveChannel(d->activeNode, 0); } void LayerModel::setActiveRChannelActive(bool newActive) { setChannelActive(d->activeNode, 0, newActive); emit activeRChannelActiveChanged(); } QObject* LayerModel::activeFilterConfig() const { QMap props; QString filterId; KisFilterMask* filterMask = qobject_cast(d->activeNode.data()); if (filterMask) { props = filterMask->filter()->getProperties(); filterId = filterMask->filter()->name(); } else { KisAdjustmentLayer* adjustmentLayer = qobject_cast(d->activeNode.data()); if (adjustmentLayer) { props = adjustmentLayer->filter()->getProperties(); filterId = adjustmentLayer->filter()->name(); } } PropertyContainer* config = new PropertyContainer(filterId, 0); QMap::const_iterator i; for(i = props.constBegin(); i != props.constEnd(); ++i) { config->setProperty(i.key().toLatin1(), i.value()); //dbgKrita << "Getting active config..." << i.key() << i.value(); } return config; } void LayerModel::setActiveFilterConfig(QObject* newConfig) { if (d->activeNode.isNull()) return; PropertyContainer* config = qobject_cast(newConfig); if (!config) return; //dbgKrita << "Attempting to set new config" << config->name(); KisFilterConfigurationSP realConfig = d->filters.value(config->name())->factoryConfiguration(KisGlobalResourcesInterface::instance()); QMap::const_iterator i; for(i = realConfig->getProperties().constBegin(); i != realConfig->getProperties().constEnd(); ++i) { realConfig->setProperty(i.key(), config->property(i.key().toLatin1())); //dbgKrita << "Creating config..." << i.key() << i.value(); } // The following code causes sporadic crashes, and disabling causes leaks. So, leaks it must be, for now. // The cause is the lack of a smart pointer interface for passing filter configs around // Must be remedied, but for now... // if (d->newConfig) // delete(d->newConfig); d->newConfig = realConfig; //d->updateActiveLayerWithNewFilterConfigTimer->start(); updateActiveLayerWithNewFilterConfig(); } void LayerModel::updateActiveLayerWithNewFilterConfig() { if (!d->newConfig) return; //dbgKrita << "Setting new config..." << d->newConfig->name(); KisFilterMask* filterMask = qobject_cast(d->activeNode.data()); if (filterMask) { //dbgKrita << "Setting filter mask"; filterMask->setFilter(d->newConfig->cloneWithResourcesSnapshot()); } else { KisAdjustmentLayer* adjustmentLayer = qobject_cast(d->activeNode.data()); if (adjustmentLayer) { //dbgKrita << "Setting filter on adjustment layer"; adjustmentLayer->setFilter(d->newConfig->cloneWithResourcesSnapshot()); } else { //dbgKrita << "UNKNOWN, BAIL OUT!"; } } d->newConfig = 0; d->activeNode->setDirty(d->activeNode->extent()); d->image->setModified(); QTimer::singleShot(100, this, SIGNAL(activeFilterConfigChanged())); } diff --git a/libs/pigment/resources/KoSegmentGradient.cpp b/libs/pigment/resources/KoSegmentGradient.cpp index b9772f10be..0bdc986496 100644 --- a/libs/pigment/resources/KoSegmentGradient.cpp +++ b/libs/pigment/resources/KoSegmentGradient.cpp @@ -1,960 +1,958 @@ /* Copyright (c) 2000 Matthias Elter 2001 John Califf 2004 Boudewijn Rempt 2004 Adrian Page 2004, 2007 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; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include #include #include #include #include #include "KoColorSpaceRegistry.h" #include "KoColorSpace.h" #include "KoMixColorsOp.h" #include #include #include KoGradientSegment::RGBColorInterpolationStrategy *KoGradientSegment::RGBColorInterpolationStrategy::m_instance = 0; KoGradientSegment::HSVCWColorInterpolationStrategy *KoGradientSegment::HSVCWColorInterpolationStrategy::m_instance = 0; KoGradientSegment::HSVCCWColorInterpolationStrategy *KoGradientSegment::HSVCCWColorInterpolationStrategy::m_instance = 0; KoGradientSegment::LinearInterpolationStrategy *KoGradientSegment::LinearInterpolationStrategy::m_instance = 0; KoGradientSegment::CurvedInterpolationStrategy *KoGradientSegment::CurvedInterpolationStrategy::m_instance = 0; KoGradientSegment::SineInterpolationStrategy *KoGradientSegment::SineInterpolationStrategy::m_instance = 0; KoGradientSegment::SphereIncreasingInterpolationStrategy *KoGradientSegment::SphereIncreasingInterpolationStrategy::m_instance = 0; KoGradientSegment::SphereDecreasingInterpolationStrategy *KoGradientSegment::SphereDecreasingInterpolationStrategy::m_instance = 0; KoSegmentGradient::KoSegmentGradient(const QString& file) : KoAbstractGradient(file) { } KoSegmentGradient::~KoSegmentGradient() { for (int i = 0; i < m_segments.count(); i++) { delete m_segments[i]; m_segments[i] = 0; } } KoSegmentGradient::KoSegmentGradient(const KoSegmentGradient &rhs) : KoAbstractGradient(rhs) { Q_FOREACH (KoGradientSegment *segment, rhs.m_segments) { pushSegment(new KoGradientSegment(*segment)); } } KoResourceSP KoSegmentGradient::clone() const { return KoResourceSP(new KoSegmentGradient(*this)); } bool KoSegmentGradient::loadFromDevice(QIODevice *dev, KisResourcesInterfaceSP resourcesInterface) { Q_UNUSED(resourcesInterface); QByteArray data = dev->readAll(); QTextStream fileContent(data, QIODevice::ReadOnly); fileContent.setAutoDetectUnicode(true); QString header = fileContent.readLine(); if (header != "GIMP Gradient") { return false; } QString nameDefinition = fileContent.readLine(); QString numSegmentsText; if (nameDefinition.startsWith("Name: ")) { QString nameText = nameDefinition.right(nameDefinition.length() - 6); setName(nameText); numSegmentsText = fileContent.readLine(); } else { // Older format without name. numSegmentsText = nameDefinition; } dbgPigment << "Loading gradient: " << name(); int numSegments; bool ok; numSegments = numSegmentsText.toInt(&ok); if (!ok || numSegments < 1) { return false; } dbgPigment << "Number of segments = " << numSegments; const KoColorSpace* rgbColorSpace = KoColorSpaceRegistry::instance()->rgb8(); for (int i = 0; i < numSegments; i++) { QString segmentText = fileContent.readLine(); QTextStream segmentFields(&segmentText); QStringList values = segmentText.split(' '); qreal leftOffset = values[0].toDouble(); qreal middleOffset = values[1].toDouble(); qreal rightOffset = values[2].toDouble(); qreal leftRed = values[3].toDouble(); qreal leftGreen = values[4].toDouble(); qreal leftBlue = values[5].toDouble(); qreal leftAlpha = values[6].toDouble(); qreal rightRed = values[7].toDouble(); qreal rightGreen = values[8].toDouble(); qreal rightBlue = values[9].toDouble(); qreal rightAlpha = values[10].toDouble(); int interpolationType = values[11].toInt(); int colorInterpolationType = values[12].toInt(); quint8 data[4]; data[2] = static_cast(leftRed * 255 + 0.5); data[1] = static_cast(leftGreen * 255 + 0.5); data[0] = static_cast(leftBlue * 255 + 0.5); data[3] = static_cast(leftAlpha * OPACITY_OPAQUE_U8 + 0.5); KoColor leftColor(data, rgbColorSpace); data[2] = static_cast(rightRed * 255 + 0.5); data[1] = static_cast(rightGreen * 255 + 0.5); data[0] = static_cast(rightBlue * 255 + 0.5); data[3] = static_cast(rightAlpha * OPACITY_OPAQUE_U8 + 0.5); KoColor rightColor(data, rgbColorSpace); KoGradientSegment *segment = new KoGradientSegment(interpolationType, colorInterpolationType, leftOffset, middleOffset, rightOffset, leftColor, rightColor); Q_CHECK_PTR(segment); if (!segment -> isValid()) { delete segment; return false; } m_segments.push_back(segment); } if (!m_segments.isEmpty()) { updatePreview(); setValid(true); return true; } else { return false; } } bool KoSegmentGradient::saveToDevice(QIODevice *dev) const { QTextStream fileContent(dev); fileContent << "GIMP Gradient\n"; fileContent << "Name: " << name() << "\n"; fileContent << m_segments.count() << "\n"; Q_FOREACH (KoGradientSegment* segment, m_segments) { fileContent << QString::number(segment->startOffset(), 'f') << " " << QString::number(segment->middleOffset(), 'f') << " " << QString::number(segment->endOffset(), 'f') << " "; QColor startColor = segment->startColor().toQColor(); QColor endColor = segment->endColor().toQColor(); fileContent << QString::number(startColor.redF(), 'f') << " " << QString::number(startColor.greenF(), 'f') << " " << QString::number(startColor.blueF(), 'f') << " " << QString::number(startColor.alphaF(), 'f') << " "; fileContent << QString::number(endColor.redF(), 'f') << " " << QString::number(endColor.greenF(), 'f') << " " << QString::number(endColor.blueF(), 'f') << " " << QString::number(endColor.alphaF(), 'f') << " "; fileContent << (int)segment->interpolation() << " " << (int)segment->colorInterpolation() << "\n"; } KoResource::saveToDevice(dev); return true; } KoGradientSegment *KoSegmentGradient::segmentAt(qreal t) const { if (t < 0.0) return 0; if (t > 1.0) return 0; if (m_segments.isEmpty()) return 0; for (QList::const_iterator it = m_segments.begin(); it != m_segments.end(); ++it) { if (t > (*it)->startOffset() - DBL_EPSILON && t < (*it)->endOffset() + DBL_EPSILON) { return *it; } } return 0; } void KoSegmentGradient::colorAt(KoColor& dst, qreal t) const { const KoGradientSegment *segment = segmentAt(t); - Q_ASSERT(segment != 0); - if (segment) { segment->colorAt(dst, t); } } QGradient* KoSegmentGradient::toQGradient() const { QGradient* gradient = new QLinearGradient(); QColor color; Q_FOREACH (KoGradientSegment* segment, m_segments) { segment->startColor().toQColor(&color); gradient->setColorAt(segment->startOffset() , color); segment->endColor().toQColor(&color); gradient->setColorAt(segment->endOffset() , color); } return gradient; } QString KoSegmentGradient::defaultFileExtension() const { return QString(".ggr"); } void KoSegmentGradient::toXML(QDomDocument &doc, QDomElement &gradientElt) const { gradientElt.setAttribute("type", "segment"); Q_FOREACH(KoGradientSegment *segment, this->segments()) { QDomElement segmentElt = doc.createElement("segment"); QDomElement start = doc.createElement("start"); QDomElement end = doc.createElement("end"); segmentElt.setAttribute("start-offset", KisDomUtils::toString(segment->startOffset())); const KoColor startColor = segment->startColor(); segmentElt.setAttribute("start-bitdepth", startColor.colorSpace()->colorDepthId().id()); segmentElt.setAttribute("start-alpha", KisDomUtils::toString(startColor.opacityF())); startColor.toXML(doc, start); segmentElt.setAttribute("middle-offset", KisDomUtils::toString(segment->middleOffset())); segmentElt.setAttribute("end-offset", KisDomUtils::toString(segment->endOffset())); const KoColor endColor = segment->endColor(); segmentElt.setAttribute("end-bitdepth", endColor.colorSpace()->colorDepthId().id()); segmentElt.setAttribute("end-alpha", KisDomUtils::toString(endColor.opacityF())); endColor.toXML(doc, end); segmentElt.setAttribute("interpolation", KisDomUtils::toString(segment->interpolation())); segmentElt.setAttribute("color-interpolation", KisDomUtils::toString(segment->colorInterpolation())); segmentElt.appendChild(start); segmentElt.appendChild(end); gradientElt.appendChild(segmentElt); } } KoSegmentGradient KoSegmentGradient::fromXML(const QDomElement &elt) { KoSegmentGradient gradient; QDomElement segmentElt = elt.firstChildElement("segment"); while (!segmentElt.isNull()) { int interpolation = KisDomUtils::toInt(segmentElt.attribute("interpolation", "0.0")); int colorInterpolation = KisDomUtils::toInt(segmentElt.attribute("color-interpolation", "0.0")); double startOffset = KisDomUtils::toDouble(segmentElt.attribute("start-offset", "0.0")); qreal middleOffset = KisDomUtils::toDouble(segmentElt.attribute("middle-offset", "0.0")); qreal endOffset = KisDomUtils::toDouble(segmentElt.attribute("end-offset", "0.0")); QDomElement start = segmentElt.firstChildElement("start"); QString startBitdepth = segmentElt.attribute("start-bitdepth", Integer8BitsColorDepthID.id()); QColor left = KoColor::fromXML(start.firstChildElement(), startBitdepth).toQColor(); left.setAlphaF(KisDomUtils::toDouble(segmentElt.attribute("start-alpha", "1.0"))); QString endBitdepth = segmentElt.attribute("end-bitdepth", Integer8BitsColorDepthID.id()); QDomElement end = segmentElt.firstChildElement("end"); QColor right = KoColor::fromXML(end.firstChildElement(), endBitdepth).toQColor(); right.setAlphaF(KisDomUtils::toDouble(segmentElt.attribute("end-alpha", "1.0"))); gradient.createSegment(interpolation, colorInterpolation, startOffset, endOffset, middleOffset, left, right); segmentElt = segmentElt.nextSiblingElement("segment"); } return gradient; } KoGradientSegment::KoGradientSegment(int interpolationType, int colorInterpolationType, qreal startOffset, qreal middleOffset, qreal endOffset, const KoColor& startColor, const KoColor& endColor) { m_interpolator = 0; switch (interpolationType) { case INTERP_LINEAR: m_interpolator = LinearInterpolationStrategy::instance(); break; case INTERP_CURVED: m_interpolator = CurvedInterpolationStrategy::instance(); break; case INTERP_SINE: m_interpolator = SineInterpolationStrategy::instance(); break; case INTERP_SPHERE_INCREASING: m_interpolator = SphereIncreasingInterpolationStrategy::instance(); break; case INTERP_SPHERE_DECREASING: m_interpolator = SphereDecreasingInterpolationStrategy::instance(); break; } m_colorInterpolator = 0; switch (colorInterpolationType) { case COLOR_INTERP_RGB: m_colorInterpolator = RGBColorInterpolationStrategy::instance(); break; case COLOR_INTERP_HSV_CCW: m_colorInterpolator = HSVCCWColorInterpolationStrategy::instance(); break; case COLOR_INTERP_HSV_CW: m_colorInterpolator = HSVCWColorInterpolationStrategy::instance(); break; } if (startOffset < DBL_EPSILON) { m_startOffset = 0; } else if (startOffset > 1 - DBL_EPSILON) { m_startOffset = 1; } else { m_startOffset = startOffset; } if (middleOffset < m_startOffset + DBL_EPSILON) { m_middleOffset = m_startOffset; } else if (middleOffset > 1 - DBL_EPSILON) { m_middleOffset = 1; } else { m_middleOffset = middleOffset; } if (endOffset < m_middleOffset + DBL_EPSILON) { m_endOffset = m_middleOffset; } else if (endOffset > 1 - DBL_EPSILON) { m_endOffset = 1; } else { m_endOffset = endOffset; } m_length = m_endOffset - m_startOffset; if (m_length < DBL_EPSILON) { m_middleT = 0.5; } else { m_middleT = (m_middleOffset - m_startOffset) / m_length; } m_startColor = startColor; m_endColor = endColor; } const KoColor& KoGradientSegment::startColor() const { return m_startColor; } const KoColor& KoGradientSegment::endColor() const { return m_endColor; } qreal KoGradientSegment::startOffset() const { return m_startOffset; } qreal KoGradientSegment::middleOffset() const { return m_middleOffset; } qreal KoGradientSegment::endOffset() const { return m_endOffset; } void KoGradientSegment::setStartOffset(qreal t) { m_startOffset = t; m_length = m_endOffset - m_startOffset; if (m_length < DBL_EPSILON) { m_middleT = 0.5; } else { m_middleT = (m_middleOffset - m_startOffset) / m_length; } } void KoGradientSegment::setMiddleOffset(qreal t) { m_middleOffset = t; if (m_length < DBL_EPSILON) { m_middleT = 0.5; } else { m_middleT = (m_middleOffset - m_startOffset) / m_length; } } void KoGradientSegment::setEndOffset(qreal t) { m_endOffset = t; m_length = m_endOffset - m_startOffset; if (m_length < DBL_EPSILON) { m_middleT = 0.5; } else { m_middleT = (m_middleOffset - m_startOffset) / m_length; } } int KoGradientSegment::interpolation() const { return m_interpolator->type(); } void KoGradientSegment::setInterpolation(int interpolationType) { switch (interpolationType) { case INTERP_LINEAR: m_interpolator = LinearInterpolationStrategy::instance(); break; case INTERP_CURVED: m_interpolator = CurvedInterpolationStrategy::instance(); break; case INTERP_SINE: m_interpolator = SineInterpolationStrategy::instance(); break; case INTERP_SPHERE_INCREASING: m_interpolator = SphereIncreasingInterpolationStrategy::instance(); break; case INTERP_SPHERE_DECREASING: m_interpolator = SphereDecreasingInterpolationStrategy::instance(); break; } } int KoGradientSegment::colorInterpolation() const { return m_colorInterpolator->type(); } void KoGradientSegment::setColorInterpolation(int colorInterpolationType) { switch (colorInterpolationType) { case COLOR_INTERP_RGB: m_colorInterpolator = RGBColorInterpolationStrategy::instance(); break; case COLOR_INTERP_HSV_CCW: m_colorInterpolator = HSVCCWColorInterpolationStrategy::instance(); break; case COLOR_INTERP_HSV_CW: m_colorInterpolator = HSVCWColorInterpolationStrategy::instance(); break; } } void KoGradientSegment::colorAt(KoColor& dst, qreal t) const { Q_ASSERT(t > m_startOffset - DBL_EPSILON && t < m_endOffset + DBL_EPSILON); qreal segmentT; if (m_length < DBL_EPSILON) { segmentT = 0.5; } else { segmentT = (t - m_startOffset) / m_length; } qreal colorT = m_interpolator->valueAt(segmentT, m_middleT); m_colorInterpolator->colorAt(dst, colorT, m_startColor, m_endColor); } bool KoGradientSegment::isValid() const { if (m_interpolator == 0 || m_colorInterpolator == 0) return false; return true; } KoGradientSegment::RGBColorInterpolationStrategy::RGBColorInterpolationStrategy() : m_colorSpace(KoColorSpaceRegistry::instance()->rgb8()) { } KoGradientSegment::RGBColorInterpolationStrategy *KoGradientSegment::RGBColorInterpolationStrategy::instance() { if (m_instance == 0) { m_instance = new RGBColorInterpolationStrategy(); Q_CHECK_PTR(m_instance); } return m_instance; } void KoGradientSegment::RGBColorInterpolationStrategy::colorAt(KoColor& dst, qreal t, const KoColor& _start, const KoColor& _end) const { KoColor buffer(m_colorSpace); KoColor start(m_colorSpace); KoColor end(m_colorSpace); KoColor startDummy, endDummy; //hack to get a color space with the bitdepth of the gradients(8bit), but with the colour profile of the image// const KoColorSpace* mixSpace = KoColorSpaceRegistry::instance()->rgb8(dst.colorSpace()->profile()); //convert to the right colorspace for the start and end if we have our mixSpace. if (mixSpace){ startDummy = KoColor(_start, mixSpace); endDummy = KoColor(_end, mixSpace); } else { startDummy = _start; endDummy = _end; } start.fromKoColor(_start); end.fromKoColor(_end); const quint8 *colors[2]; colors[0] = startDummy.data(); colors[1] = endDummy.data(); qint16 colorWeights[2]; colorWeights[0] = static_cast((1.0 - t) * 255 + 0.5); colorWeights[1] = 255 - colorWeights[0]; //check if our mixspace exists, it doesn't at startup. if (mixSpace){ if (*buffer.colorSpace() != *mixSpace) { buffer = KoColor(mixSpace); } mixSpace->mixColorsOp()->mixColors(colors, colorWeights, 2, buffer.data()); } else { buffer = KoColor(m_colorSpace); m_colorSpace->mixColorsOp()->mixColors(colors, colorWeights, 2, buffer.data()); } dst.fromKoColor(buffer); } KoGradientSegment::HSVCWColorInterpolationStrategy::HSVCWColorInterpolationStrategy() : m_colorSpace(KoColorSpaceRegistry::instance()->rgb8()) { } KoGradientSegment::HSVCWColorInterpolationStrategy *KoGradientSegment::HSVCWColorInterpolationStrategy::instance() { if (m_instance == 0) { m_instance = new HSVCWColorInterpolationStrategy(); Q_CHECK_PTR(m_instance); } return m_instance; } void KoGradientSegment::HSVCWColorInterpolationStrategy::colorAt(KoColor& dst, qreal t, const KoColor& start, const KoColor& end) const { QColor sc; QColor ec; start.toQColor(&sc); end.toQColor(&ec); int s = static_cast(sc.saturation() + t * (ec.saturation() - sc.saturation()) + 0.5); int v = static_cast(sc.value() + t * (ec.value() - sc.value()) + 0.5); int h; if (ec.hue() < sc.hue()) { h = static_cast(ec.hue() + (1 - t) * (sc.hue() - ec.hue()) + 0.5); } else { h = static_cast(ec.hue() + (1 - t) * (360 - ec.hue() + sc.hue()) + 0.5); if (h > 359) { h -= 360; } } // XXX: added an explicit cast. Is this correct? quint8 opacity = static_cast(sc.alpha() + t * (ec.alpha() - sc.alpha())); QColor result; result.setHsv(h, s, v); result.setAlpha(opacity); dst.fromQColor(result); } KoGradientSegment::HSVCCWColorInterpolationStrategy::HSVCCWColorInterpolationStrategy() : m_colorSpace(KoColorSpaceRegistry::instance()->rgb8()) { } KoGradientSegment::HSVCCWColorInterpolationStrategy *KoGradientSegment::HSVCCWColorInterpolationStrategy::instance() { if (m_instance == 0) { m_instance = new HSVCCWColorInterpolationStrategy(); Q_CHECK_PTR(m_instance); } return m_instance; } void KoGradientSegment::HSVCCWColorInterpolationStrategy::colorAt(KoColor& dst, qreal t, const KoColor& start, const KoColor& end) const { QColor sc; QColor se; start.toQColor(&sc); end.toQColor(&se); int s = static_cast(sc.saturation() + t * (se.saturation() - sc.saturation()) + 0.5); int v = static_cast(sc.value() + t * (se.value() - sc.value()) + 0.5); int h; if (sc.hue() < se.hue()) { h = static_cast(sc.hue() + t * (se.hue() - sc.hue()) + 0.5); } else { h = static_cast(sc.hue() + t * (360 - sc.hue() + se.hue()) + 0.5); if (h > 359) { h -= 360; } } // XXX: Added an explicit static cast quint8 opacity = static_cast(sc.alpha() + t * (se.alpha() - sc.alpha())); QColor result; result.setHsv(h, s, v); result.setAlpha(opacity); dst.fromQColor(result); } KoGradientSegment::LinearInterpolationStrategy *KoGradientSegment::LinearInterpolationStrategy::instance() { if (m_instance == 0) { m_instance = new LinearInterpolationStrategy(); Q_CHECK_PTR(m_instance); } return m_instance; } qreal KoGradientSegment::LinearInterpolationStrategy::calcValueAt(qreal t, qreal middle) { Q_ASSERT(t > -DBL_EPSILON && t < 1 + DBL_EPSILON); Q_ASSERT(middle > -DBL_EPSILON && middle < 1 + DBL_EPSILON); qreal value = 0; if (t <= middle) { if (middle < DBL_EPSILON) { value = 0; } else { value = (t / middle) * 0.5; } } else { if (middle > 1 - DBL_EPSILON) { value = 1; } else { value = ((t - middle) / (1 - middle)) * 0.5 + 0.5; } } return value; } qreal KoGradientSegment::LinearInterpolationStrategy::valueAt(qreal t, qreal middle) const { return calcValueAt(t, middle); } KoGradientSegment::CurvedInterpolationStrategy::CurvedInterpolationStrategy() { m_logHalf = log(0.5); } KoGradientSegment::CurvedInterpolationStrategy *KoGradientSegment::CurvedInterpolationStrategy::instance() { if (m_instance == 0) { m_instance = new CurvedInterpolationStrategy(); Q_CHECK_PTR(m_instance); } return m_instance; } qreal KoGradientSegment::CurvedInterpolationStrategy::valueAt(qreal t, qreal middle) const { Q_ASSERT(t > -DBL_EPSILON && t < 1 + DBL_EPSILON); Q_ASSERT(middle > -DBL_EPSILON && middle < 1 + DBL_EPSILON); qreal value = 0; if (middle < DBL_EPSILON) { middle = DBL_EPSILON; } value = pow(t, m_logHalf / log(middle)); return value; } KoGradientSegment::SineInterpolationStrategy *KoGradientSegment::SineInterpolationStrategy::instance() { if (m_instance == 0) { m_instance = new SineInterpolationStrategy(); Q_CHECK_PTR(m_instance); } return m_instance; } qreal KoGradientSegment::SineInterpolationStrategy::valueAt(qreal t, qreal middle) const { qreal lt = LinearInterpolationStrategy::calcValueAt(t, middle); qreal value = (sin(-M_PI_2 + M_PI * lt) + 1.0) / 2.0; return value; } KoGradientSegment::SphereIncreasingInterpolationStrategy *KoGradientSegment::SphereIncreasingInterpolationStrategy::instance() { if (m_instance == 0) { m_instance = new SphereIncreasingInterpolationStrategy(); Q_CHECK_PTR(m_instance); } return m_instance; } qreal KoGradientSegment::SphereIncreasingInterpolationStrategy::valueAt(qreal t, qreal middle) const { qreal lt = LinearInterpolationStrategy::calcValueAt(t, middle) - 1; qreal value = sqrt(1 - lt * lt); return value; } KoGradientSegment::SphereDecreasingInterpolationStrategy *KoGradientSegment::SphereDecreasingInterpolationStrategy::instance() { if (m_instance == 0) { m_instance = new SphereDecreasingInterpolationStrategy(); Q_CHECK_PTR(m_instance); } return m_instance; } qreal KoGradientSegment::SphereDecreasingInterpolationStrategy::valueAt(qreal t, qreal middle) const { qreal lt = LinearInterpolationStrategy::calcValueAt(t, middle); qreal value = 1 - sqrt(1 - lt * lt); return value; } void KoSegmentGradient::createSegment(int interpolation, int colorInterpolation, double startOffset, double endOffset, double middleOffset, const QColor & left, const QColor & right) { pushSegment(new KoGradientSegment(interpolation, colorInterpolation, startOffset, middleOffset, endOffset, KoColor(left, colorSpace()), KoColor(right, colorSpace()))); } const QList KoSegmentGradient::getHandlePositions() const { QList handlePositions; handlePositions.push_back(m_segments[0]->startOffset()); for (int i = 0; i < m_segments.count(); i++) { handlePositions.push_back(m_segments[i]->endOffset()); } return handlePositions; } const QList KoSegmentGradient::getMiddleHandlePositions() const { QList middleHandlePositions; for (int i = 0; i < m_segments.count(); i++) { middleHandlePositions.push_back(m_segments[i]->middleOffset()); } return middleHandlePositions; } void KoSegmentGradient::moveSegmentStartOffset(KoGradientSegment* segment, double t) { QList::iterator it = std::find(m_segments.begin(), m_segments.end(), segment); if (it != m_segments.end()) { if (it == m_segments.begin()) { segment->setStartOffset(0.0); return; } KoGradientSegment* previousSegment = (*(it - 1)); if (t > segment->startOffset()) { if (t > segment->middleOffset()) t = segment->middleOffset(); } else { if (t < previousSegment->middleOffset()) t = previousSegment->middleOffset(); } previousSegment->setEndOffset(t); segment->setStartOffset(t); } } void KoSegmentGradient::moveSegmentEndOffset(KoGradientSegment* segment, double t) { QList::iterator it = std::find(m_segments.begin(), m_segments.end(), segment); if (it != m_segments.end()) { if (it + 1 == m_segments.end()) { segment->setEndOffset(1.0); return; } KoGradientSegment* followingSegment = (*(it + 1)); if (t < segment->endOffset()) { if (t < segment->middleOffset()) t = segment->middleOffset(); } else { if (t > followingSegment->middleOffset()) t = followingSegment->middleOffset(); } followingSegment->setStartOffset(t); segment->setEndOffset(t); } } void KoSegmentGradient::moveSegmentMiddleOffset(KoGradientSegment* segment, double t) { if (segment) { if (t > segment->endOffset()) segment->setMiddleOffset(segment->endOffset()); else if (t < segment->startOffset()) segment->setMiddleOffset(segment->startOffset()); else segment->setMiddleOffset(t); } } void KoSegmentGradient::splitSegment(KoGradientSegment* segment) { Q_ASSERT(segment != 0); QList::iterator it = std::find(m_segments.begin(), m_segments.end(), segment); if (it != m_segments.end()) { KoColor midleoffsetColor(segment->endColor().colorSpace()); segment->colorAt(midleoffsetColor, segment->middleOffset()); KoGradientSegment* newSegment = new KoGradientSegment( segment->interpolation(), segment->colorInterpolation(), segment ->startOffset(), (segment->middleOffset() - segment->startOffset()) / 2 + segment->startOffset(), segment->middleOffset(), segment->startColor(), midleoffsetColor); m_segments.insert(it, newSegment); segment->setStartColor(midleoffsetColor); segment->setStartOffset(segment->middleOffset()); segment->setMiddleOffset((segment->endOffset() - segment->startOffset()) / 2 + segment->startOffset()); } } void KoSegmentGradient::duplicateSegment(KoGradientSegment* segment) { Q_ASSERT(segment != 0); QList::iterator it = std::find(m_segments.begin(), m_segments.end(), segment); if (it != m_segments.end()) { double middlePostionPercentage = (segment->middleOffset() - segment->startOffset()) / segment->length(); double center = segment->startOffset() + segment->length() / 2; KoGradientSegment* newSegment = new KoGradientSegment( segment->interpolation(), segment->colorInterpolation(), segment ->startOffset(), segment->length() / 2 * middlePostionPercentage + segment->startOffset(), center, segment->startColor(), segment->endColor()); m_segments.insert(it, newSegment); segment->setStartOffset(center); segment->setMiddleOffset(segment->length() * middlePostionPercentage + segment->startOffset()); } } void KoSegmentGradient::mirrorSegment(KoGradientSegment* segment) { Q_ASSERT(segment != 0); KoColor tmpColor = segment->startColor(); segment->setStartColor(segment->endColor()); segment->setEndColor(tmpColor); segment->setMiddleOffset(segment->endOffset() - (segment->middleOffset() - segment->startOffset())); if (segment->interpolation() == INTERP_SPHERE_INCREASING) segment->setInterpolation(INTERP_SPHERE_DECREASING); else if (segment->interpolation() == INTERP_SPHERE_DECREASING) segment->setInterpolation(INTERP_SPHERE_INCREASING); if (segment->colorInterpolation() == COLOR_INTERP_HSV_CW) segment->setColorInterpolation(COLOR_INTERP_HSV_CCW); else if (segment->colorInterpolation() == COLOR_INTERP_HSV_CCW) segment->setColorInterpolation(COLOR_INTERP_HSV_CW); } KoGradientSegment* KoSegmentGradient::removeSegment(KoGradientSegment* segment) { Q_ASSERT(segment != 0); if (m_segments.count() < 2) return 0; QList::iterator it = std::find(m_segments.begin(), m_segments.end(), segment); if (it != m_segments.end()) { double middlePostionPercentage; KoGradientSegment* nextSegment; if (it == m_segments.begin()) { nextSegment = (*(it + 1)); middlePostionPercentage = (nextSegment->middleOffset() - nextSegment->startOffset()) / nextSegment->length(); nextSegment->setStartOffset(segment->startOffset()); nextSegment->setMiddleOffset(middlePostionPercentage * nextSegment->length() + nextSegment->startOffset()); } else { nextSegment = (*(it - 1)); middlePostionPercentage = (nextSegment->middleOffset() - nextSegment->startOffset()) / nextSegment->length(); nextSegment->setEndOffset(segment->endOffset()); nextSegment->setMiddleOffset(middlePostionPercentage * nextSegment->length() + nextSegment->startOffset()); } delete segment; m_segments.erase(it); return nextSegment; } return 0; } bool KoSegmentGradient::removeSegmentPossible() const { if (m_segments.count() < 2) return false; return true; } const QList& KoSegmentGradient::segments() const { return m_segments; } diff --git a/libs/psd/asl/kis_asl_xml_writer.cpp b/libs/psd/asl/kis_asl_xml_writer.cpp index e147061ad1..7e3323e81a 100644 --- a/libs/psd/asl/kis_asl_xml_writer.cpp +++ b/libs/psd/asl/kis_asl_xml_writer.cpp @@ -1,397 +1,398 @@ /* * 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_asl_xml_writer.h" #include #include #include #include #include #include #include #include #include #include "kis_dom_utils.h" #include "kis_asl_writer_utils.h" struct KisAslXmlWriter::Private { QDomDocument document; QDomElement currentElement; }; KisAslXmlWriter::KisAslXmlWriter() : m_d(new Private) { QDomElement el = m_d->document.createElement("asl"); m_d->document.appendChild(el); m_d->currentElement = el; } KisAslXmlWriter::~KisAslXmlWriter() { } QDomDocument KisAslXmlWriter::document() const { if (m_d->document.documentElement() != m_d->currentElement) { warnKrita << "KisAslXmlWriter::document(): unbalanced enter/leave descriptor/array"; } return m_d->document; } void KisAslXmlWriter::enterDescriptor(const QString &key, const QString &name, const QString &classId) { QDomElement el = m_d->document.createElement("node"); if (!key.isEmpty()) { el.setAttribute("key", key); } el.setAttribute("type", "Descriptor"); el.setAttribute("name", name); el.setAttribute("classId", classId); m_d->currentElement.appendChild(el); m_d->currentElement = el; } void KisAslXmlWriter::leaveDescriptor() { if (!m_d->currentElement.parentNode().toElement().isNull()) { m_d->currentElement = m_d->currentElement.parentNode().toElement(); } else { warnKrita << "KisAslXmlWriter::leaveDescriptor(): unbalanced enter/leave descriptor"; } } void KisAslXmlWriter::enterList(const QString &key) { QDomElement el = m_d->document.createElement("node"); if (!key.isEmpty()) { el.setAttribute("key", key); } el.setAttribute("type", "List"); m_d->currentElement.appendChild(el); m_d->currentElement = el; } void KisAslXmlWriter::leaveList() { if (!m_d->currentElement.parentNode().toElement().isNull()) { m_d->currentElement = m_d->currentElement.parentNode().toElement(); } else { warnKrita << "KisAslXmlWriter::leaveList(): unbalanced enter/leave list"; } } void KisAslXmlWriter::writeDouble(const QString &key, double value) { QDomElement el = m_d->document.createElement("node"); if (!key.isEmpty()) { el.setAttribute("key", key); } el.setAttribute("type", "Double"); el.setAttribute("value", KisDomUtils::toString(value)); m_d->currentElement.appendChild(el); } void KisAslXmlWriter::writeInteger(const QString &key, int value) { QDomElement el = m_d->document.createElement("node"); if (!key.isEmpty()) { el.setAttribute("key", key); } el.setAttribute("type", "Integer"); el.setAttribute("value", KisDomUtils::toString(value)); m_d->currentElement.appendChild(el); } void KisAslXmlWriter::writeEnum(const QString &key, const QString &typeId, const QString &value) { QDomElement el = m_d->document.createElement("node"); if (!key.isEmpty()) { el.setAttribute("key", key); } el.setAttribute("type", "Enum"); el.setAttribute("typeId", typeId); el.setAttribute("value", value); m_d->currentElement.appendChild(el); } void KisAslXmlWriter::writeUnitFloat(const QString &key, const QString &unit, double value) { QDomElement el = m_d->document.createElement("node"); if (!key.isEmpty()) { el.setAttribute("key", key); } el.setAttribute("type", "UnitFloat"); el.setAttribute("unit", unit); el.setAttribute("value", KisDomUtils::toString(value)); m_d->currentElement.appendChild(el); } void KisAslXmlWriter::writeText(const QString &key, const QString &value) { QDomElement el = m_d->document.createElement("node"); if (!key.isEmpty()) { el.setAttribute("key", key); } el.setAttribute("type", "Text"); el.setAttribute("value", value); m_d->currentElement.appendChild(el); } void KisAslXmlWriter::writeBoolean(const QString &key, bool value) { QDomElement el = m_d->document.createElement("node"); if (!key.isEmpty()) { el.setAttribute("key", key); } el.setAttribute("type", "Boolean"); el.setAttribute("value", KisDomUtils::toString(value)); m_d->currentElement.appendChild(el); } void KisAslXmlWriter::writeColor(const QString &key, const QColor &value) { enterDescriptor(key, "", "RGBC"); writeDouble("Rd ", value.red()); writeDouble("Grn ", value.green()); writeDouble("Bl ", value.blue()); leaveDescriptor(); } void KisAslXmlWriter::writePoint(const QString &key, const QPointF &value) { enterDescriptor(key, "", "CrPt"); writeDouble("Hrzn", value.x()); writeDouble("Vrtc", value.y()); leaveDescriptor(); } void KisAslXmlWriter::writePhasePoint(const QString &key, const QPointF &value) { enterDescriptor(key, "", "Pnt "); writeDouble("Hrzn", value.x()); writeDouble("Vrtc", value.y()); leaveDescriptor(); } void KisAslXmlWriter::writeOffsetPoint(const QString &key, const QPointF &value) { enterDescriptor(key, "", "Pnt "); writeUnitFloat("Hrzn", "#Prc", value.x()); writeUnitFloat("Vrtc", "#Prc", value.y()); leaveDescriptor(); } void KisAslXmlWriter::writeCurve(const QString &key, const QString &name, const QVector &points) { enterDescriptor(key, "", "ShpC"); writeText("Nm ", name); enterList("Crv "); Q_FOREACH (const QPointF &pt, points) { writePoint("", pt); } leaveList(); leaveDescriptor(); } QString KisAslXmlWriter::writePattern(const QString &key, const KoPatternSP pattern) { enterDescriptor(key, "", "KisPattern"); writeText("Nm ", pattern->name()); QString uuid = KisAslWriterUtils::getPatternUuidLazy(pattern); writeText("Idnt", uuid); // Write pattern data QBuffer buffer; buffer.open(QIODevice::WriteOnly); pattern->savePatToDevice(&buffer); QDomCDATASection dataSection = m_d->document.createCDATASection(qCompress(buffer.buffer()).toBase64()); QDomElement dataElement = m_d->document.createElement("node"); dataElement.setAttribute("type", "KisPatternData"); dataElement.setAttribute("key", "Data"); dataElement.appendChild(dataSection); m_d->currentElement.appendChild(dataElement); leaveDescriptor(); return uuid; } void KisAslXmlWriter::writePatternRef(const QString &key, const KoPatternSP pattern, const QString &uuid) { enterDescriptor(key, "", "Ptrn"); writeText("Nm ", pattern->name()); writeText("Idnt", uuid); leaveDescriptor(); } void KisAslXmlWriter::writeGradientImpl(const QString &key, const QString &name, QVector colors, QVector transparencies, QVector positions, QVector middleOffsets) { enterDescriptor(key, "Gradient", "Grdn"); writeText("Nm ", name); writeEnum("GrdF", "GrdF", "CstS"); writeDouble("Intr", 4096); enterList("Clrs"); for (int i = 0; i < colors.size(); i++) { enterDescriptor("", "", "Clrt"); writeColor("Clr ", colors[i]); writeEnum("Type", "Clry", "UsrS"); // NOTE: we do not support BG/FG color tags writeInteger("Lctn", positions[i] * 4096.0); writeInteger("Mdpn", middleOffsets[i] * 100.0); leaveDescriptor(); }; leaveList(); enterList("Trns"); for (int i = 0; i < colors.size(); i++) { enterDescriptor("", "", "TrnS"); writeUnitFloat("Opct", "#Prc", transparencies[i] * 100.0); writeInteger("Lctn", positions[i] * 4096.0); writeInteger("Mdpn", middleOffsets[i] * 100.0); leaveDescriptor(); }; leaveList(); leaveDescriptor(); } void KisAslXmlWriter::writeSegmentGradient(const QString &key, const KoSegmentGradient *gradient) { const QList&segments = gradient->segments(); QVector colors; QVector transparencies; QVector positions; QVector middleOffsets; Q_FOREACH (const KoGradientSegment *seg, segments) { const qreal start = seg->startOffset(); const qreal end = seg->endOffset(); const qreal mid = (end - start) > DBL_EPSILON ? (seg->middleOffset() - start) / (end - start) : 0.5; QColor color = seg->startColor().toQColor(); qreal transparency = color.alphaF(); color.setAlphaF(1.0); colors << color; transparencies << transparency; positions << start; middleOffsets << mid; } // last segment - { + + if (!segments.isEmpty()) { const KoGradientSegment *lastSeg = segments.last(); QColor color = lastSeg->endColor().toQColor(); qreal transparency = color.alphaF(); color.setAlphaF(1.0); colors << color; transparencies << transparency; positions << lastSeg->endOffset(); middleOffsets << 0.5; } writeGradientImpl(key, gradient->name(), colors, transparencies, positions, middleOffsets); } void KisAslXmlWriter::writeStopGradient(const QString &key, const KoStopGradient *gradient) { QVector colors; QVector transparencies; QVector positions; QVector middleOffsets; Q_FOREACH (const KoGradientStop &stop, gradient->stops()) { QColor color = stop.second.toQColor(); qreal transparency = color.alphaF(); color.setAlphaF(1.0); colors << color; transparencies << transparency; positions << stop.first; middleOffsets << 0.5; } writeGradientImpl(key, gradient->name(), colors, transparencies, positions, middleOffsets); } diff --git a/libs/resources/sql/create_resources.sql b/libs/resources/sql/create_resources.sql index 8637356999..dcfa8679fd 100644 --- a/libs/resources/sql/create_resources.sql +++ b/libs/resources/sql/create_resources.sql @@ -1,15 +1,15 @@ CREATE TABLE IF NOT EXISTS resources ( id INTEGER PRIMARY KEY /* within this database, a unique and stable id for this resource */ , resource_type_id INTEGER /* points to the type of this resource */ , storage_id INTEGER /* points to the storage object that contains the actual resource */ , name TEXT NOT NULL /* the untranslatable name of the resource */ , filename TEXT NOT NULL /* the filename of the resource RELATIVE to the storage path and resource type */ , tooltip TEXT /* a translated text that can be shown in the UI */ , thumbnail BLOB /* the image representing the resource visually*/ , status INTEGER /* active resources are visible in the UI, inactive resources are considered "deleted" */ , temporary INTEGER /* temporary resources are removed from the database on startup */ , version INTEGER /* the current version number of the resource (cached for performance reasons */ , FOREIGN KEY(resource_type_id) REFERENCES resource_types(id) , UNIQUE(storage_id, resource_type_id, name) -, UNIQUE(storage_id, filename) +, UNIQUE(storage_id, filename, resource_type_id) ); diff --git a/libs/ui/KisActionPlugin.h b/libs/ui/KisActionPlugin.h index 43e551bc6d..a9124357f8 100644 --- a/libs/ui/KisActionPlugin.h +++ b/libs/ui/KisActionPlugin.h @@ -1,63 +1,63 @@ /* * Copyright (c) 2013 Sven Langkamp * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_VIEW_PLUGIN_H #define KIS_VIEW_PLUGIN_H #include #include #include class KisOperation; class KisOperationUIFactory; class KisAction; class KisViewManager; /** * KisActionPlugin is the base for plugins which add actions to the main window */ class KRITAUI_EXPORT KisActionPlugin : public QObject { Q_OBJECT public: KisActionPlugin(QObject *parent = 0); ~KisActionPlugin() override; protected: /** * Registers a KisAction to the UI and action manager. - * @param name - title of the action in the krita4.xmlgui file + * @param name - title of the action in the krita5.xmlgui file * @param action the action that should be added */ void addAction(const QString& name, KisAction *action); KisAction *createAction(const QString &name); void addUIFactory(KisOperationUIFactory *factory); void addOperation(KisOperation *operation); QPointer viewManager() const; private: QPointer m_viewManager; }; #endif // KIS_VIEW_PLUGIN_H diff --git a/libs/ui/KisApplication.cpp b/libs/ui/KisApplication.cpp index 4deb606b1a..73f6c8f6e3 100644 --- a/libs/ui/KisApplication.cpp +++ b/libs/ui/KisApplication.cpp @@ -1,1016 +1,1028 @@ /* * 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 #include #include #include #include #include "KoConfig.h" #include #include #include "thememanager.h" #include "KisDocument.h" #include "KisMainWindow.h" #include "KisAutoSaveRecoveryDialog.h" #include "KisPart.h" #include #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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "widgets/KisScreenColorPicker.h" #include "KisDlgInternalColorSelector.h" #include #include #include "kis_file_layer.h" #include "kis_group_layer.h" #include "kis_node_commands_adapter.h" #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}; QVector earlyRemoteArguments; }; 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; } } } + + // if style is set from config, try to load that + KisConfig cfg(true); + QString widgetStyleFromConfig = cfg.widgetStyle(); + if(widgetStyleFromConfig != "") { + qApp->setStyle(widgetStyleFromConfig); + } + } else { qDebug() << "Style override disabled, using" << style()->objectName(); } + + } #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(ResourceType::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(ResourceType::PaintOpPresets, "data", "/paintoppresets/"); KoResourcePaths::addResourceType(ResourceType::Workspaces, "data", "/workspaces/"); KoResourcePaths::addResourceType(ResourceType::WindowLayouts, "data", "/windowlayouts/"); KoResourcePaths::addResourceType(ResourceType::Sessions, "data", "/sessions/"); KoResourcePaths::addResourceType("psd_layer_style_collections", "data", "/asl"); KoResourcePaths::addResourceType(ResourceType::Patterns, "data", "/patterns/", true); KoResourcePaths::addResourceType(ResourceType::Gradients, "data", "/gradients/"); KoResourcePaths::addResourceType(ResourceType::Gradients, "data", "/gradients/", true); KoResourcePaths::addResourceType(ResourceType::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(ResourceType::FilterEffects, "data", "/effects/"); KoResourcePaths::addResourceType("tags", "data", "/tags/"); KoResourcePaths::addResourceType("templates", "data", "/templates"); KoResourcePaths::addResourceType("pythonscripts", "data", "/pykrita"); KoResourcePaths::addResourceType(ResourceType::Symbols, "data", "/symbols"); KoResourcePaths::addResourceType("preset_icons", "data", "/preset_icons"); KoResourcePaths::addResourceType(ResourceType::GamutMasks, "data", "/gamutmasks/", true); // // Extra directories to look for create resources. (Does anyone actually use that anymore?) // KoResourcePaths::addResourceDir(ResourceType::Gradients, "/usr/share/create/gradients/gimp"); // KoResourcePaths::addResourceDir(ResourceType::Gradients, QDir::homePath() + QString("/.create/gradients/gimp")); // KoResourcePaths::addResourceDir(ResourceType::Patterns, "/usr/share/create/patterns/gimp"); // KoResourcePaths::addResourceDir(ResourceType::Patterns, QDir::homePath() + QString("/.create/patterns/gimp")); // KoResourcePaths::addResourceDir(ResourceType::Brushes, "/usr/share/create/brushes/gimp"); // KoResourcePaths::addResourceDir(ResourceType::Brushes, QDir::homePath() + QString("/.create/brushes/gimp")); // KoResourcePaths::addResourceDir(ResourceType::Palettes, "/usr/share/create/swatches"); // KoResourcePaths::addResourceDir(ResourceType::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/"); // between 4.2.x and 4.3.0 there was a change from 'taskset' to 'tasksets' // so to make older resource folders compatible with the new version, let's rename the folder // so no tasksets are lost. if (d.exists(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/taskset/")) { d.rename(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/taskset/", QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/tasksets/"); } d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/tasksets/"); 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/"); } bool KisApplication::registerResources() { KisResourceLoaderRegistry *reg = KisResourceLoaderRegistry::instance(); reg->add(new KisResourceLoader(ResourceType::PaintOpPresets, ResourceType::PaintOpPresets, i18n("Brush presets"), QStringList() << "application/x-krita-paintoppreset")); reg->add(new KisResourceLoader(ResourceSubType::GbrBrushes, ResourceType::Brushes, i18n("Brush tips"), QStringList() << "image/x-gimp-brush")); reg->add(new KisResourceLoader(ResourceSubType::GihBrushes, ResourceType::Brushes, i18n("Brush tips"), QStringList() << "image/x-gimp-brush-animated")); reg->add(new KisResourceLoader(ResourceSubType::SvgBrushes, ResourceType::Brushes, i18n("Brush tips"), QStringList() << "image/svg+xml")); reg->add(new KisResourceLoader(ResourceSubType::PngBrushes, ResourceType::Brushes, i18n("Brush tips"), QStringList() << "image/png")); reg->add(new KisResourceLoader(ResourceSubType::SegmentedGradients, ResourceType::Gradients, i18n("Gradients"), QStringList() << "application/x-gimp-gradient")); reg->add(new KisResourceLoader(ResourceSubType::StopGradients, ResourceType::Gradients, i18n("Gradients"), QStringList() << "application/x-karbon-gradient" << "image/svg+xml")); reg->add(new KisResourceLoader(ResourceType::Palettes, ResourceType::Palettes, i18n("Palettes"), QStringList() << KisMimeDatabase::mimeTypeForSuffix("kpl") << KisMimeDatabase::mimeTypeForSuffix("gpl") << KisMimeDatabase::mimeTypeForSuffix("pal") << KisMimeDatabase::mimeTypeForSuffix("act") << KisMimeDatabase::mimeTypeForSuffix("aco") << KisMimeDatabase::mimeTypeForSuffix("css") << KisMimeDatabase::mimeTypeForSuffix("colors") << KisMimeDatabase::mimeTypeForSuffix("xml") << KisMimeDatabase::mimeTypeForSuffix("sbz"))); QList src = QImageReader::supportedMimeTypes(); QStringList allImageMimes; Q_FOREACH(const QByteArray ba, src) { if (QImageWriter::supportedMimeTypes().contains(ba)) { allImageMimes << QString::fromUtf8(ba); } } allImageMimes << KisMimeDatabase::mimeTypeForSuffix("pat"); reg->add(new KisResourceLoader(ResourceType::Patterns, ResourceType::Patterns, i18n("Patterns"), allImageMimes)); reg->add(new KisResourceLoader(ResourceType::Workspaces, ResourceType::Workspaces, i18n("Workspaces"), QStringList() << "application/x-krita-workspace")); reg->add(new KisResourceLoader(ResourceType::Symbols, ResourceType::Symbols, i18n("SVG symbol libraries"), QStringList() << "image/svg+xml")); reg->add(new KisResourceLoader(ResourceType::WindowLayouts, ResourceType::WindowLayouts, i18n("Window layouts"), QStringList() << "application/x-krita-windowlayout")); reg->add(new KisResourceLoader(ResourceType::Sessions, ResourceType::Sessions, i18n("Sessions"), QStringList() << "application/x-krita-session")); reg->add(new KisResourceLoader(ResourceType::GamutMasks, ResourceType::GamutMasks, i18n("Gamut masks"), QStringList() << "application/x-krita-gamutmasks")); reg->add(new KisResourceLoader(ResourceType::LayerStyles, ResourceType::LayerStyles, ResourceType::LayerStyles, QStringList() << "application/x-photoshop-style")); if (!KisResourceCacheDb::initialize(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation))) { QMessageBox::critical(0, i18nc("@title:window", "Krita: Fatal error"), i18n("%1\n\nKrita will quit now.", KisResourceCacheDb::lastError())); //return false; } KisResourceLocator::LocatorError r = KisResourceLocator::instance()->initialize(KoResourcePaths::getApplicationRoot() + "/share/krita"); connect(KisResourceLocator::instance(), SIGNAL(progressMessage(const QString&)), this, SLOT(setSplashScreenLoadingText(const QString&))); if (r != KisResourceLocator::LocatorError::Ok ) { QMessageBox::critical(0, i18nc("@title:window", "Krita: Fatal error"), KisResourceLocator::instance()->errorMessages().join('\n') + i18n("\n\nKrita will quit now.")); //return false; } return true; } void KisApplication::loadPlugins() { // qDebug() << "loadPlugins();"; KoShapeRegistry* r = KoShapeRegistry::instance(); r->add(new KisShapeSelectionFactory()); KoColorSpaceRegistry::instance(); KisActionRegistry::instance(); KisFilterRegistry::instance(); KisGeneratorRegistry::instance(); KisPaintOpRegistry::instance(); KoToolRegistry::instance(); KoDockRegistry::instance(); } void KisApplication::loadGuiPlugins() { // 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(); } 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 if (!registerResources()) { return false; } // 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(); KisWindowLayoutResourceSP windowLayout = rserver->resourceByName(args.windowLayout()); if (windowLayout) { windowLayout->applyLayout(); } } if (showmainWindow) { d->mainWindow = kisPart->currentMainwindow(); if (!args.workspace().isEmpty()) { KoResourceServer * rserver = KisResourceServerProvider::instance()->workspaceServer(); KisWorkspaceResourceSP workspace = rserver->resourceByName(args.workspace()); if (workspace) { d->mainWindow->restoreWorkspace(workspace->resourceId()); } } 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.createDocumentFromArguments(); 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, false, 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++; } } } } } } //add an image as file-layer if (!args.fileLayer().isEmpty()){ if (d->mainWindow->viewManager()->image()){ KisFileLayer *fileLayer = new KisFileLayer(d->mainWindow->viewManager()->image(), "", args.fileLayer(), KisFileLayer::None, d->mainWindow->viewManager()->image()->nextLayerName(), OPACITY_OPAQUE_U8); QFileInfo fi(fileLayer->path()); if (fi.exists()){ KisNodeCommandsAdapter adapter(d->mainWindow->viewManager()); adapter.addNode(fileLayer, d->mainWindow->viewManager()->activeNode()->parent(), d->mainWindow->viewManager()->activeNode()); } else{ QMessageBox::warning(nullptr, i18nc("@title:window", "Krita:Warning"), i18n("Cannot add %1 as a file layer: the file does not exist.", fileLayer->path())); } } else if (this->isRunning()){ QMessageBox::warning(nullptr, i18nc("@title:window", "Krita:Warning"), i18n("Cannot add the file layer: no document is open.\n\n" "You can create a new document using the --new-image option, or you can open an existing file.\n\n" "If you instead want to add the file layer to a document in an already running instance of Krita, check the \"Allow only one instance of Krita\" checkbox in the settings (Settings -> General -> Window).")); } else { QMessageBox::warning(nullptr, i18nc("@title:window", "Krita: Warning"), i18n("Cannot add the file layer: no document is open.\n" "You can either create a new file using the --new-image option, or you can open an existing file.")); } } // 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); } Q_FOREACH(const QByteArray &message, d->earlyRemoteArguments) { executeRemoteArguments(message, d->mainWindow); } KisUsageLogger::writeSysInfo(KisUsageLogger::screenInformation()); // not calling this before since the program will quit there. return true; } KisApplication::~KisApplication() { KisResourceCacheDb::deleteTemporaryResources(); } void KisApplication::setSplashScreen(QWidget *splashScreen) { d->splashScreen = qobject_cast(splashScreen); } void KisApplication::setSplashScreenLoadingText(const QString &textToLoad) { if (d->splashScreen) { 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::executeRemoteArguments(QByteArray message, KisMainWindow *mainWindow) { KisApplicationArguments args = KisApplicationArguments::deserialize(message); const bool doTemplate = args.doTemplate(); const bool doNewImage = args.doNewImage(); const int argsCount = args.filenames().count(); bool documentCreated = false; // Create a new image, if needed if (doNewImage) { KisDocument *doc = args.createDocumentFromArguments(); if (doc) { KisPart::instance()->addDocument(doc); d->mainWindow->addViewAndNotifyLoadingCompleted(doc); } } 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) { documentCreated |= createNewDocFromTemplate(filename, mainWindow); } else if (QFile(filename).exists()) { KisMainWindow::OpenFlags flags = d->batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; documentCreated |= mainWindow->openDocument(QUrl::fromLocalFile(filename), flags); } } } //add an image as file-layer if called in another process and singleApplication is enabled if (!args.fileLayer().isEmpty()){ if (argsCount > 0 && !documentCreated){ //arg was passed but document was not created so don't add the file layer. QMessageBox::warning(mainWindow, i18nc("@title:window", "Krita:Warning"), i18n("Couldn't open file %1",args.filenames().at(argsCount - 1))); } else if (mainWindow->viewManager()->image()){ KisFileLayer *fileLayer = new KisFileLayer(mainWindow->viewManager()->image(), "", args.fileLayer(), KisFileLayer::None, mainWindow->viewManager()->image()->nextLayerName(), OPACITY_OPAQUE_U8); QFileInfo fi(fileLayer->path()); if (fi.exists()){ KisNodeCommandsAdapter adapter(d->mainWindow->viewManager()); adapter.addNode(fileLayer, d->mainWindow->viewManager()->activeNode()->parent(), d->mainWindow->viewManager()->activeNode()); } else{ QMessageBox::warning(mainWindow, i18nc("@title:window", "Krita:Warning"), i18n("Cannot add %1 as a file layer: the file does not exist.", fileLayer->path())); } } else { QMessageBox::warning(mainWindow, i18nc("@title:window", "Krita:Warning"), i18n("Cannot add the file layer: no document is open.")); } } } void KisApplication::remoteArguments(QByteArray message, QObject *socket) { Q_UNUSED(socket); // check if we have any mainwindow KisMainWindow *mw = qobject_cast(qApp->activeWindow()); if (!mw && KisPart::instance()->mainWindows().size() > 0) { mw = KisPart::instance()->mainWindows().first(); } if (!mw) { d->earlyRemoteArguments << message; return; } executeRemoteArguments(message, mw); } 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); // 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::resetConfig() { KIS_ASSERT_RECOVER_RETURN(qApp->thread() == QThread::currentThread()); KSharedConfigPtr config = KSharedConfig::openConfig(); config->markAsClean(); // find user settings file const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QString kritarcPath = configPath + QStringLiteral("/kritarc"); QFile kritarcFile(kritarcPath); if (kritarcFile.exists()) { if (kritarcFile.open(QFile::ReadWrite)) { QString backupKritarcPath = kritarcPath + QStringLiteral(".backup"); QFile backupKritarcFile(backupKritarcPath); if (backupKritarcFile.exists()) { backupKritarcFile.remove(); } QMessageBox::information(0, i18nc("@title:window", "Krita"), i18n("Krita configurations reset!\n\n" "Backup file was created at: %1\n\n" "Restart Krita for changes to take effect.", backupKritarcPath), QMessageBox::Ok, QMessageBox::Ok); // clear file kritarcFile.rename(backupKritarcPath); kritarcFile.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(); // Restore to default workspace KConfigGroup cfg = KSharedConfig::openConfig()->group("MainWindow"); QString currentWorkspace = cfg.readEntry("CurrentWorkspace", "Default"); KoResourceServer * rserver = KisResourceServerProvider::instance()->workspaceServer(); KisWorkspaceResourceSP workspace = rserver->resourceByName(currentWorkspace); if (workspace) { d->mainWindow->restoreWorkspace(workspace->resourceId()); } } void KisApplication::askresetConfig() { 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) { resetConfig(); } } diff --git a/libs/ui/KisImportExportErrorCode.cpp b/libs/ui/KisImportExportErrorCode.cpp index b66d5edc78..b76d4f7493 100644 --- a/libs/ui/KisImportExportErrorCode.cpp +++ b/libs/ui/KisImportExportErrorCode.cpp @@ -1,223 +1,223 @@ /* * 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 "KisImportExportErrorCode.h" #include #include KisImportExportComplexError::KisImportExportComplexError(QFileDevice::FileError error) : m_error(error) { } QString KisImportExportComplexError::qtErrorMessage() const { // Error descriptions in most cases taken from https://doc.qt.io/qt-5/qfiledevice.html QString unspecifiedError = i18n("An unspecified error occurred."); switch (m_error) { case QFileDevice::FileError::NoError : // Returning this file error may mean that something is wrong in our code. // Successful operation should return ImportExportCodes::OK instead. return i18n("The action has been completed successfully."); case QFileDevice::FileError::ReadError : return i18n("An error occurred when reading from the file."); case QFileDevice::FileError::WriteError : return i18n("An error occurred when writing to the file."); case QFileDevice::FileError::FatalError : return i18n("A fatal error occurred."); case QFileDevice::FileError::ResourceError : return i18n("Out of resources (e.g. out of memory)."); case QFileDevice::FileError::OpenError : return i18n("The file could not be opened."); case QFileDevice::FileError::AbortError : return i18n("The operation was aborted."); case QFileDevice::FileError::TimeOutError : return i18n("A timeout occurred."); case QFileDevice::FileError::UnspecifiedError : return unspecifiedError; case QFileDevice::FileError::RemoveError : return i18n("The file could not be removed."); case QFileDevice::FileError::RenameError : return i18n("The file could not be renamed."); case QFileDevice::FileError::PositionError : return i18n("The position in the file could not be changed."); case QFileDevice::FileError::ResizeError : return i18n("The file could not be resized."); case QFileDevice::FileError::PermissionsError : return i18n("Permission denied. Krita is not allowed to read or write to the file."); case QFileDevice::FileError::CopyError : return i18n("The file could not be copied."); } return unspecifiedError; } KisImportExportErrorCannotRead::KisImportExportErrorCannotRead() : KisImportExportComplexError(QFileDevice::FileError()) { } KisImportExportErrorCannotRead::KisImportExportErrorCannotRead(QFileDevice::FileError error) : KisImportExportComplexError(error) { KIS_ASSERT_RECOVER_NOOP(error != QFileDevice::NoError); } QString KisImportExportErrorCannotRead::errorMessage() const { return i18n("Cannot open file for reading. Reason: %1", qtErrorMessage()); } bool KisImportExportErrorCannotRead::operator==(KisImportExportErrorCannotRead other) { return other.m_error == m_error; } KisImportExportErrorCannotWrite::KisImportExportErrorCannotWrite() : KisImportExportComplexError(QFileDevice::FileError()) { } KisImportExportErrorCannotWrite::KisImportExportErrorCannotWrite(QFileDevice::FileError error) : KisImportExportComplexError(error) { KIS_ASSERT_RECOVER_NOOP(error != QFileDevice::NoError); } QString KisImportExportErrorCannotWrite::errorMessage() const { return i18n("Cannot open file for writing. Reason: %1", qtErrorMessage()); } bool KisImportExportErrorCannotWrite::operator==(KisImportExportErrorCannotWrite other) { return other.m_error == m_error; } KisImportExportErrorCode::KisImportExportErrorCode() : errorFieldUsed(None), cannotRead(), cannotWrite() { } KisImportExportErrorCode::KisImportExportErrorCode(ImportExportCodes::ErrorCodeID id) : errorFieldUsed(CodeId), codeId(id), cannotRead(), cannotWrite() { } KisImportExportErrorCode::KisImportExportErrorCode(KisImportExportErrorCannotRead error) : errorFieldUsed(CannotRead), cannotRead(error), cannotWrite() { } KisImportExportErrorCode::KisImportExportErrorCode(KisImportExportErrorCannotWrite error) : errorFieldUsed(CannotWrite), cannotRead(), cannotWrite(error) { } bool KisImportExportErrorCode::isOk() const { // if cannotRead or cannotWrite is "NoError", it means that something is wrong in our code return errorFieldUsed == CodeId && codeId == ImportExportCodes::OK; } bool KisImportExportErrorCode::isCancelled() const { return errorFieldUsed == CodeId && codeId == ImportExportCodes::Cancelled; } bool KisImportExportErrorCode::isInternalError() const { return errorFieldUsed == CodeId && codeId == ImportExportCodes::InternalError; } QString KisImportExportErrorCode::errorMessage() const { QString internal = i18n("Unexpected error. Please contact developers."); if (errorFieldUsed == CannotRead) { return cannotRead.errorMessage(); } else if (errorFieldUsed == CannotWrite) { return cannotWrite.errorMessage(); } else if (errorFieldUsed == CodeId) { switch (codeId) { // Reading case ImportExportCodes::FileNotExist: - return i18n("The file doesn't exists."); + return i18n("The file doesn't exist."); case ImportExportCodes::NoAccessToRead: return i18n("Permission denied: Krita is not allowed to read the file."); case ImportExportCodes::FileFormatIncorrect: return i18n("The file format cannot be parsed."); case ImportExportCodes::FormatFeaturesUnsupported: return i18n("The file format contains unsupported features."); case ImportExportCodes::FormatColorSpaceUnsupported: return i18n("The file format contains unsupported color space."); case ImportExportCodes::ErrorWhileReading: return i18n("Error occurred while reading from the file."); // Writing case ImportExportCodes::CannotCreateFile: return i18n("The file cannot be created."); case ImportExportCodes::NoAccessToWrite: return i18n("Permission denied: Krita is not allowed to write to the file."); case ImportExportCodes::InsufficientMemory: return i18n("There is not enough disk space left to save the file."); case ImportExportCodes::ErrorWhileWriting: return i18n("Error occurred while writing to the file."); // Both case ImportExportCodes::Cancelled: return i18n("The action was cancelled by the user."); // Other case ImportExportCodes::Failure: return i18n("Unknown error."); case ImportExportCodes::InternalError: return internal; // OK case ImportExportCodes::OK: return i18n("The action has been completed successfully."); default: return internal; } } return internal; // errorFieldUsed = None } bool KisImportExportErrorCode::operator==(KisImportExportErrorCode errorCode) { if (errorFieldUsed != errorCode.errorFieldUsed) { return false; } if (errorFieldUsed == CodeId) { return codeId == errorCode.codeId; } if (errorFieldUsed == CannotRead) { return cannotRead == errorCode.cannotRead; } return cannotWrite == errorCode.cannotWrite; } QDebug operator<<(QDebug d, const KisImportExportErrorCode& errorCode) { switch(errorCode.errorFieldUsed) { case KisImportExportErrorCode::None: d << "None of the error fields is in use."; break; case KisImportExportErrorCode::CannotRead: d << "Cannot read: " << errorCode.cannotRead.m_error; break; case KisImportExportErrorCode::CannotWrite: d << "Cannot write: " << errorCode.cannotRead.m_error; break; case KisImportExportErrorCode::CodeId: d << "Error code = " << errorCode.codeId; } d << " " << errorCode.errorMessage(); return d; } diff --git a/libs/ui/KisMainWindow.cpp b/libs/ui/KisMainWindow.cpp index 754fba2a64..5f610cf094 100644 --- a/libs/ui/KisMainWindow.cpp +++ b/libs/ui/KisMainWindow.cpp @@ -1,2673 +1,2722 @@ /* 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 #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 "KoToolDocker.h" #include "KoToolBoxDocker_p.h" #include #include #include #include #include #include #include #include #include #include #include #ifdef Q_OS_ANDROID #include #endif #include #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 "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) + , styleMenu(new KActionMenu(i18nc("@action:inmenu", "Styles"), parent)) , 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 *importAnimation {0}; KisAction *closeAll {0}; 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 *resetConfigurations {0}; KisAction *toggleDockerTitleBars {0}; KisAction *toggleDetachCanvas {0}; KisAction *fullScreenMode {0}; KisAction *showSessionManager {0}; KisAction *expandingSpacers[2]; + KActionMenu *styleMenu; + QActionGroup* styleActions; + QMap actionMap; + KActionMenu *dockWidgetMenu; KActionMenu *windowMenu; KActionMenu *documentMenu; KActionMenu *workspaceMenu; KHelpMenu *helpMenu {0}; KRecentFilesAction *recentFiles {0}; KisResourceModel *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)) { d->workspacemodel = KisResourceModelProvider::resourceModel(ResourceType::Workspaces); connect(d->workspacemodel, SIGNAL(afterResourcesLayoutReset()), this, SLOT(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]); } + + // Style menu actions + d->styleActions = new QActionGroup(this); + QAction * action; + Q_FOREACH (QString styleName, QStyleFactory::keys()) { + action = new QAction(styleName, d->styleActions); + action->setCheckable(true); + d->actionMap.insert(styleName, action); + d->styleMenu->addAction(d->actionMap.value(styleName)); + } + + + // select the config value, or the current style if that does not exist + QString styleFromConfig = cfg.widgetStyle().toLower(); + QString styleToSelect = styleFromConfig == "" ? style()->objectName().toLower() : styleFromConfig; + + Q_FOREACH (auto key, d->actionMap.keys()) { + if(key.toLower() == styleToSelect) { // does the key match selection + d->actionMap.value(key)->setChecked(true); + } + } + + connect(d->styleActions, SIGNAL(triggered(QAction*)), + this, SLOT(slotUpdateWidgetStyle())); + + + + 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(); // Make sure the python plugins create their actions in time KisPart::instance()->notifyMainWindowIsBeingCreated(this); // If we have customized the toolbars, load that first - setLocalXMLFile(KoResourcePaths::locateLocal("data", "krita4.xmlgui")); - setXMLFile(":/kxmlgui5/krita4.xmlgui"); + setLocalXMLFile(KoResourcePaths::locateLocal("data", "krita5.xmlgui")); + setXMLFile(":/kxmlgui5/krita5.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); if (toolBar) { toolBar->setMovable(KisConfig(true).readEntry("LockAllDockerPanels", false)); 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(); KisWorkspaceResourceSP workspace = rserver->resourceByName(currentWorkspace); if (workspace) { restoreWorkspace(workspace->resourceId()); } 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 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()); #ifdef Q_OS_MACOS connect(subwin, SIGNAL(destroyed()), SLOT(updateSubwindowFlags())); updateSubwindowFlags(); #endif if (d->mdiArea->subWindowList().size() == 1) { imageView->showMaximized(); } else { imageView->show(); } /** * 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(); // 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() { QScopedPointer dlgPreferences(new KisDlgPreferences(this)); if (dlgPreferences->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::setReadWrite(bool readwrite) { d->saveAction->setEnabled(readwrite); d->importFile->setEnabled(readwrite); d->readOnly = !readwrite; updateCaption(); } void KisMainWindow::addRecentURL(const QUrl &url, const QUrl &oldUrl) { // 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) { if (!oldUrl.isEmpty()) { d->recentFiles->removeUrl(oldUrl); } 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()); caption = "RESOURCES REWRITE GOING ON " + 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; // XXX: Why this duplication of of OpenFlags... 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); if (!QFileInfo(url.toLocalFile()).isWritable()) { setReadWrite(false); } // Try to determine whether this was an unnamed autosave if (flags & RecoveryFile && ( url.toLocalFile().startsWith(QDir::tempPath()) || url.toLocalFile().startsWith(QDir::homePath()) ) && ( QFileInfo(url.toLocalFile()).fileName().startsWith(".krita") || QFileInfo(url.toLocalFile()).fileName().startsWith("krita") ) ) { QString path = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation); if (!QFileInfo(path).exists()) { path = QStandardPaths::writableLocation(QStandardPaths::HomeLocation); } newdoc->setUrl(QUrl::fromLocalFile( path + "/" + newdoc->objectName() + ".kra")); } 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) { KisUsageLogger::log(QString("Loading canceled. Error:").arg(errMsg)); 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) { KisUsageLogger::log(QString("Saving canceled. Error:").arg(errMsg)); if (!errMsg.isEmpty()) { // empty when canceled by user QMessageBox::critical(this, i18nc("@title:window", "Krita"), errMsg); } slotSaveCompleted(); } void KisMainWindow::slotSaveCompleted() { KisUsageLogger::log(QString("Saving Completed")); 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()); } QImage KisMainWindow::layoutThumbnail() { int size = 256; qreal scale = qreal(size)/qreal(qMax(geometry().width(), geometry().height())); QImage layoutThumbnail = QImage(qRound(geometry().width()*scale), qRound(geometry().height()*scale), QImage::Format_ARGB32); QPainter gc(&layoutThumbnail); gc.fillRect(0, 0, layoutThumbnail.width(), layoutThumbnail.height(), this->palette().dark()); for (int childW = 0; childW< children().size(); childW++) { if (children().at(childW)->isWidgetType()) { QWidget *w = dynamic_cast(children().at(childW)); if (w->isVisible()) { QRect wRect = QRectF(w->geometry().x()*scale , w->geometry().y()*scale , w->geometry().width()*scale , w->geometry().height()*scale ).toRect(); wRect = wRect.intersected(layoutThumbnail.rect().adjusted(-1, -1, -1, -1)); gc.setBrush(this->palette().window()); if (w == d->widgetStack) { gc.setBrush(d->mdiArea->background()); } gc.setPen(this->palette().windowText().color()); gc.drawRect(wRect); } } } gc.end(); return layoutThumbnail; } 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!"; } } 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(int workspaceId) { KisWorkspaceResourceSP workspace = KisResourceModelProvider::resourceModel(ResourceType::Workspaces) ->resourceForId(workspaceId).dynamicCast(); 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::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::slotResetConfigurations() { KisApplication *kisApp = static_cast(qApp); kisApp->askresetConfig(); } 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); } 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()); } } } +void KisMainWindow::slotUpdateWidgetStyle() +{ + KisConfig cfg(true); + QString themeFromConfig = cfg.widgetStyle(); + + Q_FOREACH (auto key, d->actionMap.keys()) { // find checked style to save to config + if(d->actionMap.value(key)->isChecked()) { + cfg.setWidgetStyle(key); + qApp->setStyle(key); + } + } +} + 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(); } 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(); #if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) QRect geom = this->geometry(); QPoint p(geom.width() / 2 + geom.left(), geom.height() / 2 + geom.top()); QScreen *screen = qApp->screenAt(p); int fileStringWidth = 300; if (screen) { fileStringWidth = int(screen->availableGeometry().width() * .40f); } #else int fileStringWidth = int(QApplication::desktop()->screenGeometry(this).width() * .40f); #endif 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(); KisResourceIterator resourceIterator(KisResourceModelProvider::resourceModel(ResourceType::Workspaces)); KisMainWindow *m_this = this; while (resourceIterator.hasNext()) { KisResourceItemSP resource = resourceIterator.next(); QAction *action = workspaceMenu->addAction(resource->name()); connect(action, &QAction::triggered, this, [=]() { m_this->restoreWorkspace(resource->id()); }); } workspaceMenu->addSeparator(); connect(workspaceMenu->addAction(i18nc("@action:inmenu", "&Import Workspace...")), &QAction::triggered, this, [&]() { QStringList mimeTypes = KisResourceLoaderRegistry::instance()->mimeTypes(ResourceType::Workspaces); 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; auto rserver = KisResourceServerProvider::instance()->workspaceServer(); KisWorkspaceResourceSP workspace(new KisWorkspaceResource("")); workspace->setDockerState(m_this->saveState()); d->viewManager->canvasResourceProvider()->notifySavingWorkspace(workspace); workspace->setValid(true); QString saveLocation = rserver->saveLocation(); QFileInfo fileInfo(saveLocation + name + workspace->defaultFileExtension()); bool fileOverWriteAccepted = false; while(!fileOverWriteAccepted) { name = QInputDialog::getText(this, i18nc("@title:window", "New Workspace..."), i18nc("@label:textbox", "Name:")); if (name.isNull() || name.isEmpty()) { return; } else { fileInfo = QFileInfo(saveLocation + name.split(" ").join("_") + workspace->defaultFileExtension()); if (fileInfo.exists()) { int res = QMessageBox::warning(this, i18nc("@title:window", "Name Already Exists") , i18n("The name '%1' already exists, do you wish to overwrite it?", name) , QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); if (res == QMessageBox::Yes) fileOverWriteAccepted = true; } else { fileOverWriteAccepted = true; } } } workspace->setFilename(fileInfo.fileName()); 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::updateSubwindowFlags() { bool onlyOne = false; if (d->mdiArea->subWindowList().size() == 1 && d->mdiArea->viewMode() == QMdiArea::SubWindowView) { onlyOne = true; } Q_FOREACH (QMdiSubWindow *subwin, d->mdiArea->subWindowList()) { if (onlyOne) { subwin->setWindowFlags(subwin->windowFlags() | Qt::FramelessWindowHint); subwin->showMaximized(); } else { subwin->setWindowFlags((subwin->windowFlags() | Qt::FramelessWindowHint) ^ Qt::FramelessWindowHint); } } } 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 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(); } } } #ifdef Q_OS_MACOS updateSubwindowFlags(); #endif 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->resourceCount() == 0) { 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::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->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->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->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->resetConfigurations = actionManager->createAction("reset_configurations"); connect(d->resetConfigurations, SIGNAL(triggered()), this, SLOT(slotResetConfigurations())); 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); + actionCollection()->addAction("style_menu", d->styleMenu); // for widget styles: breeze, fusion, etc + 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/KisMainWindow.h b/libs/ui/KisMainWindow.h index 79496225fa..e67fdf77be 100644 --- a/libs/ui/KisMainWindow.h +++ b/libs/ui/KisMainWindow.h @@ -1,515 +1,517 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Torben Weis Copyright (C) 2000-2004 David Faure 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_MAIN_WINDOW_H #define KIS_MAIN_WINDOW_H #include "kritaui_export.h" #include #include #include #include #include #include #include #include "KisView.h" #include class QCloseEvent; class QMoveEvent; class KoCanvasResourceProvider; class KisDocument; class KoDockFactoryBase; class QDockWidget; class KisView; class KisViewManager; class KoCanvasController; /** * @brief Main window for Krita * * This class is used to represent a main window within a Krita session. Each * main window contains a menubar and some toolbars, and potentially several * views of several canvases. * */ class KRITAUI_EXPORT KisMainWindow : public KXmlGuiWindow, public KoCanvasSupervisor { Q_OBJECT public: enum OpenFlag { None = 0, Import = 0x1, BatchMode = 0x2, RecoveryFile = 0x4 }; Q_DECLARE_FLAGS(OpenFlags, OpenFlag) public: /** * Constructor. * * Initializes a Calligra main window (with its basic GUI etc.). */ explicit KisMainWindow(QUuid id = QUuid()); /** * Destructor. */ ~KisMainWindow() override; QUuid id() const; /** * @brief showView shows the given view, in @p subWindow if not * null, in a new tab otherwise. */ virtual void showView(KisView *view, QMdiSubWindow *subWindow = 0); /** * @returns the currently active view */ KisView *activeView() const; /** * Sets the maximum number of recent documents entries. */ void setMaxRecentItems(uint _number); /** * The document opened a URL -> store into recent documents list. * @param oldUrl if not empty, @p url will replace @p oldUrl if present */ void addRecentURL(const QUrl &url, const QUrl &oldUrl = QUrl()); /** * get list of URL strings for recent files */ QList recentFilesUrls(); /** * removes the given url from the list of recent files */ void removeRecentUrl(const QUrl &url); /** * Load the desired document and show it. * @param url the URL to open * * @return TRUE on success. */ bool openDocument(const QUrl &url, OpenFlags flags); /** * Activate a view containing the document in this window, creating one if needed. */ void showDocument(KisDocument *document); /** * Toggles between showing the welcome screen and the MDI area * * hack: There seems to be a bug that prevents events happening to the MDI area if it * isn't actively displayed (set in the widgetStack). This can cause things like the title bar * not to update correctly Before doing any actions related to opening or creating documents, * make sure to switch this first to make sure everything can communicate to the MDI area correctly */ void showWelcomeScreen(bool show); /** * Saves the document, asking for a filename if necessary. * * @param saveas if set to TRUE the user is always prompted for a filename * @param silent if set to TRUE rootDocument()->setTitleModified will not be called. * * @return TRUE on success, false on error or cancel * (don't display anything in this case, the error dialog box is also implemented here * but restore the original URL in slotFileSaveAs) */ bool saveDocument(KisDocument *document, bool saveas, bool isExporting); void setReadWrite(bool readwrite); /// Return the list of dock widgets belonging to this main window. QList dockWidgets() const; QDockWidget* dockWidget(const QString &id); QList canvasObservers() const override; KoCanvasResourceProvider *resourceManager() const; int viewCount() const; void saveWindowState(bool restoreNormalState =false); const KConfigGroup &windowStateConfig() const; /** * A wrapper around restoreState * @param state the saved state * @return TRUE on success */ bool restoreWorkspace(int workspaceId); bool restoreWorkspaceState(const QByteArray &state); static void swapWorkspaces(KisMainWindow *a, KisMainWindow *b); KisViewManager *viewManager() const; KisView *addViewAndNotifyLoadingCompleted(KisDocument *document, QMdiSubWindow *subWindow = 0); QStringList showOpenFileDialog(bool isImporting); /** * The top-level window used for a detached canvas. */ QWidget *canvasWindow() const; bool canvasDetached() const; /** * Shows if the main window is saving anything right now. If the * user presses Ctrl+W too fast, then the document can be close * before the saving is completed. I'm not sure if it is fixable * in any way without avoiding using porcessEvents() * everywhere (DK) * * Don't use it unless you have no option. */ bool hackIsSaving() const; /// Copy the given file into the bundle directory. bool installBundle(const QString &fileName) const; /** * @brief layoutThumbnail * @return image for the workspaces. */ QImage layoutThumbnail(); Q_SIGNALS: /** * This signal is emitted if the document has been saved successfully. */ void documentSaved(); /// This signal is emitted when this windows has finished loading of a /// document. The document may be opened in another window in the end. /// In this case, the signal means there is no link between the window /// and the document anymore. void loadCompleted(); /// This signal is emitted right after the docker states have been succefully restored from config void restoringDone(); /// This signal is emitted when the color theme changes void themeChanged(); /// This signal is emitted when the shortcut key configuration has changed void keyBindingsChanged(); void guiLoadingFinished(); /// emitted when the window is migrated among different screens void screenChanged(); public Q_SLOTS: /** * clears the list of the recent files */ void clearRecentFiles(); /** * Slot for opening a new document. * * If the current document is empty, the new document replaces it. * If not, a new mainwindow will be opened for showing the document. */ void slotFileNew(); /** * Slot for opening a saved file. * * If the current document is empty, the opened document replaces it. * If not a new mainwindow will be opened for showing the opened file. */ void slotFileOpen(bool isImporting = false); /** * Slot for opening a file among the recently opened files. * * If the current document is empty, the opened document replaces it. * If not a new mainwindow will be opened for showing the opened file. */ void slotFileOpenRecent(const QUrl &); /** * @brief slotPreferences open the preferences dialog */ void slotPreferences(); /** * Update caption from document info - call when document info * (title in the about page) changes. */ void updateCaption(); /** * Saves the current document with the current name. */ void slotFileSave(); void slotShowSessionManager(); /** * Update the option widgets to the argument ones, removing the currently set widgets. */ void newOptionWidgets(KoCanvasController *controller, const QList > & optionWidgetList); KisView *newView(QObject *document, QMdiSubWindow *subWindow = 0); void notifyChildViewDestroyed(KisView *view); /// Set the active view, this will update the undo/redo actions void setActiveView(KisView *view); void subWindowActivated(); void windowFocused(); /** * Reloads the recent documents list. */ void reloadRecentFileList(); /** * Detach canvas onto a separate window, or restore it back to to main window. */ void setCanvasDetached(bool detached); void slotFileSelected(QString path); void slotEmptyFilePath(); /** * Toggle full screen on/off. */ void viewFullscreen(bool fullScreen); private Q_SLOTS: /** * Save the list of recent files. */ void saveRecentFiles(); void slotLoadCompleted(); void slotLoadCanceled(const QString &); void slotSaveCompleted(); void slotSaveCanceled(const QString &); void forceDockTabFonts(); + void slotUpdateWidgetStyle(); + /** * @internal */ void slotDocumentTitleModified(); /** * Saves the current document with a new name. */ void slotFileSaveAs(); void importAnimation(); /** * Show a dialog with author and document information. */ void slotDocumentInfo(); /** * Closes all open documents. */ bool slotFileCloseAll(); /** * @brief showAboutApplication show the about box */ virtual void showAboutApplication(); /** * Closes the mainwindow. */ void slotFileQuit(); /** * Configure toolbars. */ void slotConfigureToolbars(); /** * Post toolbar config. * (Plug action lists back in, etc.) */ void slotNewToolbarConfig(); /** * Reset User Configurations. */ void slotResetConfigurations(); /** * Shows or hides a toolbar */ void slotToolbarToggled(bool toggle); /** * File --> Import * * This will call slotFileOpen(). */ void slotImportFile(); /** * File --> Export * * This will call slotFileSaveAs(). */ void slotExportFile(); /** * Hide the dockers */ void toggleDockersVisibility(bool visible); /** * Handle theme changes from theme manager */ void slotThemeChanged(); void undo(); void redo(); void updateWindowMenu(); void updateSubwindowFlags(); void setActiveSubWindow(QWidget *window); void configChanged(); void newWindow(); void closeCurrentWindow(); void checkSanity(); /// Quits Krita with error message from m_errorMessage. void showErrorAndDie(); void initializeGeometry(); void showManual(); void switchTab(int index); void windowScreenChanged(QScreen *screen); void slotXmlGuiMakingChanges(bool finished); protected: void closeEvent(QCloseEvent * e) override; void resizeEvent(QResizeEvent * e) override; // QWidget overrides private: friend class KisWelcomePageWidget; void dragMove(QDragMoveEvent *event); void dragLeave(); private: /** * Add a the given view to the list of views of this mainwindow. * This is a private implementation. For public usage please use * newView() and addViewAndNotifyLoadingCompleted(). */ void addView(KisView *view, QMdiSubWindow *subWindow = 0); friend class KisPart; /** * Returns the dockwidget specified by the @p factory. If the dock widget doesn't exist yet it's created. * Add a "view_palette_action_menu" action to your view menu if you want to use closable dock widgets. * @param factory the factory used to create the dock widget if needed * @return the dock widget specified by @p factory (may be 0) */ QDockWidget* createDockWidget(KoDockFactoryBase* factory); bool openDocumentInternal(const QUrl &url, KisMainWindow::OpenFlags flags = 0); /** * Updates the window caption based on the document info and path. */ void updateCaption(const QString & caption, bool modified); void saveWindowSettings(); QPointer activeKisView(); void createActions(); void applyToolBarLayout(); QByteArray borrowWorkspace(KisMainWindow *borrower); private: /** * Struct used in the list created by createCustomDocumentWidgets() */ struct CustomDocumentWidgetItem { /// Pointer to the custom document widget QWidget *widget; /// title used in the sidebar. If left empty it will be displayed as "Custom Document" QString title; /// icon used in the sidebar. If left empty it will use the unknown icon QString icon; }; class Private; Private * const d; QString m_errorMessage; bool m_dieOnError; }; Q_DECLARE_OPERATORS_FOR_FLAGS(KisMainWindow::OpenFlags) #endif diff --git a/libs/ui/KisViewManager.cpp b/libs/ui/KisViewManager.cpp index 62985a4756..57bf7ad252 100644 --- a/libs/ui/KisViewManager.cpp +++ b/libs/ui/KisViewManager.cpp @@ -1,1452 +1,1452 @@ /* * This file is part of KimageShop^WKrayon^WKrita * * Copyright (c) 1999 Matthias Elter * 1999 Michael Koch * 1999 Carsten Pfeiffer * 2002 Patrick Julien * 2003-2011 Boudewijn Rempt * 2004 Clarence Dang * 2011 José Luis Vergara * 2017 L. E. Segovia * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public 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 "KisViewManager.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 "input/kis_input_manager.h" #include "canvas/kis_canvas2.h" #include "canvas/kis_canvas_controller.h" #include "canvas/kis_grid_manager.h" #include "input/kis_input_profile_manager.h" #include "kis_action_manager.h" #include "kis_action.h" #include "kis_canvas_controls_manager.h" #include "kis_canvas_resource_provider.h" #include "kis_composite_progress_proxy.h" #include #include "kis_config.h" #include "kis_config_notifier.h" #include "kis_control_frame.h" #include "kis_coordinates_converter.h" #include "KisDocument.h" #include "kis_favorite_resource_manager.h" #include "kis_filter_manager.h" #include "kis_group_layer.h" #include #include #include "kis_image_manager.h" #include #include "kis_mainwindow_observer.h" #include "kis_mask_manager.h" #include "kis_mimedata.h" #include "kis_mirror_manager.h" #include "kis_node_commands_adapter.h" #include "kis_node.h" #include "kis_node_manager.h" #include "KisDecorationsManager.h" #include #include "kis_paintop_box.h" #include #include "KisPart.h" #include #include "KisResourceServerProvider.h" #include "kis_selection.h" #include "kis_selection_mask.h" #include "kis_selection_manager.h" #include "kis_shape_controller.h" #include "kis_shape_layer.h" #include #include "kis_statusbar.h" #include #include #include "kis_tooltip_manager.h" #include #include "KisView.h" #include "kis_zoom_manager.h" #include "widgets/kis_floating_message.h" #include "kis_signal_auto_connection.h" #include "kis_icon_utils.h" #include "kis_guides_manager.h" #include "kis_derived_resources.h" #include "dialogs/kis_delayed_save_dialog.h" #include #include "kis_signals_blocker.h" class BlockingUserInputEventFilter : public QObject { bool eventFilter(QObject *watched, QEvent *event) override { Q_UNUSED(watched); if(dynamic_cast(event) || dynamic_cast(event) || dynamic_cast(event)) { return true; } else { return false; } } }; class KisViewManager::KisViewManagerPrivate { public: KisViewManagerPrivate(KisViewManager *_q, KActionCollection *_actionCollection, QWidget *_q_parent) : filterManager(_q) , createTemplate(0) , saveIncremental(0) , saveIncrementalBackup(0) , openResourcesDirectory(0) , rotateCanvasRight(0) , rotateCanvasLeft(0) , resetCanvasRotation(0) , wrapAroundAction(0) , levelOfDetailAction(0) , showRulersAction(0) , rulersTrackMouseAction(0) , zoomTo100pct(0) , zoomIn(0) , zoomOut(0) , selectionManager(_q) , statusBar(_q) , controlFrame(_q, _q_parent) , nodeManager(_q) , imageManager(_q) , gridManager(_q) , canvasControlsManager(_q) , paintingAssistantsManager(_q) , actionManager(_q, _actionCollection) , mainWindow(0) , showFloatingMessage(true) , currentImageView(0) , canvasResourceProvider(_q) , canvasResourceManager() , guiUpdateCompressor(30, KisSignalCompressor::POSTPONE, _q) , actionCollection(_actionCollection) , mirrorManager(_q) , inputManager(_q) , actionAuthor(0) , showPixelGrid(0) { KisViewManager::initializeResourceManager(&canvasResourceManager); } public: KisFilterManager filterManager; KisAction *createTemplate; KisAction *createCopy; KisAction *saveIncremental; KisAction *saveIncrementalBackup; KisAction *openResourcesDirectory; KisAction *rotateCanvasRight; KisAction *rotateCanvasLeft; KisAction *resetCanvasRotation; KisAction *wrapAroundAction; KisAction *levelOfDetailAction; KisAction *showRulersAction; KisAction *rulersTrackMouseAction; KisAction *zoomTo100pct; KisAction *zoomIn; KisAction *zoomOut; KisAction *softProof; KisAction *gamutCheck; KisAction *toggleFgBg; KisAction *resetFgBg; KisSelectionManager selectionManager; KisGuidesManager guidesManager; KisStatusBar statusBar; QPointer persistentImageProgressUpdater; QScopedPointer persistentUnthreadedProgressUpdaterRouter; QPointer persistentUnthreadedProgressUpdater; KisControlFrame controlFrame; KisNodeManager nodeManager; KisImageManager imageManager; KisGridManager gridManager; KisCanvasControlsManager canvasControlsManager; KisDecorationsManager paintingAssistantsManager; BlockingUserInputEventFilter blockingEventFilter; KisActionManager actionManager; QMainWindow* mainWindow; QPointer savedFloatingMessage; bool showFloatingMessage; QPointer currentImageView; KisCanvasResourceProvider canvasResourceProvider; KoCanvasResourceProvider canvasResourceManager; KisSignalCompressor guiUpdateCompressor; KActionCollection *actionCollection; KisMirrorManager mirrorManager; KisInputManager inputManager; KisSignalAutoConnectionsStore viewConnections; KSelectAction *actionAuthor; // Select action for author profile. KisAction *showPixelGrid; QByteArray canvasState; #if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) QFlags windowFlags; #endif bool blockUntilOperationsFinishedImpl(KisImageSP image, bool force); }; KisViewManager::KisViewManager(QWidget *parent, KActionCollection *_actionCollection) : d(new KisViewManagerPrivate(this, _actionCollection, parent)) { d->actionCollection = _actionCollection; d->mainWindow = dynamic_cast(parent); d->canvasResourceProvider.setResourceManager(&d->canvasResourceManager); connect(&d->guiUpdateCompressor, SIGNAL(timeout()), this, SLOT(guiUpdateTimeout())); createActions(); setupManagers(); // These initialization functions must wait until KisViewManager ctor is complete. d->statusBar.setup(); d->persistentImageProgressUpdater = d->statusBar.progressUpdater()->startSubtask(1, "", true); // reset state to "completed" d->persistentImageProgressUpdater->setRange(0,100); d->persistentImageProgressUpdater->setValue(100); d->persistentUnthreadedProgressUpdater = d->statusBar.progressUpdater()->startSubtask(1, "", true); // reset state to "completed" d->persistentUnthreadedProgressUpdater->setRange(0,100); d->persistentUnthreadedProgressUpdater->setValue(100); d->persistentUnthreadedProgressUpdaterRouter.reset( new KoProgressUpdater(d->persistentUnthreadedProgressUpdater, KoProgressUpdater::Unthreaded)); d->persistentUnthreadedProgressUpdaterRouter->setAutoNestNames(true); d->controlFrame.setup(parent); //Check to draw scrollbars after "Canvas only mode" toggle is created. this->showHideScrollbars(); QScopedPointer dummy(new KoDummyCanvasController(actionCollection())); KoToolManager::instance()->registerToolActions(actionCollection(), dummy.data()); QTimer::singleShot(0, this, SLOT(initializeStatusBarVisibility())); connect(KoToolManager::instance(), SIGNAL(inputDeviceChanged(KoInputDevice)), d->controlFrame.paintopBox(), SLOT(slotInputDeviceChanged(KoInputDevice))); connect(KoToolManager::instance(), SIGNAL(changedTool(KoCanvasController*,int)), d->controlFrame.paintopBox(), SLOT(slotToolChanged(KoCanvasController*,int))); connect(&d->nodeManager, SIGNAL(sigNodeActivated(KisNodeSP)), canvasResourceProvider(), SLOT(slotNodeActivated(KisNodeSP))); connect(KisPart::instance(), SIGNAL(sigViewAdded(KisView*)), SLOT(slotViewAdded(KisView*))); connect(KisPart::instance(), SIGNAL(sigViewRemoved(KisView*)), SLOT(slotViewRemoved(KisView*))); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotUpdateAuthorProfileActions())); connect(KisConfigNotifier::instance(), SIGNAL(pixelGridModeChanged()), SLOT(slotUpdatePixelGridAction())); KisInputProfileManager::instance()->loadProfiles(); KisConfig cfg(true); d->showFloatingMessage = cfg.showCanvasMessages(); const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KoColor foreground(Qt::black, cs); d->canvasResourceProvider.setFGColor(cfg.readKoColor("LastForeGroundColor",foreground)); KoColor background(Qt::white, cs); d->canvasResourceProvider.setBGColor(cfg.readKoColor("LastBackGroundColor",background)); } KisViewManager::~KisViewManager() { KisConfig cfg(false); if (canvasResourceProvider() && canvasResourceProvider()->currentPreset()) { cfg.writeKoColor("LastForeGroundColor",canvasResourceProvider()->fgColor()); cfg.writeKoColor("LastBackGroundColor",canvasResourceProvider()->bgColor()); } cfg.writeEntry("baseLength", KisResourceItemChooserSync::instance()->baseLength()); cfg.writeEntry("CanvasOnlyActive", false); // We never restart in CavnasOnlyMode delete d; } void KisViewManager::initializeResourceManager(KoCanvasResourceProvider *resourceManager) { resourceManager->addDerivedResourceConverter(toQShared(new KisCompositeOpResourceConverter)); resourceManager->addDerivedResourceConverter(toQShared(new KisEffectiveCompositeOpResourceConverter)); resourceManager->addDerivedResourceConverter(toQShared(new KisOpacityResourceConverter)); resourceManager->addDerivedResourceConverter(toQShared(new KisFlowResourceConverter)); resourceManager->addDerivedResourceConverter(toQShared(new KisSizeResourceConverter)); resourceManager->addDerivedResourceConverter(toQShared(new KisLodAvailabilityResourceConverter)); resourceManager->addDerivedResourceConverter(toQShared(new KisLodSizeThresholdResourceConverter)); resourceManager->addDerivedResourceConverter(toQShared(new KisLodSizeThresholdSupportedResourceConverter)); resourceManager->addDerivedResourceConverter(toQShared(new KisEraserModeResourceConverter)); resourceManager->addResourceUpdateMediator(toQShared(new KisPresetUpdateMediator)); } KActionCollection *KisViewManager::actionCollection() const { return d->actionCollection; } void KisViewManager::slotViewAdded(KisView *view) { // WARNING: this slot is called even when a view from another main windows is added! // Don't expect \p view be a child of this view manager! Q_UNUSED(view); if (viewCount() == 0) { d->statusBar.showAllStatusBarItems(); } } void KisViewManager::slotViewRemoved(KisView *view) { // WARNING: this slot is called even when a view from another main windows is removed! // Don't expect \p view be a child of this view manager! Q_UNUSED(view); if (viewCount() == 0) { d->statusBar.hideAllStatusBarItems(); } KisConfig cfg(false); if (canvasResourceProvider() && canvasResourceProvider()->currentPreset()) { cfg.writeEntry("LastPreset", canvasResourceProvider()->currentPreset()->name()); } } void KisViewManager::setCurrentView(KisView *view) { bool first = true; if (d->currentImageView) { d->currentImageView->notifyCurrentStateChanged(false); d->currentImageView->canvasBase()->setCursor(QCursor(Qt::ArrowCursor)); first = false; KisDocument* doc = d->currentImageView->document(); if (doc) { doc->image()->compositeProgressProxy()->removeProxy(d->persistentImageProgressUpdater); doc->disconnect(this); } d->currentImageView->canvasController()->proxyObject->disconnect(&d->statusBar); d->viewConnections.clear(); } QPointer imageView = qobject_cast(view); d->currentImageView = imageView; if (imageView) { d->softProof->setChecked(imageView->softProofing()); d->gamutCheck->setChecked(imageView->gamutCheck()); // Wait for the async image to have loaded KisDocument* doc = imageView->document(); if (KisConfig(true).readEntry("EnablePositionLabel", false)) { connect(d->currentImageView->canvasController()->proxyObject, SIGNAL(documentMousePositionChanged(QPointF)), &d->statusBar, SLOT(documentMousePositionChanged(QPointF))); } // Restore the last used brush preset, color and background color. if (first) { KisPaintOpPresetResourceServer * rserver = KisResourceServerProvider::instance()->paintOpPresetServer(); KisResourceModel *resourceModel = rserver->resourceModel(); QString defaultPresetName = "basic_tip_default"; for (int i = 0; i < resourceModel->rowCount(); i++) { QModelIndex idx = resourceModel->index(i, 0); QString resourceName = idx.data(Qt::UserRole + KisResourceModel::Name).toString().toLower(); QString fileName = idx.data(Qt::UserRole + KisResourceModel::Filename).toString().toLower(); if (resourceName.contains("basic_tip_default")) { defaultPresetName = resourceName; } else if (resourceName.contains("default") || fileName.contains("default")) { defaultPresetName = resourceName; } } KisConfig cfg(true); QString lastPreset = cfg.readEntry("LastPreset", defaultPresetName); KisPaintOpPresetSP preset = rserver->resourceByName(lastPreset); if (!preset) { preset = rserver->resourceByName(defaultPresetName); } if (!preset && rserver->resourceCount() > 0) { preset = rserver->firstResource(); } if (preset) { paintOpBox()->restoreResource(preset); canvasResourceProvider()->setCurrentCompositeOp(preset->settings()->paintOpCompositeOp()); } } KisCanvasController *canvasController = dynamic_cast(d->currentImageView->canvasController()); d->viewConnections.addUniqueConnection(&d->nodeManager, SIGNAL(sigNodeActivated(KisNodeSP)), doc->image(), SLOT(requestStrokeEndActiveNode())); d->viewConnections.addUniqueConnection(d->rotateCanvasRight, SIGNAL(triggered()), canvasController, SLOT(rotateCanvasRight15())); d->viewConnections.addUniqueConnection(d->rotateCanvasLeft, SIGNAL(triggered()),canvasController, SLOT(rotateCanvasLeft15())); d->viewConnections.addUniqueConnection(d->resetCanvasRotation, SIGNAL(triggered()),canvasController, SLOT(resetCanvasRotation())); d->viewConnections.addUniqueConnection(d->wrapAroundAction, SIGNAL(toggled(bool)), canvasController, SLOT(slotToggleWrapAroundMode(bool))); d->wrapAroundAction->setChecked(canvasController->wrapAroundMode()); d->viewConnections.addUniqueConnection(d->levelOfDetailAction, SIGNAL(toggled(bool)), canvasController, SLOT(slotToggleLevelOfDetailMode(bool))); d->levelOfDetailAction->setChecked(canvasController->levelOfDetailMode()); d->viewConnections.addUniqueConnection(d->currentImageView->image(), SIGNAL(sigColorSpaceChanged(const KoColorSpace*)), d->controlFrame.paintopBox(), SLOT(slotColorSpaceChanged(const KoColorSpace*))); d->viewConnections.addUniqueConnection(d->showRulersAction, SIGNAL(toggled(bool)), imageView->zoomManager(), SLOT(setShowRulers(bool))); d->viewConnections.addUniqueConnection(d->rulersTrackMouseAction, SIGNAL(toggled(bool)), imageView->zoomManager(), SLOT(setRulersTrackMouse(bool))); d->viewConnections.addUniqueConnection(d->zoomTo100pct, SIGNAL(triggered()), imageView->zoomManager(), SLOT(zoomTo100())); d->viewConnections.addUniqueConnection(d->zoomIn, SIGNAL(triggered()), imageView->zoomController()->zoomAction(), SLOT(zoomIn())); d->viewConnections.addUniqueConnection(d->zoomOut, SIGNAL(triggered()), imageView->zoomController()->zoomAction(), SLOT(zoomOut())); d->viewConnections.addUniqueConnection(d->softProof, SIGNAL(toggled(bool)), view, SLOT(slotSoftProofing(bool)) ); d->viewConnections.addUniqueConnection(d->gamutCheck, SIGNAL(toggled(bool)), view, SLOT(slotGamutCheck(bool)) ); // set up progrress reporting doc->image()->compositeProgressProxy()->addProxy(d->persistentImageProgressUpdater); d->viewConnections.addUniqueConnection(&d->statusBar, SIGNAL(sigCancellationRequested()), doc->image(), SLOT(requestStrokeCancellation())); d->viewConnections.addUniqueConnection(d->showPixelGrid, SIGNAL(toggled(bool)), canvasController, SLOT(slotTogglePixelGrid(bool))); imageView->zoomManager()->setShowRulers(d->showRulersAction->isChecked()); imageView->zoomManager()->setRulersTrackMouse(d->rulersTrackMouseAction->isChecked()); showHideScrollbars(); } d->filterManager.setView(imageView); d->selectionManager.setView(imageView); d->guidesManager.setView(imageView); d->nodeManager.setView(imageView); d->imageManager.setView(imageView); d->canvasControlsManager.setView(imageView); d->actionManager.setView(imageView); d->gridManager.setView(imageView); d->statusBar.setView(imageView); d->paintingAssistantsManager.setView(imageView); d->mirrorManager.setView(imageView); if (d->currentImageView) { d->currentImageView->notifyCurrentStateChanged(true); d->currentImageView->canvasController()->activate(); d->currentImageView->canvasController()->setFocus(); d->viewConnections.addUniqueConnection( image(), SIGNAL(sigSizeChanged(QPointF,QPointF)), canvasResourceProvider(), SLOT(slotImageSizeChanged())); d->viewConnections.addUniqueConnection( image(), SIGNAL(sigResolutionChanged(double,double)), canvasResourceProvider(), SLOT(slotOnScreenResolutionChanged())); d->viewConnections.addUniqueConnection( image(), SIGNAL(sigNodeChanged(KisNodeSP)), this, SLOT(updateGUI())); d->viewConnections.addUniqueConnection( d->currentImageView->zoomManager()->zoomController(), SIGNAL(zoomChanged(KoZoomMode::Mode,qreal)), canvasResourceProvider(), SLOT(slotOnScreenResolutionChanged())); } d->actionManager.updateGUI(); canvasResourceProvider()->slotImageSizeChanged(); canvasResourceProvider()->slotOnScreenResolutionChanged(); Q_EMIT viewChanged(); } KoZoomController *KisViewManager::zoomController() const { if (d->currentImageView) { return d->currentImageView->zoomController(); } return 0; } KisImageWSP KisViewManager::image() const { if (document()) { return document()->image(); } return 0; } KisCanvasResourceProvider * KisViewManager::canvasResourceProvider() { return &d->canvasResourceProvider; } KisCanvas2 * KisViewManager::canvasBase() const { if (d && d->currentImageView) { return d->currentImageView->canvasBase(); } return 0; } QWidget* KisViewManager::canvas() const { if (d && d->currentImageView && d->currentImageView->canvasBase()->canvasWidget()) { return d->currentImageView->canvasBase()->canvasWidget(); } return 0; } KisStatusBar * KisViewManager::statusBar() const { return &d->statusBar; } KisPaintopBox* KisViewManager::paintOpBox() const { return d->controlFrame.paintopBox(); } QPointer KisViewManager::createUnthreadedUpdater(const QString &name) { return d->persistentUnthreadedProgressUpdaterRouter->startSubtask(1, name, false); } QPointer KisViewManager::createThreadedUpdater(const QString &name) { return d->statusBar.progressUpdater()->startSubtask(1, name, false); } KisSelectionManager * KisViewManager::selectionManager() { return &d->selectionManager; } KisNodeSP KisViewManager::activeNode() { return d->nodeManager.activeNode(); } KisLayerSP KisViewManager::activeLayer() { return d->nodeManager.activeLayer(); } KisPaintDeviceSP KisViewManager::activeDevice() { return d->nodeManager.activePaintDevice(); } KisZoomManager * KisViewManager::zoomManager() { if (d->currentImageView) { return d->currentImageView->zoomManager(); } return 0; } KisFilterManager * KisViewManager::filterManager() { return &d->filterManager; } KisImageManager * KisViewManager::imageManager() { return &d->imageManager; } KisInputManager* KisViewManager::inputManager() const { return &d->inputManager; } KisSelectionSP KisViewManager::selection() { if (d->currentImageView) { return d->currentImageView->selection(); } return 0; } bool KisViewManager::selectionEditable() { KisLayerSP layer = activeLayer(); if (layer) { KisSelectionMaskSP mask = layer->selectionMask(); if (mask) { return mask->isEditable(); } } // global selection is always editable return true; } KisUndoAdapter * KisViewManager::undoAdapter() { if (!document()) return 0; KisImageWSP image = document()->image(); Q_ASSERT(image); return image->undoAdapter(); } void KisViewManager::createActions() { KisConfig cfg(true); d->saveIncremental = actionManager()->createAction("save_incremental_version"); connect(d->saveIncremental, SIGNAL(triggered()), this, SLOT(slotSaveIncremental())); d->saveIncrementalBackup = actionManager()->createAction("save_incremental_backup"); connect(d->saveIncrementalBackup, SIGNAL(triggered()), this, SLOT(slotSaveIncrementalBackup())); connect(mainWindow(), SIGNAL(documentSaved()), this, SLOT(slotDocumentSaved())); d->saveIncremental->setEnabled(false); d->saveIncrementalBackup->setEnabled(false); KisAction *tabletDebugger = actionManager()->createAction("tablet_debugger"); connect(tabletDebugger, SIGNAL(triggered()), this, SLOT(toggleTabletLogger())); d->createTemplate = actionManager()->createAction("create_template"); connect(d->createTemplate, SIGNAL(triggered()), this, SLOT(slotCreateTemplate())); d->createCopy = actionManager()->createAction("create_copy"); connect(d->createCopy, SIGNAL(triggered()), this, SLOT(slotCreateCopy())); d->openResourcesDirectory = actionManager()->createAction("open_resources_directory"); connect(d->openResourcesDirectory, SIGNAL(triggered()), SLOT(openResourcesDirectory())); d->rotateCanvasRight = actionManager()->createAction("rotate_canvas_right"); d->rotateCanvasLeft = actionManager()->createAction("rotate_canvas_left"); d->resetCanvasRotation = actionManager()->createAction("reset_canvas_rotation"); d->wrapAroundAction = actionManager()->createAction("wrap_around_mode"); d->levelOfDetailAction = actionManager()->createAction("level_of_detail_mode"); d->softProof = actionManager()->createAction("softProof"); d->gamutCheck = actionManager()->createAction("gamutCheck"); KisAction *tAction = actionManager()->createAction("showStatusBar"); tAction->setChecked(cfg.showStatusBar()); connect(tAction, SIGNAL(toggled(bool)), this, SLOT(showStatusBar(bool))); tAction = actionManager()->createAction("view_show_canvas_only"); tAction->setChecked(false); connect(tAction, SIGNAL(toggled(bool)), this, SLOT(switchCanvasOnly(bool))); //Workaround, by default has the same shortcut as mirrorCanvas KisAction *a = dynamic_cast(actionCollection()->action("format_italic")); if (a) { a->setDefaultShortcut(QKeySequence()); } actionManager()->createAction("ruler_pixel_multiple2"); d->showRulersAction = actionManager()->createAction("view_ruler"); d->showRulersAction->setChecked(cfg.showRulers()); connect(d->showRulersAction, SIGNAL(toggled(bool)), SLOT(slotSaveShowRulersState(bool))); d->rulersTrackMouseAction = actionManager()->createAction("rulers_track_mouse"); d->rulersTrackMouseAction->setChecked(cfg.rulersTrackMouse()); connect(d->rulersTrackMouseAction, SIGNAL(toggled(bool)), SLOT(slotSaveRulersTrackMouseState(bool))); d->zoomTo100pct = actionManager()->createAction("zoom_to_100pct"); d->zoomIn = actionManager()->createStandardAction(KStandardAction::ZoomIn, 0, ""); d->zoomOut = actionManager()->createStandardAction(KStandardAction::ZoomOut, 0, ""); d->actionAuthor = new KSelectAction(KisIconUtils::loadIcon("im-user"), i18n("Active Author Profile"), this); connect(d->actionAuthor, SIGNAL(triggered(QString)), this, SLOT(changeAuthorProfile(QString))); actionCollection()->addAction("settings_active_author", d->actionAuthor); slotUpdateAuthorProfileActions(); d->showPixelGrid = actionManager()->createAction("view_pixel_grid"); slotUpdatePixelGridAction(); d->toggleFgBg = actionManager()->createAction("toggle_fg_bg"); connect(d->toggleFgBg, SIGNAL(triggered(bool)), this, SLOT(slotToggleFgBg())); d->resetFgBg = actionManager()->createAction("reset_fg_bg"); connect(d->resetFgBg, SIGNAL(triggered(bool)), this, SLOT(slotResetFgBg())); } void KisViewManager::setupManagers() { // Create the managers for filters, selections, layers etc. // XXX: When the currentlayer changes, call updateGUI on all // managers d->filterManager.setup(actionCollection(), actionManager()); d->selectionManager.setup(actionManager()); d->guidesManager.setup(actionManager()); d->nodeManager.setup(actionCollection(), actionManager()); d->imageManager.setup(actionManager()); d->gridManager.setup(actionManager()); d->paintingAssistantsManager.setup(actionManager()); d->canvasControlsManager.setup(actionManager()); d->mirrorManager.setup(actionCollection()); } void KisViewManager::updateGUI() { d->guiUpdateCompressor.start(); } KisNodeManager * KisViewManager::nodeManager() const { return &d->nodeManager; } KisActionManager* KisViewManager::actionManager() const { return &d->actionManager; } KisGridManager * KisViewManager::gridManager() const { return &d->gridManager; } KisGuidesManager * KisViewManager::guidesManager() const { return &d->guidesManager; } KisDocument *KisViewManager::document() const { if (d->currentImageView && d->currentImageView->document()) { return d->currentImageView->document(); } return 0; } int KisViewManager::viewCount() const { KisMainWindow *mw = qobject_cast(d->mainWindow); if (mw) { return mw->viewCount(); } return 0; } bool KisViewManager::KisViewManagerPrivate::blockUntilOperationsFinishedImpl(KisImageSP image, bool force) { const int busyWaitDelay = 1000; KisDelayedSaveDialog dialog(image, !force ? KisDelayedSaveDialog::GeneralDialog : KisDelayedSaveDialog::ForcedDialog, busyWaitDelay, mainWindow); dialog.blockIfImageIsBusy(); return dialog.result() == QDialog::Accepted; } bool KisViewManager::blockUntilOperationsFinished(KisImageSP image) { return d->blockUntilOperationsFinishedImpl(image, false); } void KisViewManager::blockUntilOperationsFinishedForced(KisImageSP image) { d->blockUntilOperationsFinishedImpl(image, true); } void KisViewManager::slotCreateTemplate() { if (!document()) return; KisTemplateCreateDia::createTemplate( QStringLiteral("templates/"), ".kra", document(), mainWindow()); } void KisViewManager::slotCreateCopy() { KisDocument *srcDoc = document(); if (!srcDoc) return; if (!this->blockUntilOperationsFinished(srcDoc->image())) return; KisDocument *doc = 0; { KisImageBarrierLocker l(srcDoc->image()); doc = srcDoc->clone(); } KIS_SAFE_ASSERT_RECOVER_RETURN(doc); QString name = srcDoc->documentInfo()->aboutInfo("name"); if (name.isEmpty()) { name = document()->url().toLocalFile(); } name = i18n("%1 (Copy)", name); doc->documentInfo()->setAboutInfo("title", name); KisPart::instance()->addDocument(doc); KisMainWindow *mw = qobject_cast(d->mainWindow); mw->addViewAndNotifyLoadingCompleted(doc); } QMainWindow* KisViewManager::qtMainWindow() const { if (d->mainWindow) return d->mainWindow; //Fallback for when we have not yet set the main window. QMainWindow* w = qobject_cast(qApp->activeWindow()); if(w) return w; return mainWindow(); } void KisViewManager::setQtMainWindow(QMainWindow* newMainWindow) { d->mainWindow = newMainWindow; } void KisViewManager::slotDocumentSaved() { d->saveIncremental->setEnabled(true); d->saveIncrementalBackup->setEnabled(true); } void KisViewManager::slotSaveIncremental() { if (!document()) return; if (document()->url().isEmpty()) { KisMainWindow *mw = qobject_cast(d->mainWindow); mw->saveDocument(document(), true, false); return; } bool foundVersion; bool fileAlreadyExists; bool isBackup; QString version = "000"; QString newVersion; QString letter; QString path = QFileInfo(document()->localFilePath()).canonicalPath(); QString fileName = QFileInfo(document()->localFilePath()).fileName(); // Find current version filenames // v v Regexp to find incremental versions in the filename, taking our backup scheme into account as well // Considering our incremental version and backup scheme, format is filename_001~001.ext QRegExp regex("_\\d{1,4}[.]|_\\d{1,4}[a-z][.]|_\\d{1,4}[~]|_\\d{1,4}[a-z][~]"); regex.indexIn(fileName); // Perform the search QStringList matches = regex.capturedTexts(); foundVersion = matches.at(0).isEmpty() ? false : true; // Ensure compatibility with Save Incremental Backup // If this regex is not kept separate, the entire algorithm needs modification; // It's simpler to just add this. QRegExp regexAux("_\\d{1,4}[~]|_\\d{1,4}[a-z][~]"); regexAux.indexIn(fileName); // Perform the search QStringList matchesAux = regexAux.capturedTexts(); isBackup = matchesAux.at(0).isEmpty() ? false : true; // If the filename has a version, prepare it for incrementation if (foundVersion) { version = matches.at(matches.count() - 1); // Look at the last index, we don't care about other matches if (version.contains(QRegExp("[a-z]"))) { version.chop(1); // Trim "." letter = version.right(1); // Save letter version.chop(1); // Trim letter } else { version.chop(1); // Trim "." } version.remove(0, 1); // Trim "_" } else { // TODO: this will not work with files extensions like jp2 // ...else, simply add a version to it so the next loop works QRegExp regex2("[.][a-z]{2,4}$"); // Heuristic to find file extension regex2.indexIn(fileName); QStringList matches2 = regex2.capturedTexts(); QString extensionPlusVersion = matches2.at(0); extensionPlusVersion.prepend(version); extensionPlusVersion.prepend("_"); fileName.replace(regex2, extensionPlusVersion); } // Prepare the base for new version filename int intVersion = version.toInt(0); ++intVersion; QString baseNewVersion = QString::number(intVersion); while (baseNewVersion.length() < version.length()) { baseNewVersion.prepend("0"); } // Check if the file exists under the new name and search until options are exhausted (test appending a to z) do { newVersion = baseNewVersion; newVersion.prepend("_"); if (!letter.isNull()) newVersion.append(letter); if (isBackup) { newVersion.append("~"); } else { newVersion.append("."); } fileName.replace(regex, newVersion); - fileAlreadyExists = QFile(fileName).exists(); + bool fileAlreadyExists = QFileInfo(path + '/' + fileName).exists(); if (fileAlreadyExists) { if (!letter.isNull()) { char letterCh = letter.at(0).toLatin1(); ++letterCh; letter = QString(QChar(letterCh)); } else { letter = 'a'; } } } while (fileAlreadyExists && letter != "{"); // x, y, z, {... if (letter == "{") { QMessageBox::critical(mainWindow(), i18nc("@title:window", "Couldn't save incremental version"), i18n("Alternative names exhausted, try manually saving with a higher number")); return; } QUrl newUrl = QUrl::fromUserInput(path + '/' + fileName); document()->setFileBatchMode(true); document()->saveAs(newUrl, document()->mimeType(), true); document()->setFileBatchMode(false); KisPart::instance()->addRecentURLToAllMainWindows(newUrl, document()->url()); if (mainWindow()) { mainWindow()->updateCaption(); } } void KisViewManager::slotSaveIncrementalBackup() { if (!document()) return; if (document()->url().isEmpty()) { KisMainWindow *mw = qobject_cast(d->mainWindow); mw->saveDocument(document(), true, false); return; } bool workingOnBackup; bool fileAlreadyExists; QString version = "000"; QString newVersion; QString letter; QString path = QFileInfo(document()->localFilePath()).canonicalPath(); QString fileName = QFileInfo(document()->localFilePath()).fileName(); // First, discover if working on a backup file, or a normal file QRegExp regex("~\\d{1,4}[.]|~\\d{1,4}[a-z][.]"); regex.indexIn(fileName); // Perform the search QStringList matches = regex.capturedTexts(); workingOnBackup = matches.at(0).isEmpty() ? false : true; if (workingOnBackup) { // Try to save incremental version (of backup), use letter for alt versions version = matches.at(matches.count() - 1); // Look at the last index, we don't care about other matches if (version.contains(QRegExp("[a-z]"))) { version.chop(1); // Trim "." letter = version.right(1); // Save letter version.chop(1); // Trim letter } else { version.chop(1); // Trim "." } version.remove(0, 1); // Trim "~" // Prepare the base for new version filename int intVersion = version.toInt(0); ++intVersion; QString baseNewVersion = QString::number(intVersion); QString backupFileName = document()->localFilePath(); while (baseNewVersion.length() < version.length()) { baseNewVersion.prepend("0"); } // Check if the file exists under the new name and search until options are exhausted (test appending a to z) do { newVersion = baseNewVersion; newVersion.prepend("~"); if (!letter.isNull()) newVersion.append(letter); newVersion.append("."); backupFileName.replace(regex, newVersion); fileAlreadyExists = QFile(backupFileName).exists(); if (fileAlreadyExists) { if (!letter.isNull()) { char letterCh = letter.at(0).toLatin1(); ++letterCh; letter = QString(QChar(letterCh)); } else { letter = 'a'; } } } while (fileAlreadyExists && letter != "{"); // x, y, z, {... if (letter == "{") { QMessageBox::critical(mainWindow(), i18nc("@title:window", "Couldn't save incremental backup"), i18n("Alternative names exhausted, try manually saving with a higher number")); return; } QFile::copy(path + '/' + fileName, path + '/' + backupFileName); document()->saveAs(QUrl::fromUserInput(path + '/' + fileName), document()->mimeType(), true); if (mainWindow()) mainWindow()->updateCaption(); } else { // if NOT working on a backup... // Navigate directory searching for latest backup version, ignore letters const quint8 HARDCODED_DIGIT_COUNT = 3; QString baseNewVersion = "000"; QString backupFileName = QFileInfo(document()->localFilePath()).fileName(); QRegExp regex2("[.][a-z]{2,4}$"); // Heuristic to find file extension regex2.indexIn(backupFileName); QStringList matches2 = regex2.capturedTexts(); QString extensionPlusVersion = matches2.at(0); extensionPlusVersion.prepend(baseNewVersion); extensionPlusVersion.prepend("~"); backupFileName.replace(regex2, extensionPlusVersion); // Save version with 1 number higher than the highest version found ignoring letters do { newVersion = baseNewVersion; newVersion.prepend("~"); newVersion.append("."); backupFileName.replace(regex, newVersion); fileAlreadyExists = QFile(backupFileName).exists(); if (fileAlreadyExists) { // Prepare the base for new version filename, increment by 1 int intVersion = baseNewVersion.toInt(0); ++intVersion; baseNewVersion = QString::number(intVersion); while (baseNewVersion.length() < HARDCODED_DIGIT_COUNT) { baseNewVersion.prepend("0"); } } } while (fileAlreadyExists); // Save both as backup and on current file for interapplication workflow document()->setFileBatchMode(true); QFile::copy(path + '/' + fileName, path + '/' + backupFileName); document()->saveAs(QUrl::fromUserInput(path + '/' + fileName), document()->mimeType(), true); document()->setFileBatchMode(false); if (mainWindow()) mainWindow()->updateCaption(); } } void KisViewManager::disableControls() { // prevents possible crashes, if somebody changes the paintop during dragging by using the mousewheel // this is for Bug 250944 // the solution blocks all wheel, mouse and key event, while dragging with the freehand tool // see KisToolFreehand::initPaint() and endPaint() d->controlFrame.paintopBox()->installEventFilter(&d->blockingEventFilter); Q_FOREACH (QObject* child, d->controlFrame.paintopBox()->children()) { child->installEventFilter(&d->blockingEventFilter); } } void KisViewManager::enableControls() { d->controlFrame.paintopBox()->removeEventFilter(&d->blockingEventFilter); Q_FOREACH (QObject* child, d->controlFrame.paintopBox()->children()) { child->removeEventFilter(&d->blockingEventFilter); } } void KisViewManager::showStatusBar(bool toggled) { KisMainWindow *mw = mainWindow(); if(mw && mw->statusBar()) { mw->statusBar()->setVisible(toggled); KisConfig cfg(false); cfg.setShowStatusBar(toggled); } } void KisViewManager::switchCanvasOnly(bool toggled) { KisConfig cfg(false); KisMainWindow *main = mainWindow(); if(!main) { dbgUI << "Unable to switch to canvas-only mode, main window not found"; return; } cfg.writeEntry("CanvasOnlyActive", toggled); if (toggled) { d->canvasState = qtMainWindow()->saveState(); #if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) d->windowFlags = main->windowState(); #endif } if (cfg.hideStatusbarFullscreen()) { if (main->statusBar()) { if (!toggled) { if (main->statusBar()->dynamicPropertyNames().contains("wasvisible")) { if (main->statusBar()->property("wasvisible").toBool()) { main->statusBar()->setVisible(true); } } } else { main->statusBar()->setProperty("wasvisible", main->statusBar()->isVisible()); main->statusBar()->setVisible(false); } } } if (cfg.hideDockersFullscreen()) { KisAction* action = qobject_cast(main->actionCollection()->action("view_toggledockers")); if (action) { action->setCheckable(true); if (toggled) { if (action->isChecked()) { cfg.setShowDockers(action->isChecked()); action->setChecked(false); } else { cfg.setShowDockers(false); } } else { action->setChecked(cfg.showDockers()); } } } // QT in windows does not return to maximized upon 4th tab in a row // https://bugreports.qt.io/browse/QTBUG-57882, https://bugreports.qt.io/browse/QTBUG-52555, https://codereview.qt-project.org/#/c/185016/ if (cfg.hideTitlebarFullscreen() && !cfg.fullscreenMode()) { if(toggled) { main->setWindowState( main->windowState() | Qt::WindowFullScreen); } else { main->setWindowState( main->windowState() & ~Qt::WindowFullScreen); #if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) // If window was maximized prior to fullscreen, restore that if (d->windowFlags & Qt::WindowMaximized) { main->setWindowState( main->windowState() | Qt::WindowMaximized); } #endif } } if (cfg.hideMenuFullscreen()) { if (!toggled) { if (main->menuBar()->dynamicPropertyNames().contains("wasvisible")) { if (main->menuBar()->property("wasvisible").toBool()) { main->menuBar()->setVisible(true); } } } else { main->menuBar()->setProperty("wasvisible", main->menuBar()->isVisible()); main->menuBar()->setVisible(false); } } if (cfg.hideToolbarFullscreen()) { QList toolBars = main->findChildren(); Q_FOREACH (QToolBar* toolbar, toolBars) { if (!toggled) { if (toolbar->dynamicPropertyNames().contains("wasvisible")) { if (toolbar->property("wasvisible").toBool()) { toolbar->setVisible(true); } } } else { toolbar->setProperty("wasvisible", toolbar->isVisible()); toolbar->setVisible(false); } } } showHideScrollbars(); if (toggled) { // show a fading heads-up display about the shortcut to go back showFloatingMessage(i18n("Going into Canvas-Only mode.\nPress %1 to go back.", actionCollection()->action("view_show_canvas_only")->shortcut().toString()), QIcon()); } else { main->restoreState(d->canvasState); } } void KisViewManager::toggleTabletLogger() { d->inputManager.toggleTabletLogger(); } void KisViewManager::openResourcesDirectory() { QString dir = KoResourcePaths::locateLocal("data", ""); QDesktopServices::openUrl(QUrl::fromLocalFile(dir)); } void KisViewManager::updateIcons() { if (mainWindow()) { QList dockers = mainWindow()->dockWidgets(); Q_FOREACH (QDockWidget* dock, dockers) { QObjectList objects; objects.append(dock); while (!objects.isEmpty()) { QObject* object = objects.takeFirst(); objects.append(object->children()); KisIconUtils::updateIconCommon(object); } } } } void KisViewManager::initializeStatusBarVisibility() { KisConfig cfg(true); d->mainWindow->statusBar()->setVisible(cfg.showStatusBar()); } void KisViewManager::guiUpdateTimeout() { d->nodeManager.updateGUI(); d->selectionManager.updateGUI(); d->filterManager.updateGUI(); if (zoomManager()) { zoomManager()->updateGuiAfterDocumentSize(); } d->gridManager.updateGUI(); d->actionManager.updateGUI(); } void KisViewManager::showFloatingMessage(const QString &message, const QIcon& icon, int timeout, KisFloatingMessage::Priority priority, int alignment) { if (!d->currentImageView) return; d->currentImageView->showFloatingMessage(message, icon, timeout, priority, alignment); emit floatingMessageRequested(message, icon.name()); } KisMainWindow *KisViewManager::mainWindow() const { return qobject_cast(d->mainWindow); } void KisViewManager::showHideScrollbars() { if (!d->currentImageView) return; if (!d->currentImageView->canvasController()) return; KisConfig cfg(true); bool toggled = actionCollection()->action("view_show_canvas_only")->isChecked(); if ( (toggled && cfg.hideScrollbarsFullscreen()) || (!toggled && cfg.hideScrollbars()) ) { d->currentImageView->canvasController()->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); d->currentImageView->canvasController()->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); } else { d->currentImageView->canvasController()->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); d->currentImageView->canvasController()->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); } } void KisViewManager::slotSaveShowRulersState(bool value) { KisConfig cfg(false); cfg.setShowRulers(value); } void KisViewManager::slotSaveRulersTrackMouseState(bool value) { KisConfig cfg(false); cfg.setRulersTrackMouse(value); } void KisViewManager::setShowFloatingMessage(bool show) { d->showFloatingMessage = show; } void KisViewManager::changeAuthorProfile(const QString &profileName) { KConfigGroup appAuthorGroup(KSharedConfig::openConfig(), "Author"); if (profileName.isEmpty() || profileName == i18nc("choice for author profile", "Anonymous")) { appAuthorGroup.writeEntry("active-profile", ""); } else { appAuthorGroup.writeEntry("active-profile", profileName); } appAuthorGroup.sync(); Q_FOREACH (KisDocument *doc, KisPart::instance()->documents()) { doc->documentInfo()->updateParameters(); } } void KisViewManager::slotUpdateAuthorProfileActions() { Q_ASSERT(d->actionAuthor); if (!d->actionAuthor) { return; } d->actionAuthor->clear(); d->actionAuthor->addAction(i18nc("choice for author profile", "Anonymous")); KConfigGroup authorGroup(KSharedConfig::openConfig(), "Author"); QStringList profiles = authorGroup.readEntry("profile-names", QStringList()); QString authorInfo = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/authorinfo/"; QStringList filters = QStringList() << "*.authorinfo"; QDir dir(authorInfo); Q_FOREACH(QString entry, dir.entryList(filters)) { int ln = QString(".authorinfo").size(); entry.chop(ln); if (!profiles.contains(entry)) { profiles.append(entry); } } Q_FOREACH (const QString &profile , profiles) { d->actionAuthor->addAction(profile); } KConfigGroup appAuthorGroup(KSharedConfig::openConfig(), "Author"); QString profileName = appAuthorGroup.readEntry("active-profile", ""); if (profileName == "anonymous" || profileName.isEmpty()) { d->actionAuthor->setCurrentItem(0); } else if (profiles.contains(profileName)) { d->actionAuthor->setCurrentAction(profileName); } } void KisViewManager::slotUpdatePixelGridAction() { KIS_SAFE_ASSERT_RECOVER_RETURN(d->showPixelGrid); KisSignalsBlocker b(d->showPixelGrid); KisConfig cfg(true); d->showPixelGrid->setChecked(cfg.pixelGridEnabled() && cfg.useOpenGL()); } void KisViewManager::slotActivateTransformTool() { if(KoToolManager::instance()->activeToolId() == "KisToolTransform") { KoToolBase* tool = KoToolManager::instance()->toolById(canvasBase(), "KisToolTransform"); QSet dummy; // Start a new stroke tool->deactivate(); tool->activate(KoToolBase::DefaultActivation, dummy); } KoToolManager::instance()->switchToolRequested("KisToolTransform"); } void KisViewManager::slotToggleFgBg() { KoColor newFg = d->canvasResourceManager.backgroundColor(); KoColor newBg = d->canvasResourceManager.foregroundColor(); /** * NOTE: Some of color selectors do not differentiate foreground * and background colors, so if one wants them to end up * being set up to foreground color, it should be set the * last. */ d->canvasResourceManager.setBackgroundColor(newBg); d->canvasResourceManager.setForegroundColor(newFg); } void KisViewManager::slotResetFgBg() { // see a comment in slotToggleFgBg() d->canvasResourceManager.setBackgroundColor(KoColor(Qt::white, KoColorSpaceRegistry::instance()->rgb8())); d->canvasResourceManager.setForegroundColor(KoColor(Qt::black, KoColorSpaceRegistry::instance()->rgb8())); } void KisViewManager::slotResetRotation() { KisCanvasController *canvasController = d->currentImageView->canvasController(); canvasController->resetCanvasRotation(); } void KisViewManager::slotToggleFullscreen() { KisConfig cfg(false); KisMainWindow *main = mainWindow(); main->viewFullscreen(!main->isFullScreen()); cfg.fullscreenMode(main->isFullScreen()); } diff --git a/libs/ui/actions/kis_selection_action_factories.cpp b/libs/ui/actions/kis_selection_action_factories.cpp index 7d84a53dea..32ebdd0ee1 100644 --- a/libs/ui/actions/kis_selection_action_factories.cpp +++ b/libs/ui/actions/kis_selection_action_factories.cpp @@ -1,622 +1,624 @@ /* * Copyright (c) 2012 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_selection_action_factories.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #include "KisViewManager.h" #include "kis_canvas_resource_provider.h" #include "kis_clipboard.h" #include "kis_pixel_selection.h" #include "kis_paint_layer.h" #include "kis_image.h" #include "kis_image_barrier_locker.h" #include "kis_fill_painter.h" #include "kis_transaction.h" #include "kis_iterator_ng.h" #include "kis_processing_applicator.h" #include "kis_group_layer.h" #include "commands/kis_selection_commands.h" #include "commands/kis_image_layer_add_command.h" #include "kis_tool_proxy.h" #include "kis_canvas2.h" #include "kis_canvas_controller.h" #include "kis_selection_manager.h" #include "commands_new/kis_transaction_based_command.h" #include "kis_selection_filters.h" #include "kis_shape_selection.h" #include "kis_shape_layer.h" #include #include "kis_image_animation_interface.h" #include "kis_time_range.h" #include "kis_keyframe_channel.h" #include #include #include "kis_figure_painting_tool_helper.h" #include "kis_update_outline_job.h" namespace ActionHelper { void copyFromDevice(KisViewManager *view, KisPaintDeviceSP device, bool makeSharpClip = false, const KisTimeRange &range = KisTimeRange()) { KisImageWSP image = view->image(); if (!image) return; KisSelectionSP selection = view->selection(); QRect rc = (selection) ? selection->selectedExactRect() : image->bounds(); KisPaintDeviceSP clip = new KisPaintDevice(device->colorSpace()); Q_CHECK_PTR(clip); const KoColorSpace *cs = clip->colorSpace(); // TODO if the source is linked... copy from all linked layers?!? // Copy image data KisPainter::copyAreaOptimized(QPoint(), device, clip, rc); if (selection) { // Apply selection mask. KisPaintDeviceSP selectionProjection = selection->projection(); KisHLineIteratorSP layerIt = clip->createHLineIteratorNG(0, 0, rc.width()); KisHLineConstIteratorSP selectionIt = selectionProjection->createHLineIteratorNG(rc.x(), rc.y(), rc.width()); const KoColorSpace *selCs = selection->projection()->colorSpace(); for (qint32 y = 0; y < rc.height(); y++) { for (qint32 x = 0; x < rc.width(); x++) { /** * Sharp method is an exact reverse of COMPOSITE_OVER * so if you cover the cut/copied piece over its source * you get an exactly the same image without any seams */ if (makeSharpClip) { qreal dstAlpha = cs->opacityF(layerIt->rawData()); qreal sel = selCs->opacityF(selectionIt->oldRawData()); qreal newAlpha = sel * dstAlpha / (1.0 - dstAlpha + sel * dstAlpha); float mask = newAlpha / dstAlpha; cs->applyAlphaNormedFloatMask(layerIt->rawData(), &mask, 1); } else { cs->applyAlphaU8Mask(layerIt->rawData(), selectionIt->oldRawData(), 1); } layerIt->nextPixel(); selectionIt->nextPixel(); } layerIt->nextRow(); selectionIt->nextRow(); } } KisClipboard::instance()->setClip(clip, rc.topLeft(), range); } } void KisSelectAllActionFactory::run(KisViewManager *view) { KisImageWSP image = view->image(); if (!image) return; KisProcessingApplicator *ap = beginAction(view, kundo2_i18n("Select All")); if (!image->globalSelection()) { ap->applyCommand(new KisSetEmptyGlobalSelectionCommand(image), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); } struct SelectAll : public KisTransactionBasedCommand { SelectAll(KisImageSP image) : m_image(image) {} KisImageSP m_image; KUndo2Command* paint() override { KisSelectionSP selection = m_image->globalSelection(); KisSelectionTransaction transaction(selection->pixelSelection()); selection->pixelSelection()->clear(); selection->pixelSelection()->select(m_image->bounds()); return transaction.endAndTake(); } }; ap->applyCommand(new SelectAll(image), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); endAction(ap, KisOperationConfiguration(id()).toXML()); } void KisDeselectActionFactory::run(KisViewManager *view) { KisImageWSP image = view->image(); if (!image) return; KUndo2Command *cmd = new KisDeselectActiveSelectionCommand(view->selection(), image); KisProcessingApplicator *ap = beginAction(view, cmd->text()); ap->applyCommand(cmd, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); endAction(ap, KisOperationConfiguration(id()).toXML()); } void KisReselectActionFactory::run(KisViewManager *view) { KisImageWSP image = view->image(); if (!image) return; KUndo2Command *cmd = new KisReselectActiveSelectionCommand(view->activeNode(), image); KisProcessingApplicator *ap = beginAction(view, cmd->text()); ap->applyCommand(cmd, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); endAction(ap, KisOperationConfiguration(id()).toXML()); } void KisFillActionFactory::run(const QString &fillSource, KisViewManager *view) { KisNodeSP node = view->activeNode(); if (!node || !node->hasEditablePaintDevice()) return; KisSelectionSP selection = view->selection(); QRect selectedRect = selection ? selection->selectedRect() : view->image()->bounds(); Q_UNUSED(selectedRect); KisPaintDeviceSP filled = node->paintDevice()->createCompositionSourceDevice(); Q_UNUSED(filled); bool usePattern = false; bool useBgColor = false; if (fillSource.contains("pattern")) { usePattern = true; } else if (fillSource.contains("bg")) { useBgColor = true; } KisProcessingApplicator applicator(view->image(), node, KisProcessingApplicator::NONE, KisImageSignalVector() << ModifiedSignal, kundo2_i18n("Flood Fill Layer")); KisResourcesSnapshotSP resources = new KisResourcesSnapshot(view->image(), node, view->canvasResourceProvider()->resourceManager()); if (!fillSource.contains("opacity")) { resources->setOpacity(1.0); } KisProcessingVisitorSP visitor = new FillProcessingVisitor(resources->image()->projection(), QPoint(0, 0), // start position selection, resources, false, // fast mode usePattern, true, // fill only selection, 0, // feathering radius 0, // sizemod 80, // threshold, false, // use unmerged useBgColor); applicator.applyVisitor(visitor, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); applicator.end(); view->canvasResourceProvider()->slotPainting(); } void KisClearActionFactory::run(KisViewManager *view) { // XXX: "Add saving of XML data for Clear action" view->canvasBase()->toolProxy()->deleteSelection(); } void KisImageResizeToSelectionActionFactory::run(KisViewManager *view) { // XXX: "Add saving of XML data for Image Resize To Selection action" KisSelectionSP selection = view->selection(); if (!selection) return; view->image()->cropImage(selection->selectedExactRect()); } void KisCutCopyActionFactory::run(bool willCut, bool makeSharpClip, KisViewManager *view) { KisImageSP image = view->image(); if (!image) return; bool haveShapesSelected = view->selectionManager()->haveShapesSelected(); if (haveShapesSelected) { // XXX: "Add saving of XML data for Cut/Copy of shapes" KisImageBarrierLocker locker(image); if (willCut) { view->canvasBase()->toolProxy()->cut(); } else { view->canvasBase()->toolProxy()->copy(); } } else { KisNodeSP node = view->activeNode(); if (!node) return; KisSelectionSP selection = view->selection(); if (selection.isNull()) return; { KisImageBarrierLocker locker(image); KisPaintDeviceSP dev = node->paintDevice(); if (!dev) { dev = node->projection(); } if (!dev) { view->showFloatingMessage( i18nc("floating message when cannot copy from a node", "Cannot copy pixels from this type of layer "), QIcon(), 3000, KisFloatingMessage::Medium); return; } if (dev->exactBounds().isEmpty()) { view->showFloatingMessage( i18nc("floating message when copying empty selection", "Selection is empty: no pixels were copied "), QIcon(), 3000, KisFloatingMessage::Medium); return; } KisTimeRange range; KisKeyframeChannel *channel = node->getKeyframeChannel(KisKeyframeChannel::Content.id()); if (channel) { const int currentTime = image->animationInterface()->currentTime(); range = channel->affectedFrames(currentTime); } ActionHelper::copyFromDevice(view, dev, makeSharpClip, range); } KUndo2Command *command = 0; if (willCut && node->hasEditablePaintDevice()) { struct ClearSelection : public KisTransactionBasedCommand { ClearSelection(KisNodeSP node, KisSelectionSP sel) : m_node(node), m_sel(sel) {} KisNodeSP m_node; KisSelectionSP m_sel; KUndo2Command* paint() override { KisSelectionSP cutSelection = m_sel; // Shrinking the cutting area was previously used // for getting seamless cut-paste. Now we use makeSharpClip // instead. // QRect originalRect = cutSelection->selectedExactRect(); // static const int preciseSelectionThreshold = 16; // // if (originalRect.width() > preciseSelectionThreshold || // originalRect.height() > preciseSelectionThreshold) { // cutSelection = new KisSelection(*m_sel); // delete cutSelection->flatten(); // // KisSelectionFilter* filter = new KisShrinkSelectionFilter(1, 1, false); // // QRect processingRect = filter->changeRect(originalRect); // filter->process(cutSelection->pixelSelection(), processingRect); // } KisTransaction transaction(m_node->paintDevice()); m_node->paintDevice()->clearSelection(cutSelection); m_node->setDirty(cutSelection->selectedRect()); return transaction.endAndTake(); } }; command = new ClearSelection(node, selection); } KUndo2MagicString actionName = willCut ? kundo2_i18n("Cut") : kundo2_i18n("Copy"); KisProcessingApplicator *ap = beginAction(view, actionName); if (command) { ap->applyCommand(command, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::NORMAL); } KisOperationConfiguration config(id()); config.setProperty("will-cut", willCut); endAction(ap, config.toXML()); } } void KisCopyMergedActionFactory::run(KisViewManager *view) { KisImageWSP image = view->image(); if (!image) return; if (!view->blockUntilOperationsFinished(image)) return; image->barrierLock(); KisPaintDeviceSP dev = image->root()->projection(); ActionHelper::copyFromDevice(view, dev); image->unlock(); KisProcessingApplicator *ap = beginAction(view, kundo2_i18n("Copy Merged")); endAction(ap, KisOperationConfiguration(id()).toXML()); } void KisInvertSelectionOperation::runFromXML(KisViewManager* view, const KisOperationConfiguration& config) { KisSelectionFilter* filter = new KisInvertSelectionFilter(); runFilter(filter, view, config); } void KisSelectionToVectorActionFactory::run(KisViewManager *view) { KisSelectionSP selection = view->selection(); if (selection->hasShapeSelection()) { view->showFloatingMessage(i18nc("floating message", "Selection is already in a vector format "), QIcon(), 2000, KisFloatingMessage::Low); return; } if (!selection->outlineCacheValid()) { view->image()->addSpontaneousJob(new KisUpdateOutlineJob(selection, false, Qt::transparent)); if (!view->blockUntilOperationsFinished(view->image())) { return; } } QPainterPath selectionOutline = selection->outlineCache(); QTransform transform = view->canvasBase()->coordinatesConverter()->imageToDocumentTransform(); KoShape *shape = KoPathShape::createShapeFromPainterPath(transform.map(selectionOutline)); shape->setShapeId(KoPathShapeId); /** * Mark a shape that it belongs to a shape selection */ if(!shape->userData()) { shape->setUserData(new KisShapeSelectionMarker); } KisProcessingApplicator *ap = beginAction(view, kundo2_i18n("Convert to Vector Selection")); ap->applyCommand(view->canvasBase()->shapeController()->addShape(shape, 0), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); endAction(ap, KisOperationConfiguration(id()).toXML()); } void KisSelectionToRasterActionFactory::run(KisViewManager *view) { KisSelectionSP selection = view->selection(); if (!selection->hasShapeSelection()) { view->showFloatingMessage(i18nc("floating message", "Selection is already in a raster format "), QIcon(), 2000, KisFloatingMessage::Low); return; } KisProcessingApplicator *ap = beginAction(view, kundo2_i18n("Convert to Vector Selection")); struct RasterizeSelection : public KisTransactionBasedCommand { RasterizeSelection(KisSelectionSP sel) : m_sel(sel) {} KisSelectionSP m_sel; KUndo2Command* paint() override { // just create an empty transaction: it will rasterize the // selection and emit the necessary signals KisTransaction transaction(m_sel->pixelSelection()); return transaction.endAndTake(); } }; ap->applyCommand(new RasterizeSelection(selection), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); endAction(ap, KisOperationConfiguration(id()).toXML()); } void KisShapesToVectorSelectionActionFactory::run(KisViewManager* view) { const QList originalShapes = view->canvasBase()->shapeManager()->selection()->selectedShapes(); bool hasSelectionShapes = false; QList clonedShapes; Q_FOREACH (KoShape *shape, originalShapes) { if (dynamic_cast(shape->userData())) { hasSelectionShapes = true; continue; } clonedShapes << shape->cloneShape(); } if (clonedShapes.isEmpty()) { if (hasSelectionShapes) { view->showFloatingMessage(i18nc("floating message", "The shape already belongs to a selection"), QIcon(), 2000, KisFloatingMessage::Low); } return; } KisSelectionToolHelper helper(view->canvasBase(), kundo2_i18n("Convert shapes to vector selection")); helper.addSelectionShapes(clonedShapes); } void KisSelectionToShapeActionFactory::run(KisViewManager *view) { KisSelectionSP selection = view->selection(); if (!selection->outlineCacheValid()) { return; } QPainterPath selectionOutline = selection->outlineCache(); QTransform transform = view->canvasBase()->coordinatesConverter()->imageToDocumentTransform(); KoShape *shape = KoPathShape::createShapeFromPainterPath(transform.map(selectionOutline)); shape->setShapeId(KoPathShapeId); KoColor fgColor = view->canvasBase()->resourceManager()->resource(KoCanvasResourceProvider::ForegroundColor).value(); KoShapeStrokeSP border(new KoShapeStroke(1.0, fgColor.toQColor())); shape->setStroke(border); - view->document()->shapeController()->addShape(shape); + KUndo2Command *cmd = view->canvasBase()->shapeController()->addShapeDirect(shape, 0); + KisProcessingApplicator::runSingleCommandStroke(view->image(), cmd); } void KisStrokeSelectionActionFactory::run(KisViewManager *view, StrokeSelectionOptions params) { KisImageWSP image = view->image(); if (!image) { return; } KisSelectionSP selection = view->selection(); if (!selection) { return; } int size = params.lineSize; KisPixelSelectionSP pixelSelection = selection->projection(); if (!pixelSelection->outlineCacheValid()) { pixelSelection->recalculateOutlineCache(); } QPainterPath outline = pixelSelection->outlineCache(); QColor color = params.color.toQColor(); KisNodeSP currentNode = view->canvasResourceProvider()->resourceManager()->resource(KisCanvasResourceProvider::CurrentKritaNode).value(); if (!currentNode->inherits("KisShapeLayer") && currentNode->paintDevice()) { KoCanvasResourceProvider * rManager = view->canvasResourceProvider()->resourceManager(); KisToolShapeUtils::StrokeStyle strokeStyle = KisToolShapeUtils::StrokeStyleForeground; KisToolShapeUtils::FillStyle fillStyle = params.fillStyle(); KisFigurePaintingToolHelper helper(kundo2_i18n("Draw Polyline"), image, currentNode, rManager , strokeStyle, fillStyle); helper.setFGColorOverride(params.color); helper.setSelectionOverride(0); QPen pen(Qt::red, size); pen.setJoinStyle(Qt::RoundJoin); if (fillStyle != KisToolShapeUtils::FillStyleNone) { helper.paintPainterPathQPenFill(outline, pen, params.fillColor); } else { helper.paintPainterPathQPen(outline, pen, params.fillColor); } } else if (currentNode->inherits("KisShapeLayer")) { QTransform transform = view->canvasBase()->coordinatesConverter()->imageToDocumentTransform(); KoShape *shape = KoPathShape::createShapeFromPainterPath(transform.map(outline)); shape->setShapeId(KoPathShapeId); KoShapeStrokeSP border(new KoShapeStroke(size, color)); shape->setStroke(border); - view->document()->shapeController()->addShape(shape); + KUndo2Command *cmd = view->canvasBase()->shapeController()->addShapeDirect(shape, 0); + KisProcessingApplicator::runSingleCommandStroke(view->image(), cmd); } image->setModified(); - } void KisStrokeBrushSelectionActionFactory::run(KisViewManager *view, StrokeSelectionOptions params) { KisImageWSP image = view->image(); if (!image) { return; } KisSelectionSP selection = view->selection(); if (!selection) { return; } KisPixelSelectionSP pixelSelection = selection->projection(); if (!pixelSelection->outlineCacheValid()) { pixelSelection->recalculateOutlineCache(); } KisNodeSP currentNode = view->canvasResourceProvider()->resourceManager()->resource(KisCanvasResourceProvider::CurrentKritaNode).value(); if (!currentNode->inherits("KisShapeLayer") && currentNode->paintDevice()) { KoCanvasResourceProvider * rManager = view->canvasResourceProvider()->resourceManager(); QPainterPath outline = pixelSelection->outlineCache(); KisToolShapeUtils::StrokeStyle strokeStyle = KisToolShapeUtils::StrokeStyleForeground; KisToolShapeUtils::FillStyle fillStyle = KisToolShapeUtils::FillStyleNone; KoColor color = params.color; KisFigurePaintingToolHelper helper(kundo2_i18n("Draw Polyline"), image, currentNode, rManager, strokeStyle, fillStyle); helper.setFGColorOverride(color); helper.setSelectionOverride(0); helper.paintPainterPath(outline); image->setModified(); } } diff --git a/libs/ui/canvas/kis_canvas2.cpp b/libs/ui/canvas/kis_canvas2.cpp index 176ec9a78a..c600b6b0e9 100644 --- a/libs/ui/canvas/kis_canvas2.cpp +++ b/libs/ui/canvas/kis_canvas2.cpp @@ -1,1278 +1,1282 @@ /* 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) { + if (m_d->canvasWidget) { + /** + * We are switching the canvas type. We should reinitialize our + * connections to decorations and input manager + */ + widget->setDecorations(m_d->canvasWidget->decorations()); - // Redundant check for the constructor case, see below - if(viewManager() != 0) + if(viewManager()) { viewManager()->inputManager()->removeTrackedCanvas(this); + m_d->canvasWidget = widget; + viewManager()->inputManager()->addTrackedCanvas(this); + } else { + m_d->canvasWidget = widget; + } + } else { + m_d->canvasWidget = widget; } - 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, 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); } 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/dialogs/kis_dlg_preferences.cc b/libs/ui/dialogs/kis_dlg_preferences.cc index 22c25cae85..d4e1109305 100644 --- a/libs/ui/dialogs/kis_dlg_preferences.cc +++ b/libs/ui/dialogs/kis_dlg_preferences.cc @@ -1,1808 +1,1811 @@ /* * preferencesdlg.cc - part of KImageShop * * Copyright (c) 1999 Michael Koch * Copyright (c) 2003-2011 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_dlg_preferences.h" #include #include #include #include +#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #include #include #include #include #include #include #include #include #include #include "KoID.h" #include #include #include #include #include #include #include #include "KisProofingConfiguration.h" #include "KoColorConversionTransformation.h" #include "kis_action_registry.h" #include #include #include "kis_clipboard.h" #include "widgets/kis_cmb_idlist.h" #include "KoColorSpace.h" #include "KoColorSpaceRegistry.h" #include "kis_canvas_resource_provider.h" #include "kis_color_manager.h" #include "kis_config.h" #include "kis_cursor.h" #include "kis_image_config.h" #include "kis_preference_set_registry.h" #include "kis_file_name_requester.h" #include "slider_and_spin_box_sync.h" // for the performance update #include #include #include "input/config/kis_input_configuration_page.h" #include "input/wintab/drawpile_tablettester/tablettester.h" #ifdef Q_OS_WIN #include "config_use_qt_tablet_windows.h" # ifndef USE_QT_TABLET_WINDOWS # include # endif #include "config-high-dpi-scale-factor-rounding-policy.h" #endif struct BackupSuffixValidator : public QValidator { BackupSuffixValidator(QObject *parent) : QValidator(parent) , invalidCharacters(QStringList() << "0" << "1" << "2" << "3" << "4" << "5" << "6" << "7" << "8" << "9" << "/" << "\\" << ":" << ";" << " ") {} ~BackupSuffixValidator() override {} const QStringList invalidCharacters; State validate(QString &line, int &/*pos*/) const override { Q_FOREACH(const QString invalidChar, invalidCharacters) { if (line.contains(invalidChar)) { return Invalid; } } return Acceptable; } }; GeneralTab::GeneralTab(QWidget *_parent, const char *_name) : WdgGeneralSettings(_parent, _name) { KisConfig cfg(true); // // Cursor Tab // m_cmbCursorShape->addItem(i18n("No Cursor")); m_cmbCursorShape->addItem(i18n("Tool Icon")); m_cmbCursorShape->addItem(i18n("Arrow")); m_cmbCursorShape->addItem(i18n("Small Circle")); m_cmbCursorShape->addItem(i18n("Crosshair")); m_cmbCursorShape->addItem(i18n("Triangle Righthanded")); m_cmbCursorShape->addItem(i18n("Triangle Lefthanded")); m_cmbCursorShape->addItem(i18n("Black Pixel")); m_cmbCursorShape->addItem(i18n("White Pixel")); m_cmbCursorShape->setCurrentIndex(cfg.newCursorStyle()); m_cmbOutlineShape->addItem(i18n("No Outline")); m_cmbOutlineShape->addItem(i18n("Circle Outline")); m_cmbOutlineShape->addItem(i18n("Preview Outline")); m_cmbOutlineShape->addItem(i18n("Tilt Outline")); m_cmbOutlineShape->setCurrentIndex(cfg.newOutlineStyle()); m_showOutlinePainting->setChecked(cfg.showOutlineWhilePainting()); m_changeBrushOutline->setChecked(!cfg.forceAlwaysFullSizedOutline()); KoColor cursorColor(KoColorSpaceRegistry::instance()->rgb8()); cursorColor.fromQColor(cfg.getCursorMainColor()); cursorColorBtutton->setColor(cursorColor); // // Window Tab // m_cmbMDIType->setCurrentIndex(cfg.readEntry("mdi_viewmode", (int)QMdiArea::TabbedView)); m_backgroundimage->setText(cfg.getMDIBackgroundImage()); connect(m_bnFileName, SIGNAL(clicked()), SLOT(getBackgroundImage())); connect(clearBgImageButton, SIGNAL(clicked()), SLOT(clearBackgroundImage())); QString xml = cfg.getMDIBackgroundColor(); KoColor mdiColor = KoColor::fromXML(xml); m_mdiColor->setColor(mdiColor); m_chkRubberBand->setChecked(cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); m_chkCanvasMessages->setChecked(cfg.showCanvasMessages()); const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); m_chkHiDPI->setChecked(kritarc.value("EnableHiDPI", true).toBool()); #ifdef HAVE_HIGH_DPI_SCALE_FACTOR_ROUNDING_POLICY m_chkHiDPIFractionalScaling->setChecked(kritarc.value("EnableHiDPIFractionalScaling", true).toBool()); #else m_chkHiDPIFractionalScaling->setVisible(false); #endif chkUsageLogging->setChecked(kritarc.value("LogUsage", true).toBool()); // // Tools tab // m_radioToolOptionsInDocker->setChecked(cfg.toolOptionsInDocker()); cmbFlowMode->setCurrentIndex((int)!cfg.readEntry("useCreamyAlphaDarken", true)); m_chkSwitchSelectionCtrlAlt->setChecked(cfg.switchSelectionCtrlAlt()); chkEnableTouch->setChecked(!cfg.disableTouchOnCanvas()); chkEnableTouchRotation->setChecked(!cfg.disableTouchRotation()); chkEnableTranformToolAfterPaste->setChecked(cfg.activateTransformToolAfterPaste()); m_groupBoxKineticScrollingSettings->setChecked(cfg.kineticScrollingEnabled()); m_cmbKineticScrollingGesture->addItem(i18n("On Touch Drag")); m_cmbKineticScrollingGesture->addItem(i18n("On Click Drag")); m_cmbKineticScrollingGesture->addItem(i18n("On Middle-Click Drag")); //m_cmbKineticScrollingGesture->addItem(i18n("On Right Click Drag")); m_cmbKineticScrollingGesture->setCurrentIndex(cfg.kineticScrollingGesture()); m_kineticScrollingSensitivitySlider->setRange(0, 100); m_kineticScrollingSensitivitySlider->setValue(cfg.kineticScrollingSensitivity()); m_chkKineticScrollingHideScrollbars->setChecked(cfg.kineticScrollingHiddenScrollbars()); // // File handling // int autosaveInterval = cfg.autoSaveInterval(); //convert to minutes m_autosaveSpinBox->setValue(autosaveInterval / 60); m_autosaveCheckBox->setChecked(autosaveInterval > 0); chkHideAutosaveFiles->setChecked(cfg.readEntry("autosavefileshidden", true)); m_chkCompressKra->setChecked(cfg.compressKra()); chkZip64->setChecked(cfg.useZip64()); m_chkTrimKra->setChecked(cfg.trimKra()); m_backupFileCheckBox->setChecked(cfg.backupFile()); cmbBackupFileLocation->setCurrentIndex(cfg.readEntry("backupfilelocation", 0)); txtBackupFileSuffix->setText(cfg.readEntry("backupfilesuffix", "~")); QValidator *validator = new BackupSuffixValidator(txtBackupFileSuffix); txtBackupFileSuffix->setValidator(validator); intNumBackupFiles->setValue(cfg.readEntry("numberofbackupfiles", 1)); // // Miscellaneous // cmbStartupSession->addItem(i18n("Open default window")); cmbStartupSession->addItem(i18n("Load previous session")); cmbStartupSession->addItem(i18n("Show session manager")); cmbStartupSession->setCurrentIndex(cfg.sessionOnStartup()); chkSaveSessionOnQuit->setChecked(cfg.saveSessionOnQuit(false)); m_chkConvertOnImport->setChecked(cfg.convertToImageColorspaceOnImport()); m_undoStackSize->setValue(cfg.undoStackLimit()); m_favoritePresetsSpinBox->setValue(cfg.favoritePresets()); chkShowRootLayer->setChecked(cfg.showRootLayer()); m_chkAutoPin->setChecked(cfg.autoPinLayersToTimeline()); KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); bool dontUseNative = true; #ifdef Q_OS_UNIX if (qgetenv("XDG_CURRENT_DESKTOP") == "KDE") { dontUseNative = false; } #endif #ifdef Q_OS_WIN dontUseNative = false; #endif m_chkNativeFileDialog->setChecked(!group.readEntry("DontUseNativeFileDialog", dontUseNative)); intMaxBrushSize->setValue(cfg.readEntry("maximumBrushSize", 1000)); // // Resources // m_urlCacheDbLocation->setMode(KoFileDialog::OpenDirectory); m_urlCacheDbLocation->setConfigurationName("cachedb_location"); m_urlCacheDbLocation->setFileName(cfg.readEntry(KisResourceCacheDb::dbLocationKey, QStandardPaths::writableLocation(QStandardPaths::AppDataLocation))); m_urlResourceFolder->setMode(KoFileDialog::OpenDirectory); m_urlResourceFolder->setConfigurationName("resource_directory"); m_urlResourceFolder->setFileName(cfg.readEntry(KisResourceLocator::resourceLocationKey, QStandardPaths::writableLocation(QStandardPaths::AppDataLocation))); } void GeneralTab::setDefault() { KisConfig cfg(true); m_cmbCursorShape->setCurrentIndex(cfg.newCursorStyle(true)); m_cmbOutlineShape->setCurrentIndex(cfg.newOutlineStyle(true)); chkShowRootLayer->setChecked(cfg.showRootLayer(true)); m_autosaveCheckBox->setChecked(cfg.autoSaveInterval(true) > 0); //convert to minutes m_autosaveSpinBox->setValue(cfg.autoSaveInterval(true) / 60); chkHideAutosaveFiles->setChecked(true); m_undoStackSize->setValue(cfg.undoStackLimit(true)); m_backupFileCheckBox->setChecked(cfg.backupFile(true)); cmbBackupFileLocation->setCurrentIndex(0); txtBackupFileSuffix->setText("~"); intNumBackupFiles->setValue(1); m_showOutlinePainting->setChecked(cfg.showOutlineWhilePainting(true)); m_changeBrushOutline->setChecked(!cfg.forceAlwaysFullSizedOutline(true)); m_chkNativeFileDialog->setChecked(false); intMaxBrushSize->setValue(1000); m_cmbMDIType->setCurrentIndex((int)QMdiArea::TabbedView); m_chkRubberBand->setChecked(cfg.useOpenGL(true)); m_favoritePresetsSpinBox->setValue(cfg.favoritePresets(true)); KoColor mdiColor; mdiColor.fromXML(cfg.getMDIBackgroundColor(true)); m_mdiColor->setColor(mdiColor); m_backgroundimage->setText(cfg.getMDIBackgroundImage(true)); m_chkCanvasMessages->setChecked(cfg.showCanvasMessages(true)); m_chkCompressKra->setChecked(cfg.compressKra(true)); m_chkTrimKra->setChecked(cfg.trimKra(true)); chkZip64->setChecked(cfg.useZip64(true)); m_chkHiDPI->setChecked(false); m_chkHiDPI->setChecked(true); #ifdef HAVE_HIGH_DPI_SCALE_FACTOR_ROUNDING_POLICY m_chkHiDPIFractionalScaling->setChecked(true); #endif chkUsageLogging->setChecked(true); m_radioToolOptionsInDocker->setChecked(cfg.toolOptionsInDocker(true)); cmbFlowMode->setCurrentIndex(0); m_groupBoxKineticScrollingSettings->setChecked(cfg.kineticScrollingEnabled(true)); m_cmbKineticScrollingGesture->setCurrentIndex(cfg.kineticScrollingGesture(true)); m_kineticScrollingSensitivitySlider->setValue(cfg.kineticScrollingSensitivity(true)); m_chkKineticScrollingHideScrollbars->setChecked(cfg.kineticScrollingHiddenScrollbars(true)); m_chkSwitchSelectionCtrlAlt->setChecked(cfg.switchSelectionCtrlAlt(true)); chkEnableTouch->setChecked(!cfg.disableTouchOnCanvas(true)); chkEnableTouchRotation->setChecked(!cfg.disableTouchRotation(true)); chkEnableTranformToolAfterPaste->setChecked(cfg.activateTransformToolAfterPaste(true)); m_chkConvertOnImport->setChecked(cfg.convertToImageColorspaceOnImport(true)); KoColor cursorColor(KoColorSpaceRegistry::instance()->rgb8()); cursorColor.fromQColor(cfg.getCursorMainColor(true)); cursorColorBtutton->setColor(cursorColor); m_chkAutoPin->setChecked(cfg.autoPinLayersToTimeline(true)); m_urlCacheDbLocation->setFileName(cfg.readEntry(KisResourceCacheDb::dbLocationKey, QStandardPaths::writableLocation(QStandardPaths::AppDataLocation))); m_urlResourceFolder->setFileName(cfg.readEntry(KisResourceLocator::resourceLocationKey, QStandardPaths::writableLocation(QStandardPaths::AppDataLocation))); } CursorStyle GeneralTab::cursorStyle() { return (CursorStyle)m_cmbCursorShape->currentIndex(); } OutlineStyle GeneralTab::outlineStyle() { return (OutlineStyle)m_cmbOutlineShape->currentIndex(); } KisConfig::SessionOnStartup GeneralTab::sessionOnStartup() const { return (KisConfig::SessionOnStartup)cmbStartupSession->currentIndex(); } bool GeneralTab::saveSessionOnQuit() const { return chkSaveSessionOnQuit->isChecked(); } bool GeneralTab::showRootLayer() { return chkShowRootLayer->isChecked(); } int GeneralTab::autoSaveInterval() { //convert to seconds return m_autosaveCheckBox->isChecked() ? m_autosaveSpinBox->value() * 60 : 0; } int GeneralTab::undoStackSize() { return m_undoStackSize->value(); } bool GeneralTab::showOutlineWhilePainting() { return m_showOutlinePainting->isChecked(); } int GeneralTab::mdiMode() { return m_cmbMDIType->currentIndex(); } int GeneralTab::favoritePresets() { return m_favoritePresetsSpinBox->value(); } bool GeneralTab::showCanvasMessages() { return m_chkCanvasMessages->isChecked(); } bool GeneralTab::compressKra() { return m_chkCompressKra->isChecked(); } bool GeneralTab::trimKra() { return m_chkTrimKra->isChecked(); } bool GeneralTab::useZip64() { return chkZip64->isChecked(); } bool GeneralTab::toolOptionsInDocker() { return m_radioToolOptionsInDocker->isChecked(); } bool GeneralTab::kineticScrollingEnabled() { return m_groupBoxKineticScrollingSettings->isChecked(); } int GeneralTab::kineticScrollingGesture() { return m_cmbKineticScrollingGesture->currentIndex(); } int GeneralTab::kineticScrollingSensitivity() { return m_kineticScrollingSensitivitySlider->value(); } bool GeneralTab::kineticScrollingHiddenScrollbars() { return m_chkKineticScrollingHideScrollbars->isChecked(); } bool GeneralTab::switchSelectionCtrlAlt() { return m_chkSwitchSelectionCtrlAlt->isChecked(); } bool GeneralTab::convertToImageColorspaceOnImport() { return m_chkConvertOnImport->isChecked(); } bool GeneralTab::autopinLayersToTimeline() { return m_chkAutoPin->isChecked(); } void GeneralTab::getBackgroundImage() { KoFileDialog dialog(this, KoFileDialog::OpenFile, "BackgroundImages"); dialog.setCaption(i18n("Select a Background Image")); dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)); dialog.setImageFilters(); QString fn = dialog.filename(); // dialog box was canceled or somehow no file was selected if (fn.isEmpty()) { return; } QImage image(fn); if (image.isNull()) { QMessageBox::warning(this, i18nc("@title:window", "Krita"), i18n("%1 is not a valid image file!", fn)); } else { m_backgroundimage->setText(fn); } } void GeneralTab::clearBackgroundImage() { // clearing the background image text will implicitly make the background color be used m_backgroundimage->setText(""); } #include "kactioncollection.h" #include "KisActionsSnapshot.h" ShortcutSettingsTab::ShortcutSettingsTab(QWidget *parent, const char *name) : QWidget(parent) { setObjectName(name); QGridLayout * l = new QGridLayout(this); l->setMargin(0); m_page = new WdgShortcutSettings(this); l->addWidget(m_page, 0, 0); m_snapshot.reset(new KisActionsSnapshot); KActionCollection *collection = KisPart::instance()->currentMainwindow()->actionCollection(); Q_FOREACH (QAction *action, collection->actions()) { m_snapshot->addAction(action->objectName(), action); } QMap sortedCollections = m_snapshot->actionCollections(); for (auto it = sortedCollections.constBegin(); it != sortedCollections.constEnd(); ++it) { m_page->addCollection(it.value(), it.key()); } } ShortcutSettingsTab::~ShortcutSettingsTab() { } void ShortcutSettingsTab::setDefault() { m_page->allDefault(); } void ShortcutSettingsTab::saveChanges() { m_page->save(); KisActionRegistry::instance()->settingsPageSaved(); } void ShortcutSettingsTab::cancelChanges() { m_page->undo(); } ColorSettingsTab::ColorSettingsTab(QWidget *parent, const char *name) : QWidget(parent) { setObjectName(name); // XXX: Make sure only profiles that fit the specified color model // are shown in the profile combos QGridLayout * l = new QGridLayout(this); l->setMargin(0); m_page = new WdgColorSettings(this); l->addWidget(m_page, 0, 0); KisConfig cfg(true); m_page->chkUseSystemMonitorProfile->setChecked(cfg.useSystemMonitorProfile()); connect(m_page->chkUseSystemMonitorProfile, SIGNAL(toggled(bool)), this, SLOT(toggleAllowMonitorProfileSelection(bool))); m_page->cmbWorkingColorSpace->setIDList(KoColorSpaceRegistry::instance()->listKeys()); m_page->cmbWorkingColorSpace->setCurrent(cfg.workingColorSpace()); m_page->bnAddColorProfile->setIcon(KisIconUtils::loadIcon("document-open")); m_page->bnAddColorProfile->setToolTip( i18n("Open Color Profile") ); connect(m_page->bnAddColorProfile, SIGNAL(clicked()), SLOT(installProfile())); QFormLayout *monitorProfileGrid = new QFormLayout(m_page->monitorprofileholder); for(int i = 0; i < QGuiApplication::screens().count(); ++i) { QLabel *lbl = new QLabel(i18nc("The number of the screen", "Screen %1:", i + 1)); m_monitorProfileLabels << lbl; KisSqueezedComboBox *cmb = new KisSqueezedComboBox(); cmb->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); monitorProfileGrid->addRow(lbl, cmb); m_monitorProfileWidgets << cmb; } // disable if not Linux as KisColorManager is not yet implemented outside Linux #ifndef Q_OS_LINUX m_page->chkUseSystemMonitorProfile->setChecked(false); m_page->chkUseSystemMonitorProfile->setDisabled(true); m_page->chkUseSystemMonitorProfile->setHidden(true); #endif refillMonitorProfiles(KoID("RGBA")); for(int i = 0; i < QApplication::screens().count(); ++i) { if (m_monitorProfileWidgets[i]->contains(cfg.monitorProfile(i))) { m_monitorProfileWidgets[i]->setCurrent(cfg.monitorProfile(i)); } } m_page->chkBlackpoint->setChecked(cfg.useBlackPointCompensation()); m_page->chkAllowLCMSOptimization->setChecked(cfg.allowLCMSOptimization()); m_page->chkForcePaletteColor->setChecked(cfg.forcePaletteColors()); KisImageConfig cfgImage(true); KisProofingConfigurationSP proofingConfig = cfgImage.defaultProofingconfiguration(); m_page->sldAdaptationState->setMaximum(20); m_page->sldAdaptationState->setMinimum(0); m_page->sldAdaptationState->setValue((int)proofingConfig->adaptationState*20); //probably this should become the screenprofile? KoColor ga(KoColorSpaceRegistry::instance()->rgb8()); ga.fromKoColor(proofingConfig->warningColor); m_page->gamutAlarm->setColor(ga); const KoColorSpace *proofingSpace = KoColorSpaceRegistry::instance()->colorSpace(proofingConfig->proofingModel, proofingConfig->proofingDepth, proofingConfig->proofingProfile); if (proofingSpace) { m_page->proofingSpaceSelector->setCurrentColorSpace(proofingSpace); } m_page->cmbProofingIntent->setCurrentIndex((int)proofingConfig->intent); m_page->ckbProofBlackPoint->setChecked(proofingConfig->conversionFlags.testFlag(KoColorConversionTransformation::BlackpointCompensation)); m_pasteBehaviourGroup.addButton(m_page->radioPasteWeb, PASTE_ASSUME_WEB); m_pasteBehaviourGroup.addButton(m_page->radioPasteMonitor, PASTE_ASSUME_MONITOR); m_pasteBehaviourGroup.addButton(m_page->radioPasteAsk, PASTE_ASK); QAbstractButton *button = m_pasteBehaviourGroup.button(cfg.pasteBehaviour()); Q_ASSERT(button); if (button) { button->setChecked(true); } m_page->cmbMonitorIntent->setCurrentIndex(cfg.monitorRenderIntent()); toggleAllowMonitorProfileSelection(cfg.useSystemMonitorProfile()); } void ColorSettingsTab::installProfile() { KoFileDialog dialog(this, KoFileDialog::OpenFiles, "OpenDocumentICC"); dialog.setCaption(i18n("Install Color Profiles")); dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::HomeLocation)); dialog.setMimeTypeFilters(QStringList() << "application/vnd.iccprofile", "application/vnd.iccprofile"); QStringList profileNames = dialog.filenames(); KoColorSpaceEngine *iccEngine = KoColorSpaceEngineRegistry::instance()->get("icc"); Q_ASSERT(iccEngine); QString saveLocation = KoResourcePaths::saveLocation("icc_profiles"); Q_FOREACH (const QString &profileName, profileNames) { if (!QFile::copy(profileName, saveLocation + QFileInfo(profileName).fileName())) { qWarning() << "Could not install profile!" << saveLocation + QFileInfo(profileName).fileName(); continue; } iccEngine->addProfile(saveLocation + QFileInfo(profileName).fileName()); } KisConfig cfg(true); refillMonitorProfiles(KoID("RGBA")); for(int i = 0; i < QApplication::screens().count(); ++i) { if (m_monitorProfileWidgets[i]->contains(cfg.monitorProfile(i))) { m_monitorProfileWidgets[i]->setCurrent(cfg.monitorProfile(i)); } } } void ColorSettingsTab::toggleAllowMonitorProfileSelection(bool useSystemProfile) { KisConfig cfg(true); if (useSystemProfile) { QStringList devices = KisColorManager::instance()->devices(); if (devices.size() == QApplication::screens().count()) { for(int i = 0; i < QApplication::screens().count(); ++i) { m_monitorProfileWidgets[i]->clear(); QString monitorForScreen = cfg.monitorForScreen(i, devices[i]); Q_FOREACH (const QString &device, devices) { m_monitorProfileLabels[i]->setText(i18nc("The display/screen we got from Qt", "Screen %1:", i + 1)); m_monitorProfileWidgets[i]->addSqueezedItem(KisColorManager::instance()->deviceName(device), device); if (devices[i] == monitorForScreen) { m_monitorProfileWidgets[i]->setCurrentIndex(i); } } } } } else { refillMonitorProfiles(KoID("RGBA")); for(int i = 0; i < QApplication::screens().count(); ++i) { if (m_monitorProfileWidgets[i]->contains(cfg.monitorProfile(i))) { m_monitorProfileWidgets[i]->setCurrent(cfg.monitorProfile(i)); } } } } void ColorSettingsTab::setDefault() { m_page->cmbWorkingColorSpace->setCurrent("RGBA"); refillMonitorProfiles(KoID("RGBA")); KisConfig cfg(true); KisImageConfig cfgImage(true); KisProofingConfigurationSP proofingConfig = cfgImage.defaultProofingconfiguration(); const KoColorSpace *proofingSpace = KoColorSpaceRegistry::instance()->colorSpace(proofingConfig->proofingModel,proofingConfig->proofingDepth,proofingConfig->proofingProfile); if (proofingSpace) { m_page->proofingSpaceSelector->setCurrentColorSpace(proofingSpace); } m_page->cmbProofingIntent->setCurrentIndex((int)proofingConfig->intent); m_page->ckbProofBlackPoint->setChecked(proofingConfig->conversionFlags.testFlag(KoColorConversionTransformation::BlackpointCompensation)); m_page->sldAdaptationState->setValue(0); //probably this should become the screenprofile? KoColor ga(KoColorSpaceRegistry::instance()->rgb8()); ga.fromKoColor(proofingConfig->warningColor); m_page->gamutAlarm->setColor(ga); m_page->chkBlackpoint->setChecked(cfg.useBlackPointCompensation(true)); m_page->chkAllowLCMSOptimization->setChecked(cfg.allowLCMSOptimization(true)); m_page->chkForcePaletteColor->setChecked(cfg.forcePaletteColors(true)); m_page->cmbMonitorIntent->setCurrentIndex(cfg.monitorRenderIntent(true)); m_page->chkUseSystemMonitorProfile->setChecked(cfg.useSystemMonitorProfile(true)); QAbstractButton *button = m_pasteBehaviourGroup.button(cfg.pasteBehaviour(true)); Q_ASSERT(button); if (button) { button->setChecked(true); } } void ColorSettingsTab::refillMonitorProfiles(const KoID & colorSpaceId) { for (int i = 0; i < QApplication::screens().count(); ++i) { m_monitorProfileWidgets[i]->clear(); } QMap profileList; Q_FOREACH(const KoColorProfile *profile, KoColorSpaceRegistry::instance()->profilesFor(colorSpaceId.id())) { profileList[profile->name()] = profile; } Q_FOREACH (const KoColorProfile *profile, profileList.values()) { //qDebug() << "Profile" << profile->name() << profile->isSuitableForDisplay() << csf->defaultProfile(); if (profile->isSuitableForDisplay()) { for (int i = 0; i < QApplication::screens().count(); ++i) { m_monitorProfileWidgets[i]->addSqueezedItem(profile->name()); } } } for (int i = 0; i < QApplication::screens().count(); ++i) { m_monitorProfileLabels[i]->setText(i18nc("The number of the screen", "Screen %1:", i + 1)); m_monitorProfileWidgets[i]->setCurrent(KoColorSpaceRegistry::instance()->defaultProfileForColorSpace(colorSpaceId.id())); } } //--------------------------------------------------------------------------------------------------- void TabletSettingsTab::setDefault() { KisCubicCurve curve; curve.fromString(DEFAULT_CURVE_STRING); m_page->pressureCurve->setCurve(curve); m_page->chkUseRightMiddleClickWorkaround->setChecked( KisConfig(true).useRightMiddleTabletButtonWorkaround(true)); #if defined Q_OS_WIN && (!defined USE_QT_TABLET_WINDOWS || defined QT_HAS_WINTAB_SWITCH) #ifdef USE_QT_TABLET_WINDOWS // ask Qt if WinInk is actually available const bool isWinInkAvailable = true; #else const bool isWinInkAvailable = KisTabletSupportWin8::isAvailable(); #endif if (isWinInkAvailable) { KisConfig cfg(true); m_page->radioWintab->setChecked(!cfg.useWin8PointerInput(true)); m_page->radioWin8PointerInput->setChecked(cfg.useWin8PointerInput(true)); } else { m_page->radioWintab->setChecked(true); m_page->radioWin8PointerInput->setChecked(false); } #else m_page->grpTabletApi->setVisible(false); #endif } TabletSettingsTab::TabletSettingsTab(QWidget* parent, const char* name): QWidget(parent) { setObjectName(name); QGridLayout * l = new QGridLayout(this); l->setMargin(0); m_page = new WdgTabletSettings(this); l->addWidget(m_page, 0, 0); KisConfig cfg(true); KisCubicCurve curve; curve.fromString( cfg.pressureTabletCurve() ); m_page->pressureCurve->setMaximumSize(QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX)); m_page->pressureCurve->setCurve(curve); m_page->chkUseRightMiddleClickWorkaround->setChecked( cfg.useRightMiddleTabletButtonWorkaround()); #if defined Q_OS_WIN && (!defined USE_QT_TABLET_WINDOWS || defined QT_HAS_WINTAB_SWITCH) #ifdef USE_QT_TABLET_WINDOWS // ask Qt if WinInk is actually available const bool isWinInkAvailable = true; #else const bool isWinInkAvailable = KisTabletSupportWin8::isAvailable(); #endif if (isWinInkAvailable) { m_page->radioWintab->setChecked(!cfg.useWin8PointerInput()); m_page->radioWin8PointerInput->setChecked(cfg.useWin8PointerInput()); } else { m_page->radioWintab->setChecked(true); m_page->radioWin8PointerInput->setChecked(false); m_page->grpTabletApi->setVisible(false); } #ifdef USE_QT_TABLET_WINDOWS connect(m_page->btnResolutionSettings, SIGNAL(clicked()), SLOT(slotResolutionSettings())); connect(m_page->radioWintab, SIGNAL(toggled(bool)), m_page->btnResolutionSettings, SLOT(setEnabled(bool))); m_page->btnResolutionSettings->setEnabled(m_page->radioWintab->isChecked()); #else m_page->btnResolutionSettings->setVisible(false); #endif #else m_page->grpTabletApi->setVisible(false); #endif connect(m_page->btnTabletTest, SIGNAL(clicked()), SLOT(slotTabletTest())); } void TabletSettingsTab::slotTabletTest() { TabletTestDialog tabletTestDialog(this); tabletTestDialog.exec(); } #if defined Q_OS_WIN && defined USE_QT_TABLET_WINDOWS #include "KisDlgCustomTabletResolution.h" #endif void TabletSettingsTab::slotResolutionSettings() { #if defined Q_OS_WIN && defined USE_QT_TABLET_WINDOWS KisDlgCustomTabletResolution dlg(this); dlg.exec(); #endif } //--------------------------------------------------------------------------------------------------- #include "kis_acyclic_signal_connector.h" int getTotalRAM() { return KisImageConfig(true).totalRAM(); } int PerformanceTab::realTilesRAM() { return intMemoryLimit->value() - intPoolLimit->value(); } PerformanceTab::PerformanceTab(QWidget *parent, const char *name) : WdgPerformanceSettings(parent, name) { KisImageConfig cfg(true); const double totalRAM = cfg.totalRAM(); lblTotalMemory->setText(KFormat().formatByteSize(totalRAM * 1024 * 1024, 0, KFormat::IECBinaryDialect, KFormat::UnitMegaByte)); sliderMemoryLimit->setSuffix(i18n(" %")); sliderMemoryLimit->setRange(1, 100, 2); sliderMemoryLimit->setSingleStep(0.01); sliderPoolLimit->setSuffix(i18n(" %")); sliderPoolLimit->setRange(0, 20, 2); sliderMemoryLimit->setSingleStep(0.01); sliderUndoLimit->setSuffix(i18n(" %")); sliderUndoLimit->setRange(0, 50, 2); sliderMemoryLimit->setSingleStep(0.01); intMemoryLimit->setMinimumWidth(80); intPoolLimit->setMinimumWidth(80); intUndoLimit->setMinimumWidth(80); SliderAndSpinBoxSync *sync1 = new SliderAndSpinBoxSync(sliderMemoryLimit, intMemoryLimit, getTotalRAM); sync1->slotParentValueChanged(); m_syncs << sync1; SliderAndSpinBoxSync *sync2 = new SliderAndSpinBoxSync(sliderPoolLimit, intPoolLimit, std::bind(&KisIntParseSpinBox::value, intMemoryLimit)); connect(intMemoryLimit, SIGNAL(valueChanged(int)), sync2, SLOT(slotParentValueChanged())); sync2->slotParentValueChanged(); m_syncs << sync2; SliderAndSpinBoxSync *sync3 = new SliderAndSpinBoxSync(sliderUndoLimit, intUndoLimit, std::bind(&PerformanceTab::realTilesRAM, this)); connect(intPoolLimit, SIGNAL(valueChanged(int)), sync3, SLOT(slotParentValueChanged())); connect(intMemoryLimit, SIGNAL(valueChanged(int)), sync3, SLOT(slotParentValueChanged())); sync3->slotParentValueChanged(); m_syncs << sync3; sliderSwapSize->setSuffix(i18n(" GiB")); sliderSwapSize->setRange(1, 64); intSwapSize->setRange(1, 64); KisAcyclicSignalConnector *swapSizeConnector = new KisAcyclicSignalConnector(this); swapSizeConnector->connectForwardInt(sliderSwapSize, SIGNAL(valueChanged(int)), intSwapSize, SLOT(setValue(int))); swapSizeConnector->connectBackwardInt(intSwapSize, SIGNAL(valueChanged(int)), sliderSwapSize, SLOT(setValue(int))); lblSwapFileLocation->setText(cfg.swapDir()); connect(bnSwapFile, SIGNAL(clicked()), SLOT(selectSwapDir())); sliderThreadsLimit->setRange(1, QThread::idealThreadCount()); sliderFrameClonesLimit->setRange(1, QThread::idealThreadCount()); sliderFpsLimit->setRange(20, 300); sliderFpsLimit->setSuffix(i18n(" fps")); connect(sliderThreadsLimit, SIGNAL(valueChanged(int)), SLOT(slotThreadsLimitChanged(int))); connect(sliderFrameClonesLimit, SIGNAL(valueChanged(int)), SLOT(slotFrameClonesLimitChanged(int))); intCachedFramesSizeLimit->setRange(1, 10000); intCachedFramesSizeLimit->setSuffix(i18n(" px")); intCachedFramesSizeLimit->setSingleStep(1); intCachedFramesSizeLimit->setPageStep(1000); intRegionOfInterestMargin->setRange(1, 100); intRegionOfInterestMargin->setSuffix(i18n(" %")); intRegionOfInterestMargin->setSingleStep(1); intRegionOfInterestMargin->setPageStep(10); connect(chkCachedFramesSizeLimit, SIGNAL(toggled(bool)), intCachedFramesSizeLimit, SLOT(setEnabled(bool))); connect(chkUseRegionOfInterest, SIGNAL(toggled(bool)), intRegionOfInterestMargin, SLOT(setEnabled(bool))); #ifndef Q_OS_WIN // AVX workaround is needed on Windows+GCC only chkDisableAVXOptimizations->setVisible(false); #endif load(false); } PerformanceTab::~PerformanceTab() { qDeleteAll(m_syncs); } void PerformanceTab::load(bool requestDefault) { KisImageConfig cfg(true); sliderMemoryLimit->setValue(cfg.memoryHardLimitPercent(requestDefault)); sliderPoolLimit->setValue(cfg.memoryPoolLimitPercent(requestDefault)); sliderUndoLimit->setValue(cfg.memorySoftLimitPercent(requestDefault)); chkPerformanceLogging->setChecked(cfg.enablePerfLog(requestDefault)); chkProgressReporting->setChecked(cfg.enableProgressReporting(requestDefault)); sliderSwapSize->setValue(cfg.maxSwapSize(requestDefault) / 1024); lblSwapFileLocation->setText(cfg.swapDir(requestDefault)); m_lastUsedThreadsLimit = cfg.maxNumberOfThreads(requestDefault); m_lastUsedClonesLimit = cfg.frameRenderingClones(requestDefault); sliderThreadsLimit->setValue(m_lastUsedThreadsLimit); sliderFrameClonesLimit->setValue(m_lastUsedClonesLimit); sliderFpsLimit->setValue(cfg.fpsLimit(requestDefault)); { KisConfig cfg2(true); chkOpenGLFramerateLogging->setChecked(cfg2.enableOpenGLFramerateLogging(requestDefault)); chkBrushSpeedLogging->setChecked(cfg2.enableBrushSpeedLogging(requestDefault)); chkDisableVectorOptimizations->setChecked(cfg2.enableAmdVectorizationWorkaround(requestDefault)); #ifdef Q_OS_WIN chkDisableAVXOptimizations->setChecked(cfg2.disableAVXOptimizations(requestDefault)); #endif chkBackgroundCacheGeneration->setChecked(cfg2.calculateAnimationCacheInBackground(requestDefault)); } if (cfg.useOnDiskAnimationCacheSwapping(requestDefault)) { optOnDisk->setChecked(true); } else { optInMemory->setChecked(true); } chkCachedFramesSizeLimit->setChecked(cfg.useAnimationCacheFrameSizeLimit(requestDefault)); intCachedFramesSizeLimit->setValue(cfg.animationCacheFrameSizeLimit(requestDefault)); intCachedFramesSizeLimit->setEnabled(chkCachedFramesSizeLimit->isChecked()); chkUseRegionOfInterest->setChecked(cfg.useAnimationCacheRegionOfInterest(requestDefault)); intRegionOfInterestMargin->setValue(cfg.animationCacheRegionOfInterestMargin(requestDefault) * 100.0); intRegionOfInterestMargin->setEnabled(chkUseRegionOfInterest->isChecked()); } void PerformanceTab::save() { KisImageConfig cfg(false); cfg.setMemoryHardLimitPercent(sliderMemoryLimit->value()); cfg.setMemorySoftLimitPercent(sliderUndoLimit->value()); cfg.setMemoryPoolLimitPercent(sliderPoolLimit->value()); cfg.setEnablePerfLog(chkPerformanceLogging->isChecked()); cfg.setEnableProgressReporting(chkProgressReporting->isChecked()); cfg.setMaxSwapSize(sliderSwapSize->value() * 1024); cfg.setSwapDir(lblSwapFileLocation->text()); cfg.setMaxNumberOfThreads(sliderThreadsLimit->value()); cfg.setFrameRenderingClones(sliderFrameClonesLimit->value()); cfg.setFpsLimit(sliderFpsLimit->value()); { KisConfig cfg2(true); cfg2.setEnableOpenGLFramerateLogging(chkOpenGLFramerateLogging->isChecked()); cfg2.setEnableBrushSpeedLogging(chkBrushSpeedLogging->isChecked()); cfg2.setEnableAmdVectorizationWorkaround(chkDisableVectorOptimizations->isChecked()); #ifdef Q_OS_WIN cfg2.setDisableAVXOptimizations(chkDisableAVXOptimizations->isChecked()); #endif cfg2.setCalculateAnimationCacheInBackground(chkBackgroundCacheGeneration->isChecked()); } cfg.setUseOnDiskAnimationCacheSwapping(optOnDisk->isChecked()); cfg.setUseAnimationCacheFrameSizeLimit(chkCachedFramesSizeLimit->isChecked()); cfg.setAnimationCacheFrameSizeLimit(intCachedFramesSizeLimit->value()); cfg.setUseAnimationCacheRegionOfInterest(chkUseRegionOfInterest->isChecked()); cfg.setAnimationCacheRegionOfInterestMargin(intRegionOfInterestMargin->value() / 100.0); } void PerformanceTab::selectSwapDir() { KisImageConfig cfg(true); QString swapDir = cfg.swapDir(); swapDir = QFileDialog::getExistingDirectory(0, i18nc("@title:window", "Select a swap directory"), swapDir); if (swapDir.isEmpty()) { return; } lblSwapFileLocation->setText(swapDir); } void PerformanceTab::slotThreadsLimitChanged(int value) { KisSignalsBlocker b(sliderFrameClonesLimit); sliderFrameClonesLimit->setValue(qMin(m_lastUsedClonesLimit, value)); m_lastUsedThreadsLimit = value; } void PerformanceTab::slotFrameClonesLimitChanged(int value) { KisSignalsBlocker b(sliderThreadsLimit); sliderThreadsLimit->setValue(qMax(m_lastUsedThreadsLimit, value)); m_lastUsedClonesLimit = value; } //--------------------------------------------------------------------------------------------------- #include "KoColor.h" #include "opengl/KisOpenGLModeProber.h" #include "opengl/KisScreenInformationAdapter.h" #include #include QString colorSpaceString(KisSurfaceColorSpace cs, int depth) { const QString csString = #ifdef HAVE_HDR cs == KisSurfaceColorSpace::bt2020PQColorSpace ? "Rec. 2020 PQ" : cs == KisSurfaceColorSpace::scRGBColorSpace ? "Rec. 709 Linear" : #endif cs == KisSurfaceColorSpace::sRGBColorSpace ? "sRGB" : cs == KisSurfaceColorSpace::DefaultColorSpace ? "sRGB" : "Unknown Color Space"; return QString("%1 (%2 bit)").arg(csString).arg(depth); } int formatToIndex(KisConfig::RootSurfaceFormat fmt) { return fmt == KisConfig::BT2020_PQ ? 1 : fmt == KisConfig::BT709_G10 ? 2 : 0; } KisConfig::RootSurfaceFormat indexToFormat(int value) { return value == 1 ? KisConfig::BT2020_PQ : value == 2 ? KisConfig::BT709_G10 : KisConfig::BT709_G22; } DisplaySettingsTab::DisplaySettingsTab(QWidget *parent, const char *name) : WdgDisplaySettings(parent, name) { KisConfig cfg(true); const QString rendererOpenGLText = i18nc("canvas renderer", "OpenGL"); const QString rendererSoftwareText = i18nc("canvas renderer", "Software Renderer (very slow)"); #ifdef Q_OS_WIN const QString rendererOpenGLESText = i18nc("canvas renderer", "Direct3D 11 via ANGLE"); #else const QString rendererOpenGLESText = i18nc("canvas renderer", "OpenGL ES"); #endif const KisOpenGL::OpenGLRenderer renderer = KisOpenGL::getCurrentOpenGLRenderer(); lblCurrentRenderer->setText(renderer == KisOpenGL::RendererOpenGLES ? rendererOpenGLESText : renderer == KisOpenGL::RendererDesktopGL ? rendererOpenGLText : renderer == KisOpenGL::RendererSoftware ? rendererSoftwareText : i18nc("canvas renderer", "Unknown")); cmbPreferredRenderer->clear(); const KisOpenGL::OpenGLRenderers supportedRenderers = KisOpenGL::getSupportedOpenGLRenderers(); const bool onlyOneRendererSupported = supportedRenderers == KisOpenGL::RendererDesktopGL || supportedRenderers == KisOpenGL::RendererOpenGLES || supportedRenderers == KisOpenGL::RendererSoftware; if (!onlyOneRendererSupported) { QString qtPreferredRendererText; if (KisOpenGL::getQtPreferredOpenGLRenderer() == KisOpenGL::RendererOpenGLES) { qtPreferredRendererText = rendererOpenGLESText; } else if (KisOpenGL::getQtPreferredOpenGLRenderer() == KisOpenGL::RendererSoftware) { qtPreferredRendererText = rendererSoftwareText; } else { qtPreferredRendererText = rendererOpenGLText; } cmbPreferredRenderer->addItem(i18nc("canvas renderer", "Auto (%1)", qtPreferredRendererText), KisOpenGL::RendererAuto); cmbPreferredRenderer->setCurrentIndex(0); } else { cmbPreferredRenderer->setEnabled(false); } if (supportedRenderers & KisOpenGL::RendererDesktopGL) { cmbPreferredRenderer->addItem(rendererOpenGLText, KisOpenGL::RendererDesktopGL); if (KisOpenGL::getUserPreferredOpenGLRendererConfig() == KisOpenGL::RendererDesktopGL) { cmbPreferredRenderer->setCurrentIndex(cmbPreferredRenderer->count() - 1); } } #ifdef Q_OS_ANDROID if (onlyOneRendererSupported) { if (KisOpenGL::getQtPreferredOpenGLRenderer() == KisOpenGL::RendererOpenGLES) { cmbPreferredRenderer->addItem(rendererOpenGLESText, KisOpenGL::RendererOpenGLES); cmbPreferredRenderer->setCurrentIndex(0); } } #endif #ifdef Q_OS_WIN if (supportedRenderers & KisOpenGL::RendererOpenGLES) { cmbPreferredRenderer->addItem(rendererOpenGLESText, KisOpenGL::RendererOpenGLES); if (KisOpenGL::getUserPreferredOpenGLRendererConfig() == KisOpenGL::RendererOpenGLES) { cmbPreferredRenderer->setCurrentIndex(cmbPreferredRenderer->count() - 1); } } if (supportedRenderers & KisOpenGL::RendererSoftware) { cmbPreferredRenderer->addItem(rendererSoftwareText, KisOpenGL::RendererSoftware); if (KisOpenGL::getUserPreferredOpenGLRendererConfig() == KisOpenGL::RendererSoftware) { cmbPreferredRenderer->setCurrentIndex(cmbPreferredRenderer->count() - 1); } } #endif if (!(supportedRenderers & (KisOpenGL::RendererDesktopGL | KisOpenGL::RendererOpenGLES | KisOpenGL::RendererSoftware))) { grpOpenGL->setEnabled(false); grpOpenGL->setChecked(false); chkUseTextureBuffer->setEnabled(false); chkDisableVsync->setEnabled(false); cmbFilterMode->setEnabled(false); } else { grpOpenGL->setEnabled(true); grpOpenGL->setChecked(cfg.useOpenGL()); chkUseTextureBuffer->setEnabled(cfg.useOpenGL()); chkUseTextureBuffer->setChecked(cfg.useOpenGLTextureBuffer()); chkDisableVsync->setVisible(cfg.showAdvancedOpenGLSettings()); chkDisableVsync->setEnabled(cfg.useOpenGL()); chkDisableVsync->setChecked(cfg.disableVSync()); cmbFilterMode->setEnabled(cfg.useOpenGL()); cmbFilterMode->setCurrentIndex(cfg.openGLFilteringMode()); // Don't show the high quality filtering mode if it's not available if (!KisOpenGL::supportsLoD()) { cmbFilterMode->removeItem(3); } } lblCurrentDisplayFormat->setText(""); lblCurrentRootSurfaceFormat->setText(""); lblHDRWarning->setText(""); cmbPreferedRootSurfaceFormat->addItem(colorSpaceString(KisSurfaceColorSpace::sRGBColorSpace, 8)); #ifdef HAVE_HDR cmbPreferedRootSurfaceFormat->addItem(colorSpaceString(KisSurfaceColorSpace::bt2020PQColorSpace, 10)); cmbPreferedRootSurfaceFormat->addItem(colorSpaceString(KisSurfaceColorSpace::scRGBColorSpace, 16)); #endif cmbPreferedRootSurfaceFormat->setCurrentIndex(formatToIndex(KisConfig::BT709_G22)); slotPreferredSurfaceFormatChanged(cmbPreferedRootSurfaceFormat->currentIndex()); QOpenGLContext *context = QOpenGLContext::currentContext(); if (!context) { context = QOpenGLContext::globalShareContext(); } if (context) { #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) QScreen *screen = QGuiApplication::screenAt(rect().center()); #else QScreen *screen = 0; #endif KisScreenInformationAdapter adapter(context); if (screen && adapter.isValid()) { KisScreenInformationAdapter::ScreenInfo info = adapter.infoForScreen(screen); if (info.isValid()) { QStringList toolTip; toolTip << i18n("Display Id: %1", info.screen->name()); toolTip << i18n("Display Name: %1 %2", info.screen->manufacturer(), info.screen->model()); toolTip << i18n("Min Luminance: %1", info.minLuminance); toolTip << i18n("Max Luminance: %1", info.maxLuminance); toolTip << i18n("Max Full Frame Luminance: %1", info.maxFullFrameLuminance); toolTip << i18n("Red Primary: %1, %2", info.redPrimary[0], info.redPrimary[1]); toolTip << i18n("Green Primary: %1, %2", info.greenPrimary[0], info.greenPrimary[1]); toolTip << i18n("Blue Primary: %1, %2", info.bluePrimary[0], info.bluePrimary[1]); toolTip << i18n("White Point: %1, %2", info.whitePoint[0], info.whitePoint[1]); lblCurrentDisplayFormat->setToolTip(toolTip.join('\n')); lblCurrentDisplayFormat->setText(colorSpaceString(info.colorSpace, info.bitsPerColor)); } else { lblCurrentDisplayFormat->setToolTip(""); lblCurrentDisplayFormat->setText(i18n("Unknown")); } } else { lblCurrentDisplayFormat->setToolTip(""); lblCurrentDisplayFormat->setText(i18n("Unknown")); qWarning() << "Failed to fetch display info:" << adapter.errorString(); } const QSurfaceFormat currentFormat = KisOpenGLModeProber::instance()->surfaceformatInUse(); #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) KisSurfaceColorSpace colorSpace = currentFormat.colorSpace(); #else KisSurfaceColorSpace colorSpace = KisSurfaceColorSpace::DefaultColorSpace; #endif lblCurrentRootSurfaceFormat->setText(colorSpaceString(colorSpace, currentFormat.redBufferSize())); cmbPreferedRootSurfaceFormat->setCurrentIndex(formatToIndex(cfg.rootSurfaceFormat())); connect(cmbPreferedRootSurfaceFormat, SIGNAL(currentIndexChanged(int)), SLOT(slotPreferredSurfaceFormatChanged(int))); slotPreferredSurfaceFormatChanged(cmbPreferedRootSurfaceFormat->currentIndex()); } #ifndef HAVE_HDR grpHDRSettings->setVisible(false); tabWidget->removeTab(tabWidget->indexOf(tabHDR)); #endif const QStringList openglWarnings = KisOpenGL::getOpenGLWarnings(); if (openglWarnings.isEmpty()) { lblOpenGLWarnings->setVisible(false); } else { QString text(" "); text.append(i18n("Warning(s):")); text.append("
    "); Q_FOREACH (const QString &warning, openglWarnings) { text.append("
  • "); text.append(warning.toHtmlEscaped()); text.append("
  • "); } text.append("
"); lblOpenGLWarnings->setText(text); lblOpenGLWarnings->setVisible(true); } if (qApp->applicationName() == "kritasketch" || qApp->applicationName() == "kritagemini") { grpOpenGL->setVisible(false); grpOpenGL->setMaximumHeight(0); } KisImageConfig imageCfg(false); KoColor c; c.fromQColor(imageCfg.selectionOverlayMaskColor()); c.setOpacity(1.0); btnSelectionOverlayColor->setColor(c); sldSelectionOverlayOpacity->setRange(0.0, 1.0, 2); sldSelectionOverlayOpacity->setSingleStep(0.05); sldSelectionOverlayOpacity->setValue(imageCfg.selectionOverlayMaskColor().alphaF()); intCheckSize->setValue(cfg.checkSize()); chkMoving->setChecked(cfg.scrollCheckers()); KoColor ck1(KoColorSpaceRegistry::instance()->rgb8()); ck1.fromQColor(cfg.checkersColor1()); colorChecks1->setColor(ck1); KoColor ck2(KoColorSpaceRegistry::instance()->rgb8()); ck2.fromQColor(cfg.checkersColor2()); colorChecks2->setColor(ck2); KoColor cb(KoColorSpaceRegistry::instance()->rgb8()); cb.fromQColor(cfg.canvasBorderColor()); canvasBorder->setColor(cb); hideScrollbars->setChecked(cfg.hideScrollbars()); chkCurveAntialiasing->setChecked(cfg.antialiasCurves()); chkSelectionOutlineAntialiasing->setChecked(cfg.antialiasSelectionOutline()); chkChannelsAsColor->setChecked(cfg.showSingleChannelAsColor()); chkHidePopups->setChecked(cfg.hidePopups()); connect(grpOpenGL, SIGNAL(toggled(bool)), SLOT(slotUseOpenGLToggled(bool))); KoColor gridColor(KoColorSpaceRegistry::instance()->rgb8()); gridColor.fromQColor(cfg.getPixelGridColor()); pixelGridColorButton->setColor(gridColor); pixelGridDrawingThresholdBox->setValue(cfg.getPixelGridDrawingThreshold() * 100); } void DisplaySettingsTab::setDefault() { KisConfig cfg(true); cmbPreferredRenderer->setCurrentIndex(0); if (!(KisOpenGL::getSupportedOpenGLRenderers() & (KisOpenGL::RendererDesktopGL | KisOpenGL::RendererOpenGLES))) { grpOpenGL->setEnabled(false); grpOpenGL->setChecked(false); chkUseTextureBuffer->setEnabled(false); chkDisableVsync->setEnabled(false); cmbFilterMode->setEnabled(false); } else { grpOpenGL->setEnabled(true); grpOpenGL->setChecked(cfg.useOpenGL(true)); chkUseTextureBuffer->setChecked(cfg.useOpenGLTextureBuffer(true)); chkUseTextureBuffer->setEnabled(true); chkDisableVsync->setEnabled(true); chkDisableVsync->setChecked(cfg.disableVSync(true)); cmbFilterMode->setEnabled(true); cmbFilterMode->setCurrentIndex(cfg.openGLFilteringMode(true)); } chkMoving->setChecked(cfg.scrollCheckers(true)); KisImageConfig imageCfg(false); KoColor c; c.fromQColor(imageCfg.selectionOverlayMaskColor(true)); c.setOpacity(1.0); btnSelectionOverlayColor->setColor(c); sldSelectionOverlayOpacity->setValue(imageCfg.selectionOverlayMaskColor(true).alphaF()); intCheckSize->setValue(cfg.checkSize(true)); KoColor ck1(KoColorSpaceRegistry::instance()->rgb8()); ck1.fromQColor(cfg.checkersColor1(true)); colorChecks1->setColor(ck1); KoColor ck2(KoColorSpaceRegistry::instance()->rgb8()); ck2.fromQColor(cfg.checkersColor2(true)); colorChecks2->setColor(ck2); KoColor cvb(KoColorSpaceRegistry::instance()->rgb8()); cvb.fromQColor(cfg.canvasBorderColor(true)); canvasBorder->setColor(cvb); hideScrollbars->setChecked(cfg.hideScrollbars(true)); chkCurveAntialiasing->setChecked(cfg.antialiasCurves(true)); chkSelectionOutlineAntialiasing->setChecked(cfg.antialiasSelectionOutline(true)); chkChannelsAsColor->setChecked(cfg.showSingleChannelAsColor(true)); chkHidePopups->setChecked(cfg.hidePopups(true)); KoColor gridColor(KoColorSpaceRegistry::instance()->rgb8()); gridColor.fromQColor(cfg.getPixelGridColor(true)); pixelGridColorButton->setColor(gridColor); pixelGridDrawingThresholdBox->setValue(cfg.getPixelGridDrawingThreshold(true) * 100); cmbPreferedRootSurfaceFormat->setCurrentIndex(formatToIndex(KisConfig::BT709_G22)); slotPreferredSurfaceFormatChanged(cmbPreferedRootSurfaceFormat->currentIndex()); } void DisplaySettingsTab::slotUseOpenGLToggled(bool isChecked) { chkUseTextureBuffer->setEnabled(isChecked); chkDisableVsync->setEnabled(isChecked); cmbFilterMode->setEnabled(isChecked); } void DisplaySettingsTab::slotPreferredSurfaceFormatChanged(int index) { Q_UNUSED(index); QOpenGLContext *context = QOpenGLContext::currentContext(); if (context) { #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) QScreen *screen = QGuiApplication::screenAt(rect().center()); #else QScreen *screen = 0; #endif KisScreenInformationAdapter adapter(context); if (adapter.isValid()) { KisScreenInformationAdapter::ScreenInfo info = adapter.infoForScreen(screen); if (info.isValid()) { if (cmbPreferedRootSurfaceFormat->currentIndex() != formatToIndex(KisConfig::BT709_G22) && info.colorSpace == KisSurfaceColorSpace::sRGBColorSpace) { lblHDRWarning->setText(i18n("WARNING: current display doesn't support HDR rendering")); } else { lblHDRWarning->setText(""); } } } } } //--------------------------------------------------------------------------------------------------- FullscreenSettingsTab::FullscreenSettingsTab(QWidget* parent) : WdgFullscreenSettingsBase(parent) { KisConfig cfg(true); chkDockers->setChecked(cfg.hideDockersFullscreen()); chkMenu->setChecked(cfg.hideMenuFullscreen()); chkScrollbars->setChecked(cfg.hideScrollbarsFullscreen()); chkStatusbar->setChecked(cfg.hideStatusbarFullscreen()); chkTitlebar->setChecked(cfg.hideTitlebarFullscreen()); chkToolbar->setChecked(cfg.hideToolbarFullscreen()); } void FullscreenSettingsTab::setDefault() { KisConfig cfg(true); chkDockers->setChecked(cfg.hideDockersFullscreen(true)); chkMenu->setChecked(cfg.hideMenuFullscreen(true)); chkScrollbars->setChecked(cfg.hideScrollbarsFullscreen(true)); chkStatusbar->setChecked(cfg.hideStatusbarFullscreen(true)); chkTitlebar->setChecked(cfg.hideTitlebarFullscreen(true)); chkToolbar->setChecked(cfg.hideToolbarFullscreen(true)); } //--------------------------------------------------------------------------------------------------- KisDlgPreferences::KisDlgPreferences(QWidget* parent, const char* name) : KPageDialog(parent) { Q_UNUSED(name); setWindowTitle(i18n("Configure Krita")); setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::RestoreDefaults); setFaceType(KPageDialog::List); // General KoVBox *vbox = new KoVBox(); KPageWidgetItem *page = new KPageWidgetItem(vbox, i18n("General")); page->setObjectName("general"); page->setHeader(i18n("General")); page->setIcon(KisIconUtils::loadIcon("go-home")); m_pages << page; addPage(page); m_general = new GeneralTab(vbox); // Shortcuts vbox = new KoVBox(); page = new KPageWidgetItem(vbox, i18n("Keyboard Shortcuts")); page->setObjectName("shortcuts"); page->setHeader(i18n("Shortcuts")); page->setIcon(KisIconUtils::loadIcon("document-export")); m_pages << page; addPage(page); m_shortcutSettings = new ShortcutSettingsTab(vbox); connect(this, SIGNAL(accepted()), m_shortcutSettings, SLOT(saveChanges())); connect(this, SIGNAL(rejected()), m_shortcutSettings, SLOT(cancelChanges())); // Canvas input settings m_inputConfiguration = new KisInputConfigurationPage(); page = addPage(m_inputConfiguration, i18n("Canvas Input Settings")); page->setHeader(i18n("Canvas Input")); page->setObjectName("canvasinput"); page->setIcon(KisIconUtils::loadIcon("configure")); m_pages << page; // Display vbox = new KoVBox(); page = new KPageWidgetItem(vbox, i18n("Display")); page->setObjectName("display"); page->setHeader(i18n("Display")); page->setIcon(KisIconUtils::loadIcon("preferences-desktop-display")); m_pages << page; addPage(page); m_displaySettings = new DisplaySettingsTab(vbox); // Color vbox = new KoVBox(); page = new KPageWidgetItem(vbox, i18n("Color Management")); page->setObjectName("colormanagement"); page->setHeader(i18n("Color")); page->setIcon(KisIconUtils::loadIcon("preferences-desktop-color")); m_pages << page; addPage(page); m_colorSettings = new ColorSettingsTab(vbox); // Performance vbox = new KoVBox(); page = new KPageWidgetItem(vbox, i18n("Performance")); page->setObjectName("performance"); page->setHeader(i18n("Performance")); page->setIcon(KisIconUtils::loadIcon("applications-system")); m_pages << page; addPage(page); m_performanceSettings = new PerformanceTab(vbox); // Tablet vbox = new KoVBox(); page = new KPageWidgetItem(vbox, i18n("Tablet settings")); page->setObjectName("tablet"); page->setHeader(i18n("Tablet")); page->setIcon(KisIconUtils::loadIcon("document-edit")); m_pages << page; addPage(page); m_tabletSettings = new TabletSettingsTab(vbox); // full-screen mode vbox = new KoVBox(); page = new KPageWidgetItem(vbox, i18n("Canvas-only settings")); page->setObjectName("canvasonly"); page->setHeader(i18n("Canvas-only")); page->setIcon(KisIconUtils::loadIcon("folder-pictures")); m_pages << page; addPage(page); m_fullscreenSettings = new FullscreenSettingsTab(vbox); // Author profiles m_authorPage = new KoConfigAuthorPage(); page = addPage(m_authorPage, i18nc("@title:tab Author page", "Author" )); page->setObjectName("author"); page->setHeader(i18n("Author")); page->setIcon(KisIconUtils::loadIcon("im-user")); m_pages << page; QPushButton *restoreDefaultsButton = button(QDialogButtonBox::RestoreDefaults); restoreDefaultsButton->setText(i18nc("@action:button", "Restore Defaults")); connect(this, SIGNAL(accepted()), m_inputConfiguration, SLOT(saveChanges())); connect(this, SIGNAL(rejected()), m_inputConfiguration, SLOT(revertChanges())); KisPreferenceSetRegistry *preferenceSetRegistry = KisPreferenceSetRegistry::instance(); QStringList keys = preferenceSetRegistry->keys(); keys.sort(); Q_FOREACH(const QString &key, keys) { KisAbstractPreferenceSetFactory *preferenceSetFactory = preferenceSetRegistry->value(key); KisPreferenceSet* preferenceSet = preferenceSetFactory->createPreferenceSet(); vbox = new KoVBox(); page = new KPageWidgetItem(vbox, preferenceSet->name()); page->setHeader(preferenceSet->header()); page->setIcon(preferenceSet->icon()); addPage(page); preferenceSet->setParent(vbox); preferenceSet->loadPreferences(); connect(restoreDefaultsButton, SIGNAL(clicked(bool)), preferenceSet, SLOT(loadDefaultPreferences()), Qt::UniqueConnection); connect(this, SIGNAL(accepted()), preferenceSet, SLOT(savePreferences()), Qt::UniqueConnection); } connect(restoreDefaultsButton, SIGNAL(clicked(bool)), this, SLOT(slotDefault())); KisConfig cfg(true); QString currentPageName = cfg.readEntry("KisDlgPreferences/CurrentPage"); Q_FOREACH(KPageWidgetItem *page, m_pages) { if (page->objectName() == currentPageName) { setCurrentPage(page); break; } } } KisDlgPreferences::~KisDlgPreferences() { KisConfig cfg(true); cfg.writeEntry("KisDlgPreferences/CurrentPage", currentPage()->objectName()); } void KisDlgPreferences::showEvent(QShowEvent *event){ KPageDialog::showEvent(event); button(QDialogButtonBox::Cancel)->setAutoDefault(false); button(QDialogButtonBox::Ok)->setAutoDefault(false); button(QDialogButtonBox::RestoreDefaults)->setAutoDefault(false); button(QDialogButtonBox::Cancel)->setDefault(false); button(QDialogButtonBox::Ok)->setDefault(false); button(QDialogButtonBox::RestoreDefaults)->setDefault(false); } void KisDlgPreferences::slotButtonClicked(QAbstractButton *button) { if (buttonBox()->buttonRole(button) == QDialogButtonBox::RejectRole) { m_cancelClicked = true; } } void KisDlgPreferences::slotDefault() { if (currentPage()->objectName() == "general") { m_general->setDefault(); } else if (currentPage()->objectName() == "shortcuts") { m_shortcutSettings->setDefault(); } else if (currentPage()->objectName() == "display") { m_displaySettings->setDefault(); } else if (currentPage()->objectName() == "colormanagement") { m_colorSettings->setDefault(); } else if (currentPage()->objectName() == "performance") { m_performanceSettings->load(true); } else if (currentPage()->objectName() == "tablet") { m_tabletSettings->setDefault(); } else if (currentPage()->objectName() == "canvasonly") { m_fullscreenSettings->setDefault(); } else if (currentPage()->objectName() == "canvasinput") { m_inputConfiguration->setDefaults(); } } bool KisDlgPreferences::editPreferences() { connect(this->buttonBox(), SIGNAL(clicked(QAbstractButton*)), this, SLOT(slotButtonClicked(QAbstractButton*))); int retval = exec(); Q_UNUSED(retval) if (!m_cancelClicked) { // General settings KisConfig cfg(false); cfg.setNewCursorStyle(m_general->cursorStyle()); cfg.setNewOutlineStyle(m_general->outlineStyle()); cfg.setShowRootLayer(m_general->showRootLayer()); cfg.setShowOutlineWhilePainting(m_general->showOutlineWhilePainting()); cfg.setForceAlwaysFullSizedOutline(!m_general->m_changeBrushOutline->isChecked()); cfg.setSessionOnStartup(m_general->sessionOnStartup()); cfg.setSaveSessionOnQuit(m_general->saveSessionOnQuit()); KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); group.writeEntry("DontUseNativeFileDialog", !m_general->m_chkNativeFileDialog->isChecked()); cfg.writeEntry("maximumBrushSize", m_general->intMaxBrushSize->value()); cfg.writeEntry("mdi_viewmode", m_general->mdiMode()); cfg.setMDIBackgroundColor(m_general->m_mdiColor->color().toXML()); cfg.setMDIBackgroundImage(m_general->m_backgroundimage->text()); cfg.setAutoSaveInterval(m_general->autoSaveInterval()); cfg.writeEntry("autosavefileshidden", m_general->chkHideAutosaveFiles->isChecked()); cfg.setBackupFile(m_general->m_backupFileCheckBox->isChecked()); cfg.writeEntry("backupfilelocation", m_general->cmbBackupFileLocation->currentIndex()); cfg.writeEntry("backupfilesuffix", m_general->txtBackupFileSuffix->text()); cfg.writeEntry("numberofbackupfiles", m_general->intNumBackupFiles->value()); + cfg.setShowCanvasMessages(m_general->showCanvasMessages()); cfg.setCompressKra(m_general->compressKra()); cfg.setTrimKra(m_general->trimKra()); cfg.setUseZip64(m_general->useZip64()); const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); kritarc.setValue("EnableHiDPI", m_general->m_chkHiDPI->isChecked()); #ifdef HAVE_HIGH_DPI_SCALE_FACTOR_ROUNDING_POLICY kritarc.setValue("EnableHiDPIFractionalScaling", m_general->m_chkHiDPIFractionalScaling->isChecked()); #endif kritarc.setValue("LogUsage", m_general->chkUsageLogging->isChecked()); cfg.setToolOptionsInDocker(m_general->toolOptionsInDocker()); cfg.writeEntry("useCreamyAlphaDarken", (bool)!m_general->cmbFlowMode->currentIndex()); cfg.setKineticScrollingEnabled(m_general->kineticScrollingEnabled()); cfg.setKineticScrollingGesture(m_general->kineticScrollingGesture()); cfg.setKineticScrollingSensitivity(m_general->kineticScrollingSensitivity()); cfg.setKineticScrollingHideScrollbars(m_general->kineticScrollingHiddenScrollbars()); cfg.setSwitchSelectionCtrlAlt(m_general->switchSelectionCtrlAlt()); cfg.setDisableTouchOnCanvas(!m_general->chkEnableTouch->isChecked()); cfg.setDisableTouchRotation(!m_general->chkEnableTouchRotation->isChecked()); cfg.setActivateTransformToolAfterPaste(m_general->chkEnableTranformToolAfterPaste->isChecked()); cfg.setConvertToImageColorspaceOnImport(m_general->convertToImageColorspaceOnImport()); cfg.setUndoStackLimit(m_general->undoStackSize()); cfg.setFavoritePresets(m_general->favoritePresets()); cfg.setAutoPinLayersToTimeline(m_general->autopinLayersToTimeline()); cfg.writeEntry(KisResourceCacheDb::dbLocationKey, m_general->m_urlCacheDbLocation->fileName()); cfg.writeEntry(KisResourceLocator::resourceLocationKey, m_general->m_urlResourceFolder->fileName()); // Color settings cfg.setUseSystemMonitorProfile(m_colorSettings->m_page->chkUseSystemMonitorProfile->isChecked()); for (int i = 0; i < QApplication::screens().count(); ++i) { if (m_colorSettings->m_page->chkUseSystemMonitorProfile->isChecked()) { int currentIndex = m_colorSettings->m_monitorProfileWidgets[i]->currentIndex(); QString monitorid = m_colorSettings->m_monitorProfileWidgets[i]->itemData(currentIndex).toString(); cfg.setMonitorForScreen(i, monitorid); } else { cfg.setMonitorProfile(i, m_colorSettings->m_monitorProfileWidgets[i]->currentUnsqueezedText(), m_colorSettings->m_page->chkUseSystemMonitorProfile->isChecked()); } } cfg.setWorkingColorSpace(m_colorSettings->m_page->cmbWorkingColorSpace->currentItem().id()); KisImageConfig cfgImage(false); cfgImage.setDefaultProofingConfig(m_colorSettings->m_page->proofingSpaceSelector->currentColorSpace(), m_colorSettings->m_page->cmbProofingIntent->currentIndex(), m_colorSettings->m_page->ckbProofBlackPoint->isChecked(), m_colorSettings->m_page->gamutAlarm->color(), (double)m_colorSettings->m_page->sldAdaptationState->value()/20); cfg.setUseBlackPointCompensation(m_colorSettings->m_page->chkBlackpoint->isChecked()); cfg.setAllowLCMSOptimization(m_colorSettings->m_page->chkAllowLCMSOptimization->isChecked()); cfg.setForcePaletteColors(m_colorSettings->m_page->chkForcePaletteColor->isChecked()); cfg.setPasteBehaviour(m_colorSettings->m_pasteBehaviourGroup.checkedId()); cfg.setRenderIntent(m_colorSettings->m_page->cmbMonitorIntent->currentIndex()); // Tablet settings cfg.setPressureTabletCurve( m_tabletSettings->m_page->pressureCurve->curve().toString() ); cfg.setUseRightMiddleTabletButtonWorkaround( m_tabletSettings->m_page->chkUseRightMiddleClickWorkaround->isChecked()); #if defined Q_OS_WIN && (!defined USE_QT_TABLET_WINDOWS || defined QT_HAS_WINTAB_SWITCH) #ifdef USE_QT_TABLET_WINDOWS // ask Qt if WinInk is actually available const bool isWinInkAvailable = true; #else const bool isWinInkAvailable = KisTabletSupportWin8::isAvailable(); #endif if (isWinInkAvailable) { cfg.setUseWin8PointerInput(m_tabletSettings->m_page->radioWin8PointerInput->isChecked()); } #endif m_performanceSettings->save(); if (!cfg.useOpenGL() && m_displaySettings->grpOpenGL->isChecked()) cfg.setCanvasState("TRY_OPENGL"); if (m_displaySettings->grpOpenGL->isChecked()) { KisOpenGL::OpenGLRenderer renderer = static_cast( m_displaySettings->cmbPreferredRenderer->itemData( m_displaySettings->cmbPreferredRenderer->currentIndex()).toInt()); KisOpenGL::setUserPreferredOpenGLRendererConfig(renderer); } else { KisOpenGL::setUserPreferredOpenGLRendererConfig(KisOpenGL::RendererNone); } cfg.setUseOpenGLTextureBuffer(m_displaySettings->chkUseTextureBuffer->isChecked()); cfg.setOpenGLFilteringMode(m_displaySettings->cmbFilterMode->currentIndex()); cfg.setDisableVSync(m_displaySettings->chkDisableVsync->isChecked()); cfg.setRootSurfaceFormat(&kritarc, indexToFormat(m_displaySettings->cmbPreferedRootSurfaceFormat->currentIndex())); cfg.setCheckSize(m_displaySettings->intCheckSize->value()); cfg.setScrollingCheckers(m_displaySettings->chkMoving->isChecked()); cfg.setCheckersColor1(m_displaySettings->colorChecks1->color().toQColor()); cfg.setCheckersColor2(m_displaySettings->colorChecks2->color().toQColor()); cfg.setCanvasBorderColor(m_displaySettings->canvasBorder->color().toQColor()); cfg.setHideScrollbars(m_displaySettings->hideScrollbars->isChecked()); KoColor c = m_displaySettings->btnSelectionOverlayColor->color(); c.setOpacity(m_displaySettings->sldSelectionOverlayOpacity->value()); cfgImage.setSelectionOverlayMaskColor(c.toQColor()); cfg.setAntialiasCurves(m_displaySettings->chkCurveAntialiasing->isChecked()); cfg.setAntialiasSelectionOutline(m_displaySettings->chkSelectionOutlineAntialiasing->isChecked()); cfg.setShowSingleChannelAsColor(m_displaySettings->chkChannelsAsColor->isChecked()); cfg.setHidePopups(m_displaySettings->chkHidePopups->isChecked()); cfg.setHideDockersFullscreen(m_fullscreenSettings->chkDockers->checkState()); cfg.setHideMenuFullscreen(m_fullscreenSettings->chkMenu->checkState()); cfg.setHideScrollbarsFullscreen(m_fullscreenSettings->chkScrollbars->checkState()); cfg.setHideStatusbarFullscreen(m_fullscreenSettings->chkStatusbar->checkState()); cfg.setHideTitlebarFullscreen(m_fullscreenSettings->chkTitlebar->checkState()); cfg.setHideToolbarFullscreen(m_fullscreenSettings->chkToolbar->checkState()); cfg.setCursorMainColor(m_general->cursorColorBtutton->color().toQColor()); cfg.setPixelGridColor(m_displaySettings->pixelGridColorButton->color().toQColor()); cfg.setPixelGridDrawingThreshold(m_displaySettings->pixelGridDrawingThresholdBox->value() / 100); m_authorPage->apply(); cfg.logImportantSettings(); } return !m_cancelClicked; } diff --git a/libs/ui/flake/kis_shape_controller.cpp b/libs/ui/flake/kis_shape_controller.cpp index a399741b84..19ed55bab7 100644 --- a/libs/ui/flake/kis_shape_controller.cpp +++ b/libs/ui/flake/kis_shape_controller.cpp @@ -1,283 +1,267 @@ /* * 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" +#include "KoAddRemoveShapeCommands.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) +KoShapeContainer *KisShapeController::createParentForShapes(const QList shapes, KUndo2Command *parentCommand) { - KIS_SAFE_ASSERT_RECOVER_RETURN(!shapes.isEmpty()); + KoShapeContainer *resultParent = 0; + KisCommandUtils::CompositeCommand *resultCommand = + new KisCommandUtils::CompositeCommand(parentCommand); + + KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!shapes.isEmpty(), resultParent); + Q_FOREACH (KoShape *shape, shapes) { + KIS_SAFE_ASSERT_RECOVER_BREAK(!shape->parent()); + } KisCanvas2 *canvas = dynamic_cast(KoToolManager::instance()->activeCanvasController()->canvas()); - KIS_SAFE_ASSERT_RECOVER_RETURN(canvas); + KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(canvas, resultParent); - 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); + KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!baseBelongsToSelection || allSameBelongsToShapeSelection, resultParent); - 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()); + if (baseBelongsToSelection && allSameBelongsToShapeSelection) { + KisSelectionSP selection = canvas->viewManager()->selection(); + if (selection) { + KisSelectionComponent* shapeSelectionComponent = selection->shapeSelection(); - Q_FOREACH(KoShape *shape, shapes) { - shapeSelection->addShape(shape); - } + if (!shapeSelectionComponent) { + shapeSelectionComponent = new KisShapeSelection(this, image(), selection); + resultCommand->addCommand(selection->convertToVectorSelection(shapeSelectionComponent)); } - } else { - KisShapeLayer *shapeLayer = + + KisShapeSelection * shapeSelection = static_cast(shapeSelectionComponent); + resultParent = shapeSelection; + } + } 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); + 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())); - } - - Q_FOREACH(KoShape *shape, shapes) { - shapeLayer->addShape(shape); - } + resultCommand->addCommand( + new KisImageLayerAddCommand(image(), + shapeLayer, + image()->rootLayer(), + image()->rootLayer()->childCount())); } - } - - 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); + resultParent = shapeLayer; + } - 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); + return resultParent; } 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_controller.h b/libs/ui/flake/kis_shape_controller.h index 296f714fa2..6c3c4ca388 100644 --- a/libs/ui/flake/kis_shape_controller.h +++ b/libs/ui/flake/kis_shape_controller.h @@ -1,93 +1,92 @@ /* * 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_CONTROLLER #define KIS_SHAPE_CONTROLLER #include #include "kis_dummies_facade_base.h" #include class KisNodeDummy; class KoShapeLayer; class KisCanvas2; class KisDocument; class KisNameServer; /** * KisShapeController keeps track of new layers, shapes, masks and * selections -- everything that needs to be wrapped as a shape for * the tools to work on. */ class KRITAUI_EXPORT KisShapeController : public KisDummiesFacadeBase, public KoShapeControllerBase { Q_OBJECT public: KisShapeController(KisDocument *doc, KisNameServer *nameServer); ~KisShapeController() override; bool hasDummyForNode(KisNodeSP node) const override; KisNodeDummy* dummyForNode(KisNodeSP layer) const override; KisNodeDummy* rootDummy() const override; int dummiesCount() const override; KoShapeLayer* shapeForNode(KisNodeSP layer) const; void setInitialShapeForCanvas(KisCanvas2 *canvas); void setImage(KisImageWSP image) override; private: void addNodeImpl(KisNodeSP node, KisNodeSP parent, KisNodeSP aboveThis) override; void removeNodeImpl(KisNodeSP node) override; private Q_SLOTS: void slotUpdateDocumentResolution(); void slotUpdateDocumentSize(); Q_SIGNALS: /** * These three signals are forwarded from the local shape manager of * KisShapeLayer. This is done because we switch KoShapeManager and * therefore KoSelection in KisCanvas2, so we need to connect local * managers to the UI as well. * * \see comment in the constructor of KisCanvas2 */ void selectionChanged(); void selectionContentChanged(); void currentLayerChanged(const KoShapeLayer*); public: - void addShapes(const QList shapes) override; - void removeShape(KoShape* shape) override; + KoShapeContainer* createParentForShapes(const QList shapes, KUndo2Command *parentCommand) override; QRectF documentRectInPixels() const override; qreal pixelsPerInch() const override; private: struct Private; Private * const m_d; }; #endif diff --git a/libs/ui/flake/kis_shape_layer.cc b/libs/ui/flake/kis_shape_layer.cc index 35ebf2f84a..7c74e3977d 100644 --- a/libs/ui/flake/kis_shape_layer.cc +++ b/libs/ui/flake/kis_shape_layer.cc @@ -1,687 +1,682 @@ /* * 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 "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 "kis_do_something_command.h" #include #include #include #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(); child->applyAbsoluteTransformation(parentTransform.inverted()); } } void remove(KoShape *child) override { KIS_SAFE_ASSERT_RECOVER_NOOP(inheritsTransform(child)); if (inheritsTransform(child)) { QTransform parentTransform = q->absoluteTransformation(); 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().inverted(); 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() * thisInvertedTransform); addShape(clonedShape); } 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().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() * 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() * 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))); + + ShapeLayerContainerModel *model = dynamic_cast(this->model()); + KIS_SAFE_ASSERT_RECOVER_RETURN(model); + model->setAssociatedRootShapeManager(m_d->canvas->shapeManager()); } 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(); } void KisShapeLayer::slotTransformShapes(const QTransform &newTransform) { KoShapeTransformCommand cmd({this}, {transformation()}, {newTransform}); 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; } return false; } 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); } class TransformShapeLayerDeferred : public KUndo2Command { public: TransformShapeLayerDeferred(KisShapeLayer *shapeLayer, const QTransform &globalDocTransform) : m_shapeLayer(shapeLayer), m_globalDocTransform(globalDocTransform), m_blockingConnection(std::bind(&KisShapeLayer::slotTransformShapes, shapeLayer, std::placeholders::_1)) { } void undo() { KIS_SAFE_ASSERT_RECOVER_NOOP(QThread::currentThread() != qApp->thread()); m_blockingConnection.start(m_savedTransform); } void redo() { m_savedTransform = m_shapeLayer->transformation(); const QTransform globalTransform = m_shapeLayer->absoluteTransformation(); const QTransform localTransform = globalTransform * m_globalDocTransform * globalTransform.inverted(); KIS_SAFE_ASSERT_RECOVER_NOOP(QThread::currentThread() != qApp->thread()); m_blockingConnection.start(localTransform * m_savedTransform); } private: KisShapeLayer *m_shapeLayer; QTransform m_globalDocTransform; QTransform m_savedTransform; KisSafeBlockingQueueConnectionProxy m_blockingConnection; }; KUndo2Command* KisShapeLayer::transform(const QTransform &transform) { QList shapes = shapesToBeTransformed(); if (shapes.isEmpty()) return 0; KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(shapes.size() == 1 && shapes.first() == this, 0); /** * We cannot transform shapes in the worker thread. Therefor we emit blocking-queued * signal to transform them in the GUI thread and then return. */ KisImageViewConverter *converter = dynamic_cast(this->converter()); QTransform docSpaceTransform = converter->documentToView() * transform * converter->viewToDocument(); return new TransformShapeLayerDeferred(this, docSpaceTransform); } KUndo2Command *KisShapeLayer::setProfile(const KoColorProfile *profile) { using namespace KisDoSomethingCommandOps; KUndo2Command *cmd = new KUndo2Command(); new KisDoSomethingCommand(this, false, cmd); m_d->paintDevice->setProfile(profile, cmd); new KisDoSomethingCommand(this, true, cmd); return cmd; } KUndo2Command *KisShapeLayer::convertTo(const KoColorSpace *dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { using namespace KisDoSomethingCommandOps; KUndo2Command *cmd = new KUndo2Command(); new KisDoSomethingCommand(this, false, cmd); m_d->paintDevice->convertTo(dstColorSpace, renderingIntent, conversionFlags, cmd); new KisDoSomethingCommand(this, true, cmd); return cmd; } 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 a0e62c696e..e1f22aa27d 100644 --- a/libs/ui/flake/kis_shape_layer_canvas.cpp +++ b/libs/ui/flake/kis_shape_layer_canvas.cpp @@ -1,461 +1,487 @@ /* * 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" #include "krita_utils.h" 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::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_asyncUpdateSignalCompressor(100, KisSignalCompressor::FIRST_INACTIVE) , m_safeForcedConnection(std::bind(&KisShapeLayerCanvas::slotStartAsyncRepaint, this)) { /** * 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())); 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(); } } 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; } QString debugName() const override { QString result; QDebug dbg(&result); dbg << "KisRepaintShapeLayerLayerJob" << m_layer; return result; } 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) { 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; } } m_asyncUpdateSignalCompressor.start(); m_hasUpdateInCompressor = true; } void KisShapeLayerCanvas::updateCanvas(const QRectF& rc) { updateCanvas(QVector({rc})); } void KisShapeLayerCanvas::slotStartAsyncRepaint() { QRect repaintRect; QRect uncroppedRepaintRect; bool forceUpdateHiddenAreasOnly = false; const qint32 MASK_IMAGE_WIDTH = 256; const qint32 MASK_IMAGE_HEIGHT = 256; { QMutexLocker locker(&m_dirtyRegionMutex); repaintRect = m_dirtyRegion.boundingRect(); forceUpdateHiddenAreasOnly = m_forceUpdateHiddenAreasOnly; /// Since we are going to override the previous jobs, we should fetch /// all the area covered by it. Otherwise we'll get dirty leftovers of /// the layer on the projection Q_FOREACH (const KoShapeManager::PaintJob &job, m_paintJobsOrder.jobs) { repaintRect |= m_viewConverter->documentToView().mapRect(job.docUpdateRect).toAlignedRect(); } m_paintJobsOrder.clear(); 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() uncroppedRepaintRect = repaintRect; repaintRect = repaintRect.intersected(m_parentLayer->image()->bounds()); } else { const QRectF shapesBounds = KoShape::boundingRect(m_shapeManager->shapes()); repaintRect |= kisGrowRect(m_viewConverter->documentToView(shapesBounds).toAlignedRect(), 2); uncroppedRepaintRect = repaintRect; } /** * Vector shapes are not thread-safe against concurrent read-writes, so we * need to utilize rather complicated policy on accessing them: * * 1) All shape writes happen in GUI thread (right in the tools) * 2) No concurrent reads from the shapes may happen in other threads * while the user is modifying them. * * That is why our shape rendering code is split into two parts: * * 1) First we just fetch a shallow copy of the shapes of the layer (it * takes about 1ms for complicated vector layers) and pack them into * KoShapeManager::PaintJobsList jobs. It happens here, in * slotStartAsyncRepaint(), which runs in the GUI thread. It guarantees * that no one is accessing the shapes during the copy operation. * * 2) The rendering itself happens in the worker thread in repaint(). But * repaint() doesn't access original shapes anymore. It accesses only they * shallow copies, which means that there is no concurrent * access to anything (*). * * (*) "no concurrent access to anything" is a rather fragile term :) There * will still be concurrent access to it, on detaching... But(!), when detaching, * the original data is kept unchanged, so "it should be safe enough"(c). Especially * if we guarantee that rendering thread may not cause a detach (?), and the detach * can happen only from a single GUI thread. */ const QVector updateRects = KritaUtils::splitRectIntoPatchesTight(repaintRect, QSize(MASK_IMAGE_WIDTH, MASK_IMAGE_HEIGHT)); KoShapeManager::PaintJobsOrder jobsOrder; Q_FOREACH (const QRect &viewUpdateRect, updateRects) { jobsOrder.jobs << KoShapeManager::PaintJob(m_viewConverter->viewToDocument().mapRect(QRectF(viewUpdateRect)), viewUpdateRect); } jobsOrder.uncroppedViewUpdateRect = uncroppedRepaintRect; m_shapeManager->preparePaintJobs(jobsOrder, m_parentLayer); { QMutexLocker locker(&m_dirtyRegionMutex); // check if it is still empty! It should be true, because GUI thread is // the only actor that can add stuff to it. KIS_SAFE_ASSERT_RECOVER_NOOP(m_paintJobsOrder.isEmpty()); m_paintJobsOrder = jobsOrder; } m_hasUpdateInCompressor = false; m_image->addSpontaneousJob(new KisRepaintShapeLayerLayerJob(m_parentLayer, this)); } void KisShapeLayerCanvas::slotImageSizeChanged() { QRegion dirtyCacheRegion; dirtyCacheRegion += m_image->bounds(); dirtyCacheRegion += m_cachedImageRect; dirtyCacheRegion -= m_image->bounds() & m_cachedImageRect; QVector dirtyRects; auto rc = dirtyCacheRegion.begin(); while (rc != dirtyCacheRegion.end()) { dirtyRects.append(m_viewConverter->viewToDocument(*rc)); rc++; } updateCanvas(dirtyRects); m_cachedImageRect = m_image->bounds(); } void KisShapeLayerCanvas::repaint() { KoShapeManager::PaintJobsOrder paintJobsOrder; { QMutexLocker locker(&m_dirtyRegionMutex); std::swap(paintJobsOrder, m_paintJobsOrder); } /** * Sometimes two update jobs might not override and the second one * will arrive right after the first one */ if (paintJobsOrder.isEmpty()) return; 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); quint8 * dstData = new quint8[MASK_IMAGE_WIDTH * MASK_IMAGE_HEIGHT * m_projection->pixelSize()]; QRect repaintRect = paintJobsOrder.uncroppedViewUpdateRect; m_projection->clear(repaintRect); Q_FOREACH (const KoShapeManager::PaintJob &job, paintJobsOrder.jobs) { if (job.isEmpty()) { m_projection->clear(job.viewUpdateRect); continue; } + KIS_SAFE_ASSERT_RECOVER(job.viewUpdateRect.width() <= MASK_IMAGE_WIDTH && + job.viewUpdateRect.height() <= MASK_IMAGE_HEIGHT) { + continue; + } + image.fill(0); tempPainter.setTransform(QTransform()); - tempPainter.setClipRect(QRect(0,0,MASK_IMAGE_WIDTH,MASK_IMAGE_HEIGHT)); + tempPainter.setClipRect(QRect(0,0,job.viewUpdateRect.width(), job.viewUpdateRect.height())); tempPainter.setTransform(m_viewConverter->documentToView() * QTransform::fromTranslate(-job.viewUpdateRect.x(), -job.viewUpdateRect.y())); m_shapeManager->paintJob(tempPainter, job, false); - KoColorSpaceRegistry::instance()->rgb8() - ->convertPixelsTo(image.constBits(), dstData, m_projection->colorSpace(), - MASK_IMAGE_WIDTH * MASK_IMAGE_HEIGHT, - KoColorConversionTransformation::internalRenderingIntent(), - KoColorConversionTransformation::internalConversionFlags()); - - // TODO: use job.viewUpdateRect instead of MASK_IMAGE_WIDTH/HEIGHT - m_projection->writeBytes(dstData, - job.viewUpdateRect.x(), - job.viewUpdateRect.y(), - MASK_IMAGE_WIDTH, - MASK_IMAGE_HEIGHT); + if (image.size() != job.viewUpdateRect.size()) { + const quint8 *imagePtr = image.constBits(); + const int imageRowStride = 4 * image.width(); + + for (int y = 0; y < job.viewUpdateRect.height(); y++) { + + KoColorSpaceRegistry::instance()->rgb8() + ->convertPixelsTo(imagePtr, dstData, m_projection->colorSpace(), + job.viewUpdateRect.width(), + KoColorConversionTransformation::internalRenderingIntent(), + KoColorConversionTransformation::internalConversionFlags()); + + m_projection->writeBytes(dstData, + job.viewUpdateRect.x(), + job.viewUpdateRect.y() + y, + job.viewUpdateRect.width(), + 1); + + imagePtr += imageRowStride; + } + } else { + KoColorSpaceRegistry::instance()->rgb8() + ->convertPixelsTo(image.constBits(), dstData, m_projection->colorSpace(), + MASK_IMAGE_WIDTH * MASK_IMAGE_HEIGHT, + KoColorConversionTransformation::internalRenderingIntent(), + KoColorConversionTransformation::internalConversionFlags()); + + m_projection->writeBytes(dstData, + job.viewUpdateRect.x(), + job.viewUpdateRect.y(), + MASK_IMAGE_WIDTH, + MASK_IMAGE_HEIGHT); + } repaintRect |= job.viewUpdateRect; } 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(); m_safeForcedConnection.start(); } } bool KisShapeLayerCanvas::hasPendingUpdates() const { return m_hasUpdateInCompressor; } void KisShapeLayerCanvas::forceRepaintWithHiddenAreas() { KIS_SAFE_ASSERT_RECOVER_RETURN(m_parentLayer->image()); KIS_SAFE_ASSERT_RECOVER_RETURN(!m_isDestroying); { QMutexLocker locker(&m_dirtyRegionMutex); m_forceUpdateHiddenAreasOnly = true; } m_asyncUpdateSignalCompressor.stop(); m_safeForcedConnection.start(); } 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(); } diff --git a/libs/ui/flake/kis_shape_selection.cpp b/libs/ui/flake/kis_shape_selection.cpp index c5912433da..633c79c793 100644 --- a/libs/ui/flake/kis_shape_selection.cpp +++ b/libs/ui/flake/kis_shape_selection.cpp @@ -1,338 +1,337 @@ /* * Copyright (c) 2010 Sven Langkamp * Copyright (c) 2011 Jan Hambrecht * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_shape_selection.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_shape_selection_model.h" #include "kis_shape_selection_canvas.h" #include "kis_take_all_shapes_command.h" #include "kis_image_view_converter.h" #include "kis_shape_layer.h" #include KisShapeSelection::KisShapeSelection(KoShapeControllerBase *shapeControllerBase, KisImageWSP image, KisSelectionWSP selection) : KoShapeLayer(m_model = new KisShapeSelectionModel(image, selection, this)) - , m_image(image) - , m_shapeControllerBase(shapeControllerBase) { - Q_ASSERT(m_image); - setShapeId("KisShapeSelection"); - setSelectable(false); - m_converter = new KisImageViewConverter(image); - m_canvas = new KisShapeSelectionCanvas(shapeControllerBase); - m_canvas->shapeManager()->addShape(this); - - m_model->setObjectName("KisShapeSelectionModel"); - m_model->moveToThread(image->thread()); - m_canvas->setObjectName("KisShapeSelectionCanvas"); - m_canvas->moveToThread(image->thread()); - - connect(this, SIGNAL(sigMoveShapes(QPointF)), SLOT(slotMoveShapes(QPointF))); + init(image, shapeControllerBase); } KisShapeSelection::~KisShapeSelection() { m_model->setShapeSelection(0); delete m_canvas; delete m_converter; } KisShapeSelection::KisShapeSelection(const KisShapeSelection& rhs, KisSelection* selection) : KoShapeLayer(m_model = new KisShapeSelectionModel(rhs.m_image, selection, this)) { - m_image = rhs.m_image; - m_shapeControllerBase = rhs.m_shapeControllerBase; - m_converter = new KisImageViewConverter(m_image); - m_canvas = new KisShapeSelectionCanvas(m_shapeControllerBase); + init(rhs.m_image, rhs.m_shapeControllerBase); // TODO: refactor shape selection to pass signals // via KoShapeManager, not via the model m_canvas->shapeManager()->setUpdatesBlocked(true); m_model->setUpdatesEnabled(false); m_canvas->shapeManager()->addShape(this); Q_FOREACH (KoShape *shape, rhs.shapes()) { KoShape *clonedShape = shape->cloneShape(); KIS_SAFE_ASSERT_RECOVER(clonedShape) { continue; } this->addShape(clonedShape); } m_canvas->shapeManager()->setUpdatesBlocked(false); m_model->setUpdatesEnabled(true); } -KisSelectionComponent* KisShapeSelection::clone(KisSelection* selection) +void KisShapeSelection::init(KisImageSP image, KoShapeControllerBase *shapeControllerBase) { - /** - * TODO: make cloning of vector selections safe! Right now it crashes - * on Windows because of manipulations with timers from non-gui thread. - */ - KIS_SAFE_ASSERT_RECOVER_NOOP(QThread::currentThread() == qApp->thread()); + KIS_SAFE_ASSERT_RECOVER_RETURN(image); + KIS_SAFE_ASSERT_RECOVER_RETURN(shapeControllerBase); + m_image = image; + m_shapeControllerBase = shapeControllerBase; + + setShapeId("KisShapeSelection"); + setSelectable(false); + m_converter = new KisImageViewConverter(image); + m_canvas = new KisShapeSelectionCanvas(shapeControllerBase); + m_canvas->shapeManager()->addShape(this); + + m_model->setObjectName("KisShapeSelectionModel"); + m_model->moveToThread(image->thread()); + m_canvas->setObjectName("KisShapeSelectionCanvas"); + m_canvas->moveToThread(image->thread()); + + connect(this, SIGNAL(sigMoveShapes(QPointF)), SLOT(slotMoveShapes(QPointF))); +} + +KisSelectionComponent* KisShapeSelection::clone(KisSelection* selection) +{ return new KisShapeSelection(*this, selection); } bool KisShapeSelection::saveSelection(KoStore * store) const { const QSizeF sizeInPx = m_image->bounds().size(); const QSizeF sizeInPt(sizeInPx.width() / m_image->xRes(), sizeInPx.height() / m_image->yRes()); return KisShapeLayer::saveShapesToStore(store, this->shapes(), sizeInPt); } bool KisShapeSelection::loadSelection(KoStore* store) { QSizeF fragmentSize; // unused! // FIXME: we handle xRes() only! KIS_SAFE_ASSERT_RECOVER_NOOP(qFuzzyCompare(m_image->xRes(), m_image->yRes())); const qreal resolutionPPI = 72.0 * m_image->xRes(); QList shapes; if (store->open("content.svg")) { KoStoreDevice storeDev(store); storeDev.open(QIODevice::ReadOnly); shapes = KisShapeLayer::createShapesFromSvg(&storeDev, "", m_image->bounds(), resolutionPPI, m_canvas->shapeController()->resourceManager(), &fragmentSize); store->close(); Q_FOREACH (KoShape *shape, shapes) { addShape(shape); } return true; } return false; } void KisShapeSelection::setUpdatesEnabled(bool enabled) { m_model->setUpdatesEnabled(enabled); } bool KisShapeSelection::updatesEnabled() const { return m_model->updatesEnabled(); } KUndo2Command* KisShapeSelection::resetToEmpty() { return new KisTakeAllShapesCommand(this, true, false); } bool KisShapeSelection::isEmpty() const { return !m_model->count(); } QPainterPath KisShapeSelection::outlineCache() const { return m_outline; } bool KisShapeSelection::outlineCacheValid() const { return true; } void KisShapeSelection::recalculateOutlineCache() { QTransform resolutionMatrix; resolutionMatrix.scale(m_image->xRes(), m_image->yRes()); QList shapesList = shapes(); QPainterPath outline; Q_FOREACH (KoShape * shape, shapesList) { /** * WARNING: we should unite all the shapes in image coordinates, * not in points. Boolean operations inside the QPainterPath * linearize the curves into lines and they use absolute values * for thresholds. * * See KritaUtils::pathShapeBooleanSpaceWorkaround() for more info */ QTransform shapeMatrix = shape->absoluteTransformation(); outline = outline.united(resolutionMatrix.map(shapeMatrix.map(shape->outline()))); } m_outline = outline; } void KisShapeSelection::paintComponent(QPainter& painter, KoShapePaintingContext &) const { Q_UNUSED(painter); } void KisShapeSelection::renderToProjection(KisPaintDeviceSP projection) { Q_ASSERT(projection); Q_ASSERT(m_image); QRectF boundingRect = outlineCache().boundingRect(); renderSelection(projection, boundingRect.toAlignedRect()); } void KisShapeSelection::renderToProjection(KisPaintDeviceSP projection, const QRect& r) { Q_ASSERT(projection); renderSelection(projection, r); } void KisShapeSelection::renderSelection(KisPaintDeviceSP projection, const QRect& requestedRect) { KIS_SAFE_ASSERT_RECOVER_RETURN(projection); KIS_SAFE_ASSERT_RECOVER_RETURN(m_image); const qint32 MASK_IMAGE_WIDTH = 256; const qint32 MASK_IMAGE_HEIGHT = 256; const QPainterPath selectionOutline = outlineCache(); if (*projection->defaultPixel().data() == OPACITY_TRANSPARENT_U8) { projection->clear(requestedRect); } else { KoColor transparentColor(Qt::transparent, projection->colorSpace()); projection->fill(requestedRect, transparentColor); } const QRect r = requestedRect & selectionOutline.boundingRect().toAlignedRect(); QImage polygonMaskImage(MASK_IMAGE_WIDTH, MASK_IMAGE_HEIGHT, QImage::Format_ARGB32); QPainter maskPainter(&polygonMaskImage); maskPainter.setRenderHint(QPainter::Antialiasing, true); // Break the mask up into chunks so we don't have to allocate a potentially very large QImage. for (qint32 x = r.x(); x < r.x() + r.width(); x += MASK_IMAGE_WIDTH) { for (qint32 y = r.y(); y < r.y() + r.height(); y += MASK_IMAGE_HEIGHT) { maskPainter.fillRect(polygonMaskImage.rect(), Qt::black); maskPainter.translate(-x, -y); maskPainter.fillPath(selectionOutline, Qt::white); maskPainter.translate(x, y); qint32 rectWidth = qMin(r.x() + r.width() - x, MASK_IMAGE_WIDTH); qint32 rectHeight = qMin(r.y() + r.height() - y, MASK_IMAGE_HEIGHT); KisSequentialIterator it(projection, QRect(x, y, rectWidth, rectHeight)); while (it.nextPixel()) { (*it.rawData()) = qRed(polygonMaskImage.pixel(it.x() - x, it.y() - y)); } } } } KoShapeManager* KisShapeSelection::shapeManager() const { return m_canvas->shapeManager(); } KisShapeSelectionFactory::KisShapeSelectionFactory() : KoShapeFactoryBase("KisShapeSelection", "selection shape container") { setHidden(true); } void KisShapeSelection::moveX(qint32 x) { const QPointF diff(x / m_image->xRes(), 0); emit sigMoveShapes(diff); } void KisShapeSelection::moveY(qint32 y) { const QPointF diff(0, y / m_image->yRes()); emit sigMoveShapes(diff); } void KisShapeSelection::slotMoveShapes(const QPointF &diff) { Q_FOREACH (KoShape* shape, shapeManager()->shapes()) { if (shape != this) { QPointF pos = shape->position(); shape->setPosition(pos + diff); } } } // TODO same code as in vector layer, refactor! KUndo2Command* KisShapeSelection::transform(const QTransform &transform) { QList shapes = m_canvas->shapeManager()->shapes(); if(shapes.isEmpty()) return 0; QTransform realTransform = m_converter->documentToView() * transform * m_converter->viewToDocument(); QList oldTransformations; QList newTransformations; // this code won't work if there are shapes, that inherit the transformation from the parent container. // the chart and tree shapes are examples for that, but they aren't used in krita and there are no other shapes like that. Q_FOREACH (const KoShape* shape, shapes) { QTransform oldTransform = shape->transformation(); oldTransformations.append(oldTransform); if (dynamic_cast(shape)) { newTransformations.append(oldTransform); } else { QTransform globalTransform = shape->absoluteTransformation(); QTransform localTransform = globalTransform * realTransform * globalTransform.inverted(); newTransformations.append(localTransform*oldTransform); } } return new KoShapeTransformCommand(shapes, oldTransformations, newTransformations); } diff --git a/libs/ui/flake/kis_shape_selection.h b/libs/ui/flake/kis_shape_selection.h index e42ca3e2b9..a9d168c14e 100644 --- a/libs/ui/flake/kis_shape_selection.h +++ b/libs/ui/flake/kis_shape_selection.h @@ -1,142 +1,142 @@ /* * 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. */ #ifndef KIS_SHAPE_SELECTION_H #define KIS_SHAPE_SELECTION_H #include #include #include #include #include #include #include #include class KoStore; class KoShapeManager; class KisShapeSelectionCanvas; class KisShapeSelectionModel; class KisImageViewConverter; class KUndo2Command; /** * The marker class. * It is added to the shape's user data to show this shape * is a part of a shape selection */ class KisShapeSelectionMarker : public KoShapeUserData { KoShapeUserData* clone() const override { return new KisShapeSelectionMarker(*this); } }; class KRITAUI_EXPORT KisShapeSelection : public QObject, public KoShapeLayer, public KisSelectionComponent { Q_OBJECT KisShapeSelection(const KisShapeSelection& rhs); public: KisShapeSelection(KoShapeControllerBase *shapeControllerBase, KisImageWSP image, KisSelectionWSP selection); ~KisShapeSelection() override; KisShapeSelection(const KisShapeSelection& rhs, KisSelection* selection); KisSelectionComponent* clone(KisSelection* selection) override; bool saveSelection(KoStore * store) const; bool loadSelection(KoStore * store); /** * Renders the shapes to a selection. This method should only be called * by KisSelection to update it's projection. * * @param projection the target selection */ void renderToProjection(KisPaintDeviceSP projection) override; void renderToProjection(KisPaintDeviceSP projection, const QRect& r) override; KUndo2Command* resetToEmpty() override; bool isEmpty() const override; QPainterPath outlineCache() const override; bool outlineCacheValid() const override; void recalculateOutlineCache() override; KoShapeManager *shapeManager() const; void moveX(qint32 x) override; void moveY(qint32 y) override; KUndo2Command* transform(const QTransform &transform) override; Q_SIGNALS: void sigMoveShapes(const QPointF &diff); private Q_SLOTS: void slotMoveShapes(const QPointF &diff); protected: void paintComponent(QPainter& painter, KoShapePaintingContext &paintcontext) const override; private: friend class KisTakeAllShapesCommand; void setUpdatesEnabled(bool enabled); bool updatesEnabled() const; - + void init(KisImageSP image, KoShapeControllerBase *shapeControllerBase); private: void renderSelection(KisPaintDeviceSP projection, const QRect& requestedRect); KisImageWSP m_image; QPainterPath m_outline; KisImageViewConverter *m_converter; KisShapeSelectionCanvas *m_canvas; KisShapeSelectionModel *m_model; KoShapeControllerBase *m_shapeControllerBase; friend class KisShapeSelectionModel; }; class KRITAUI_EXPORT KisShapeSelectionFactory : public KoShapeFactoryBase { public: KisShapeSelectionFactory(); ~KisShapeSelectionFactory() override {} KoShape *createDefaultShape(KoDocumentResourceManager *documentResources = 0) const override { Q_UNUSED(documentResources); return 0; } bool supports(const KoXmlElement & e, KoShapeLoadingContext &context) const override { Q_UNUSED(e); Q_UNUSED(context); return false; } }; #endif diff --git a/libs/ui/flake/kis_shape_selection_model.cpp b/libs/ui/flake/kis_shape_selection_model.cpp index 8a1e9ab558..2cb7a29d65 100644 --- a/libs/ui/flake/kis_shape_selection_model.cpp +++ b/libs/ui/flake/kis_shape_selection_model.cpp @@ -1,178 +1,174 @@ /* * Copyright (c) 2007 Sven Langkamp * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_shape_selection_model.h" #include "kis_debug.h" #include #include #include #include "kis_shape_selection.h" #include "kis_selection.h" #include "kis_image.h" #include "kis_update_selection_job.h" KisShapeSelectionModel::KisShapeSelectionModel(KisImageWSP image, KisSelectionWSP selection, KisShapeSelection* shapeSelection) : m_image(image) , m_parentSelection(selection) , m_shapeSelection(shapeSelection) , m_updatesEnabled(true) { } KisShapeSelectionModel::~KisShapeSelectionModel() { m_image = 0; m_parentSelection = 0; } void KisShapeSelectionModel::requestUpdate(const QRect &updateRect) { m_shapeSelection->recalculateOutlineCache(); if (m_updatesEnabled) { m_parentSelection->requestCompressedProjectionUpdate(updateRect); } } void KisShapeSelectionModel::add(KoShape *child) { if (!m_shapeSelection) return; if (m_shapeMap.contains(child)) return; child->setStroke(KoShapeStrokeModelSP()); child->setBackground( QSharedPointer(0)); m_shapeMap.insert(child, child->boundingRect()); m_shapeSelection->shapeManager()->addShape(child); QRect updateRect = child->boundingRect().toAlignedRect(); if (m_image.isValid()) { QTransform matrix; matrix.scale(m_image->xRes(), m_image->yRes()); updateRect = matrix.mapRect(updateRect); } if (m_shapeMap.count() == 1) { // The shape is the first one, so the shape selection just got created // Pixel selection provides no longer the datamanager of the selection // so update the whole selection requestUpdate(QRect()); } else { requestUpdate(updateRect); } } void KisShapeSelectionModel::remove(KoShape *child) { if (!m_shapeMap.contains(child)) return; QRect updateRect = child->boundingRect().toAlignedRect(); m_shapeMap.remove(child); if (m_shapeSelection) { m_shapeSelection->shapeManager()->remove(child); } if (m_image.isValid()) { QTransform matrix; matrix.scale(m_image->xRes(), m_image->yRes()); updateRect = matrix.mapRect(updateRect); if (m_shapeSelection) { // No m_shapeSelection indicates the selection is being deleted requestUpdate(updateRect); - - if (m_updatesEnabled && m_shapeMap.isEmpty()) { - m_parentSelection->notifyShapeSelectionBecameEmpty(); - } } } } void KisShapeSelectionModel::setUpdatesEnabled(bool enabled) { m_updatesEnabled = enabled; } bool KisShapeSelectionModel::updatesEnabled() const { return m_updatesEnabled; } void KisShapeSelectionModel::setClipped(const KoShape *child, bool clipping) { Q_UNUSED(child); Q_UNUSED(clipping); } bool KisShapeSelectionModel::isClipped(const KoShape *child) const { Q_UNUSED(child); return false; } void KisShapeSelectionModel::setInheritsTransform(const KoShape *shape, bool inherit) { Q_UNUSED(shape); Q_UNUSED(inherit); } bool KisShapeSelectionModel::inheritsTransform(const KoShape *shape) const { Q_UNUSED(shape); return false; } int KisShapeSelectionModel::count() const { return m_shapeMap.count(); } QList KisShapeSelectionModel::shapes() const { return QList(m_shapeMap.keys()); } void KisShapeSelectionModel::containerChanged(KoShapeContainer *, KoShape::ChangeType) { } void KisShapeSelectionModel::childChanged(KoShape * child, KoShape::ChangeType type) { if (!m_shapeSelection) return; // TODO: check if still needed if (type == KoShape::ParentChanged) return; QRectF changedRect = m_shapeMap[child]; changedRect = changedRect.united(child->boundingRect()); m_shapeMap[child] = child->boundingRect(); if (m_image.isValid()) { QTransform matrix; matrix.scale(m_image->xRes(), m_image->yRes()); changedRect = matrix.mapRect(changedRect); } requestUpdate(changedRect.toAlignedRect()); } void KisShapeSelectionModel::setShapeSelection(KisShapeSelection* selection) { m_shapeSelection = selection; } diff --git a/libs/ui/forms/wdggeometryoptions.ui b/libs/ui/forms/wdggeometryoptions.ui index 4787016614..601bee511d 100644 --- a/libs/ui/forms/wdggeometryoptions.ui +++ b/libs/ui/forms/wdggeometryoptions.ui @@ -1,111 +1,148 @@ WdgGeometryOptions 0 0 - 185 - 46 + 287 + 167 Geometry Options Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter 1 1 0 0 0 0 - - + + - Outline: + Fill: - - + + 0 0 - No Outline + Not Filled - Brush + Foreground Color - Brush (Background Color) + Background Color + + + + + Pattern - - + + - Fill: + Outline: - - + + 0 0 - Not Filled - - - - - Foreground Color + No Outline - Background Color + Brush - Pattern + Brush (Background Color) + + + + Pattern Transform + + + + + + Rotate: + + + + + + + Scale: + + + + + + + + + + + + + + + KisDoubleSliderSpinBox + QWidget +
kis_slider_spin_box.h
+ 1 +
+
diff --git a/libs/ui/input/kis_input_manager_p.cpp b/libs/ui/input/kis_input_manager_p.cpp index 4016231683..28adb61266 100644 --- a/libs/ui/input/kis_input_manager_p.cpp +++ b/libs/ui/input/kis_input_manager_p.cpp @@ -1,689 +1,694 @@ /* * 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. */ #include "kis_input_manager_p.h" #include #include #include #include #include "kis_input_manager.h" #include "kis_config.h" #include "kis_abstract_input_action.h" #include "kis_tool_invocation_action.h" #include "kis_stroke_shortcut.h" #include "kis_touch_shortcut.h" #include "kis_native_gesture_shortcut.h" #include "kis_input_profile_manager.h" #include "kis_extended_modifiers_mapper.h" #include "kis_zoom_and_rotate_action.h" /** * This hungry class EventEater encapsulates event masking logic. * * Its basic role is to kill synthetic mouseMove events sent by Xorg or Qt after * tablet events. Those events are sent in order to allow widgets that haven't * implemented tablet specific functionality to seamlessly behave as if one were * using a mouse. These synthetic events are *supposed* to be optional, or at * least come with a flag saying "This is a fake event!!" but neither of those * methods is trustworthy. (This is correct as of Qt 5.4 + Xorg.) * * Qt 5.4 provides no reliable way to see if a user's tablet is being hovered * over the pad, since it converts all tablethover events into mousemove, with * no option to turn this off. Moreover, sometimes the MouseButtonPress event * from the tapping their tablet happens BEFORE the TabletPress event. This * means we have to resort to a somewhat complicated logic. What makes this * truly a joke is that we are not guaranteed to observe TabletProximityEnter * events when we're using a tablet, either, you may only see an Enter event. * * Once we see tablet events heading our way, we can say pretty confidently that * every mouse event is fake. There are two painful cases to consider - a * mousePress event could arrive before the tabletPress event, or it could * arrive much later, e.g. after tabletRelease. The first was only seen on Linux * with Qt's XInput2 code, the solution was to hold onto mousePress events * temporarily and wait for tabletPress later, this is contained in git history * but is now removed. The second case is currently handled by the * eatOneMousePress function, which waits as long as necessary to detect and * block a single mouse press event. */ static bool isMouseEventType(QEvent::Type t) { return (t == QEvent::MouseMove || t == QEvent::MouseButtonPress || t == QEvent::MouseButtonRelease || t == QEvent::MouseButtonDblClick); } KisInputManager::Private::EventEater::EventEater() { KisConfig cfg(true); activateSecondaryButtonsWorkaround = cfg.useRightMiddleTabletButtonWorkaround(); } bool KisInputManager::Private::EventEater::eventFilter(QObject* target, QEvent* event ) { Q_UNUSED(target) auto debugEvent = [&](int i) { if (KisTabletDebugger::instance()->debugEnabled()) { QString pre = QString("[BLOCKED %1:]").arg(i); QMouseEvent *ev = static_cast(event); dbgTablet << KisTabletDebugger::instance()->eventToString(*ev, pre); } }; auto debugTabletEvent = [&](int i) { if (KisTabletDebugger::instance()->debugEnabled()) { QString pre = QString("[BLOCKED %1:]").arg(i); QTabletEvent *ev = static_cast(event); dbgTablet << KisTabletDebugger::instance()->eventToString(*ev, pre); } }; if (peckish && event->type() == QEvent::MouseButtonPress // Drop one mouse press following tabletPress or touchBegin && (static_cast(event)->button() == Qt::LeftButton)) { peckish = false; debugEvent(1); return true; } if (activateSecondaryButtonsWorkaround) { if (event->type() == QEvent::TabletPress || event->type() == QEvent::TabletRelease) { QTabletEvent *te = static_cast(event); if (te->button() != Qt::LeftButton) { debugTabletEvent(3); return true; } } else if (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonRelease || event->type() == QEvent::MouseButtonDblClick) { QMouseEvent *me = static_cast(event); if (me->button() != Qt::LeftButton) { return false; } } } if (isMouseEventType(event->type()) && (hungry // On Mac, we need mouse events when the tablet is in proximity, but not pressed down // since tablet move events are not generated until after tablet press. #ifndef Q_OS_MAC || (eatSyntheticEvents && static_cast(event)->source() != Qt::MouseEventNotSynthesized) #endif )) { // Drop mouse events if enabled or event was synthetic & synthetic events are disabled debugEvent(2); return true; } return false; // All clear - let this one through! } void KisInputManager::Private::EventEater::activate() { if (!hungry && (KisTabletDebugger::instance()->debugEnabled())) { dbgTablet << "Start blocking mouse events"; } hungry = true; } void KisInputManager::Private::EventEater::deactivate() { if (hungry && (KisTabletDebugger::instance()->debugEnabled())) { dbgTablet << "Stop blocking mouse events"; } hungry = false; } void KisInputManager::Private::EventEater::eatOneMousePress() { // Enable on other platforms if getting full-pressure splotches peckish = true; } bool KisInputManager::Private::ignoringQtCursorEvents() { return eventEater.hungry; } void KisInputManager::Private::setMaskSyntheticEvents(bool value) { eventEater.eatSyntheticEvents = value; } KisInputManager::Private::Private(KisInputManager *qq) : q(qq) , moveEventCompressor(10 /* ms */, KisSignalCompressor::FIRST_ACTIVE, KisSignalCompressor::ADDITIVE_INTERVAL) , priorityEventFilterSeqNo(0) , canvasSwitcher(this, qq) { KisConfig cfg(true); moveEventCompressor.setDelay(cfg.tabletEventsDelay()); testingAcceptCompressedTabletEvents = cfg.testingAcceptCompressedTabletEvents(); testingCompressBrushEvents = cfg.testingCompressBrushEvents(); if (cfg.trackTabletEventLatency()) { tabletLatencyTracker = new TabletLatencyTracker(); } matcher.setInputActionGroupsMaskCallback( [this] () { return this->canvas ? this->canvas->inputActionGroupsMask() : AllActionGroup; }); } static const int InputWidgetsThreshold = 2000; static const int OtherWidgetsThreshold = 400; KisInputManager::Private::CanvasSwitcher::CanvasSwitcher(Private *_d, QObject *p) : QObject(p), d(_d), eatOneMouseStroke(false), focusSwitchThreshold(InputWidgetsThreshold) { } void KisInputManager::Private::CanvasSwitcher::setupFocusThreshold(QObject* object) { QWidget *widget = qobject_cast(object); KIS_SAFE_ASSERT_RECOVER_RETURN(widget); thresholdConnections.clear(); thresholdConnections.addConnection(&focusSwitchThreshold, SIGNAL(timeout()), widget, SLOT(setFocus())); } void KisInputManager::Private::CanvasSwitcher::addCanvas(KisCanvas2 *canvas) { if (!canvas) return; QObject *canvasWidget = canvas->canvasWidget(); if (!canvasResolver.contains(canvasWidget)) { canvasResolver.insert(canvasWidget, canvas); + } else { + // just a sanity cheeck to find out if we are + // trying to add two canvases concurrently. + KIS_SAFE_ASSERT_RECOVER_NOOP(d->canvas == canvas); + } + + if (canvas != d->canvas) { d->q->setupAsEventFilter(canvasWidget); canvasWidget->installEventFilter(this); setupFocusThreshold(canvasWidget); focusSwitchThreshold.setEnabled(false); d->canvas = canvas; d->toolProxy = qobject_cast(canvas->toolProxy()); - } else { - KIS_ASSERT_RECOVER_RETURN(d->canvas == canvas); } } void KisInputManager::Private::CanvasSwitcher::removeCanvas(KisCanvas2 *canvas) { QObject *widget = canvas->canvasWidget(); canvasResolver.remove(widget); if (d->eventsReceiver == widget) { d->q->setupAsEventFilter(0); } widget->removeEventFilter(this); } bool isInputWidget(QWidget *w) { if (!w) return false; QList types; types << QLatin1String("QAbstractSlider"); types << QLatin1String("QAbstractSpinBox"); types << QLatin1String("QLineEdit"); types << QLatin1String("QTextEdit"); types << QLatin1String("QPlainTextEdit"); types << QLatin1String("QComboBox"); types << QLatin1String("QKeySequenceEdit"); Q_FOREACH (const QLatin1String &type, types) { if (w->inherits(type.data())) { return true; } } return false; } bool KisInputManager::Private::CanvasSwitcher::eventFilter(QObject* object, QEvent* event ) { if (canvasResolver.contains(object)) { switch (event->type()) { case QEvent::FocusIn: { QFocusEvent *fevent = static_cast(event); KisCanvas2 *canvas = canvasResolver.value(object); // only relevant canvases from the same main window should be // registered in the switcher KIS_SAFE_ASSERT_RECOVER_BREAK(canvas); if (canvas != d->canvas) { eatOneMouseStroke = 2 * (fevent->reason() == Qt::MouseFocusReason); } d->canvas = canvas; d->toolProxy = qobject_cast(canvas->toolProxy()); d->q->setupAsEventFilter(object); object->removeEventFilter(this); object->installEventFilter(this); setupFocusThreshold(object); focusSwitchThreshold.setEnabled(false); const QPoint globalPos = QCursor::pos(); const QPoint localPos = d->canvas->canvasWidget()->mapFromGlobal(globalPos); QWidget *canvasWindow = d->canvas->canvasWidget()->window(); const QPoint windowsPos = canvasWindow ? canvasWindow->mapFromGlobal(globalPos) : localPos; QEnterEvent event(localPos, windowsPos, globalPos); d->q->eventFilter(object, &event); break; } case QEvent::FocusOut: { focusSwitchThreshold.setEnabled(true); break; } case QEvent::Enter: { break; } case QEvent::Leave: { focusSwitchThreshold.stop(); break; } case QEvent::Wheel: { QWidget *widget = static_cast(object); widget->setFocus(); break; } case QEvent::MouseButtonPress: case QEvent::MouseButtonRelease: case QEvent::TabletPress: case QEvent::TabletRelease: focusSwitchThreshold.forceDone(); if (eatOneMouseStroke) { eatOneMouseStroke--; return true; } break; case QEvent::MouseButtonDblClick: focusSwitchThreshold.forceDone(); if (eatOneMouseStroke) { return true; } break; case QEvent::MouseMove: case QEvent::TabletMove: { QWidget *widget = static_cast(object); if (!widget->hasFocus()) { const int delay = isInputWidget(QApplication::focusWidget()) ? InputWidgetsThreshold : OtherWidgetsThreshold; focusSwitchThreshold.setDelayThreshold(delay); focusSwitchThreshold.start(); } } break; default: break; } } return QObject::eventFilter(object, event); } KisInputManager::Private::ProximityNotifier::ProximityNotifier(KisInputManager::Private *_d, QObject *p) : QObject(p), d(_d) {} bool KisInputManager::Private::ProximityNotifier::eventFilter(QObject* object, QEvent* event ) { /** * All Qt builds in range 5.7.0...5.11.X on X11 had a problem that made all * the tablet events be accepted by default. It meant that no mouse * events were synthesized, and, therefore, no Enter/Leave were generated. * * The fix for this bug has been added only in Qt 5.12.0: * https://codereview.qt-project.org/#/c/239918/ * * To avoid this problem we should explicitly ignore all the tablet events. */ #if defined Q_OS_LINUX && \ QT_VERSION >= QT_VERSION_CHECK(5, 9, 0) && \ QT_VERSION < QT_VERSION_CHECK(5, 12, 0) if (event->type() == QEvent::TabletMove || event->type() == QEvent::TabletPress || event->type() == QEvent::TabletRelease) { event->ignore(); } #endif switch (event->type()) { case QEvent::TabletEnterProximity: d->debugEvent(event); // Tablet proximity events are unreliable AND fake mouse events do not // necessarily come after tablet events, so this is insufficient. // d->eventEater.eatOneMousePress(); // Qt sends fake mouse events instead of hover events, so not very useful. // Don't block mouse events on tablet since tablet move events are not generated until // after tablet press. #ifndef Q_OS_MACOS d->blockMouseEvents(); #endif break; case QEvent::TabletLeaveProximity: d->debugEvent(event); d->allowMouseEvents(); break; default: break; } return QObject::eventFilter(object, event); } void KisInputManager::Private::addStrokeShortcut(KisAbstractInputAction* action, int index, const QList &modifiers, Qt::MouseButtons buttons) { KisStrokeShortcut *strokeShortcut = new KisStrokeShortcut(action, index); QList buttonList; if(buttons & Qt::LeftButton) { buttonList << Qt::LeftButton; } if(buttons & Qt::RightButton) { buttonList << Qt::RightButton; } if(buttons & Qt::MidButton) { buttonList << Qt::MidButton; } if(buttons & Qt::XButton1) { buttonList << Qt::XButton1; } if(buttons & Qt::XButton2) { buttonList << Qt::XButton2; } if (buttonList.size() > 0) { #if QT_VERSION >= QT_VERSION_CHECK(5,14,0) strokeShortcut->setButtons(QSet(modifiers.begin(), modifiers.end()), QSet(buttonList.begin(), buttonList.end())); #else strokeShortcut->setButtons(QSet::fromList(modifiers), QSet::fromList(buttonList)); #endif matcher.addShortcut(strokeShortcut); } else { delete strokeShortcut; } } void KisInputManager::Private::addKeyShortcut(KisAbstractInputAction* action, int index, const QList &keys) { if (keys.size() == 0) return; KisSingleActionShortcut *keyShortcut = new KisSingleActionShortcut(action, index); //Note: Ordering is important here, Shift + V is different from V + Shift, //which is the reason we use the last key here since most users will enter //shortcuts as "Shift + V". Ideally this should not happen, but this is //the way the shortcut matcher is currently implemented. QList allKeys = keys; Qt::Key key = allKeys.takeLast(); #if QT_VERSION >= QT_VERSION_CHECK(5,14,0) QSet modifiers = QSet(allKeys.begin(), allKeys.end()); #else QSet modifiers = QSet::fromList(allKeys); #endif keyShortcut->setKey(modifiers, key); matcher.addShortcut(keyShortcut); } void KisInputManager::Private::addWheelShortcut(KisAbstractInputAction* action, int index, const QList &modifiers, KisShortcutConfiguration::MouseWheelMovement wheelAction) { QScopedPointer keyShortcut( new KisSingleActionShortcut(action, index)); KisSingleActionShortcut::WheelAction a; switch(wheelAction) { case KisShortcutConfiguration::WheelUp: a = KisSingleActionShortcut::WheelUp; break; case KisShortcutConfiguration::WheelDown: a = KisSingleActionShortcut::WheelDown; break; case KisShortcutConfiguration::WheelLeft: a = KisSingleActionShortcut::WheelLeft; break; case KisShortcutConfiguration::WheelRight: a = KisSingleActionShortcut::WheelRight; break; case KisShortcutConfiguration::WheelTrackpad: a = KisSingleActionShortcut::WheelTrackpad; break; default: return; } #if QT_VERSION >= QT_VERSION_CHECK(5,14,0) keyShortcut->setWheel(QSet(modifiers.begin(), modifiers.end()), a); #else keyShortcut->setWheel(QSet::fromList(modifiers), a); #endif matcher.addShortcut(keyShortcut.take()); } void KisInputManager::Private::addTouchShortcut(KisAbstractInputAction* action, int index, KisShortcutConfiguration::GestureAction gesture) { KisTouchShortcut *shortcut = new KisTouchShortcut(action, index, gesture); dbgKrita << "TouchAction:" << action->name(); switch(gesture) { case KisShortcutConfiguration::RotateGesture: case KisShortcutConfiguration::PinchGesture: #ifndef Q_OS_MACOS case KisShortcutConfiguration::ZoomAndRotateGesture: #endif shortcut->setMinimumTouchPoints(2); shortcut->setMaximumTouchPoints(2); break; case KisShortcutConfiguration::PanGesture: shortcut->setMinimumTouchPoints(3); shortcut->setMaximumTouchPoints(10); break; default: break; } matcher.addShortcut(shortcut); } bool KisInputManager::Private::addNativeGestureShortcut(KisAbstractInputAction* action, int index, KisShortcutConfiguration::GestureAction gesture) { // Qt5 only implements QNativeGestureEvent for macOS Qt::NativeGestureType type; switch (gesture) { #ifdef Q_OS_MACOS case KisShortcutConfiguration::PinchGesture: type = Qt::ZoomNativeGesture; break; case KisShortcutConfiguration::PanGesture: type = Qt::PanNativeGesture; break; case KisShortcutConfiguration::RotateGesture: type = Qt::RotateNativeGesture; break; case KisShortcutConfiguration::SmartZoomGesture: type = Qt::SmartZoomNativeGesture; break; #endif default: return false; } KisNativeGestureShortcut *shortcut = new KisNativeGestureShortcut(action, index, type); matcher.addShortcut(shortcut); return true; } void KisInputManager::Private::setupActions() { QList actions = KisInputProfileManager::instance()->actions(); Q_FOREACH (KisAbstractInputAction *action, actions) { KisToolInvocationAction *toolAction = dynamic_cast(action); if(toolAction) { defaultInputAction = toolAction; } } connect(KisInputProfileManager::instance(), SIGNAL(currentProfileChanged()), q, SLOT(profileChanged())); if(KisInputProfileManager::instance()->currentProfile()) { q->profileChanged(); } } bool KisInputManager::Private::processUnhandledEvent(QEvent *event) { bool retval = false; if (forwardAllEventsToTool || event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease) { defaultInputAction->processUnhandledEvent(event); retval = true; } return retval && !forwardAllEventsToTool; } bool KisInputManager::Private::tryHidePopupPalette() { if (canvas->isPopupPaletteVisible()) { canvas->slotShowPopupPalette(); return true; } return false; } #ifdef HAVE_X11 inline QPointF dividePoints(const QPointF &pt1, const QPointF &pt2) { return QPointF(pt1.x() / pt2.x(), pt1.y() / pt2.y()); } inline QPointF multiplyPoints(const QPointF &pt1, const QPointF &pt2) { return QPointF(pt1.x() * pt2.x(), pt1.y() * pt2.y()); } #endif void KisInputManager::Private::blockMouseEvents() { eventEater.activate(); } void KisInputManager::Private::allowMouseEvents() { eventEater.deactivate(); } void KisInputManager::Private::eatOneMousePress() { eventEater.eatOneMousePress(); } void KisInputManager::Private::resetCompressor() { compressedMoveEvent.reset(); moveEventCompressor.stop(); } bool KisInputManager::Private::handleCompressedTabletEvent(QEvent *event) { bool retval = false; /** * When Krita (as an application) has no input focus, we cannot * handle key events. But at the same time, when the user hovers * Krita canvas, we should still show him the correct cursor. * * So here we just add a simple workaround to resync shortcut * matcher's state at least against the basic modifiers, like * Shift, Control and Alt. */ QWidget *recievingWidget = dynamic_cast(eventsReceiver); if (recievingWidget && !recievingWidget->hasFocus()) { QVector guessedKeys; KisExtendedModifiersMapper mapper; Qt::KeyboardModifiers modifiers = mapper.queryStandardModifiers(); Q_FOREACH (Qt::Key key, mapper.queryExtendedModifiers()) { QKeyEvent kevent(QEvent::ShortcutOverride, key, modifiers); guessedKeys << KisExtendedModifiersMapper::workaroundShiftAltMetaHell(&kevent); } matcher.recoveryModifiersWithoutFocus(guessedKeys); } if (!matcher.pointerMoved(event) && toolProxy) { toolProxy->forwardHoverEvent(event); } retval = true; event->setAccepted(true); return retval; } qint64 KisInputManager::Private::TabletLatencyTracker::currentTimestamp() const { // on OS X, we need to compute the timestamp that compares correctly against the native event timestamp, // which seems to be the msecs since system startup. On Linux with WinTab, we produce the timestamp that // we compare against ourselves in QWindowSystemInterface. QElapsedTimer elapsed; elapsed.start(); return elapsed.msecsSinceReference(); } void KisInputManager::Private::TabletLatencyTracker::print(const QString &message) { dbgTablet << qUtf8Printable(message); } diff --git a/libs/ui/kis_config.cc b/libs/ui/kis_config.cc index dc923c5aa9..b0b93fdbf1 100644 --- a/libs/ui/kis_config.cc +++ b/libs/ui/kis_config.cc @@ -1,2238 +1,2248 @@ /* * 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 #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::writeSysInfo("Current Settings\n"); KisUsageLogger::writeSysInfo(QString(" Current Swap Location: %1").arg(KisImageConfig(true).swapDir())); KisUsageLogger::writeSysInfo(QString(" Current Swap Location writable: %1").arg(QFileInfo(KisImageConfig(true).swapDir()).isWritable() ? "true" : "false")); KisUsageLogger::writeSysInfo(QString(" Undo Enabled: %1").arg(undoEnabled()? "true" : "false")); KisUsageLogger::writeSysInfo(QString(" Undo Stack Limit: %1").arg(undoStackLimit())); KisUsageLogger::writeSysInfo(QString(" Use OpenGL: %1").arg(useOpenGL() ? "true" : "false")); KisUsageLogger::writeSysInfo(QString(" Use OpenGL Texture Buffer: %1").arg(useOpenGLTextureBuffer() ? "true" : "false")); KisUsageLogger::writeSysInfo(QString(" Use AMD Vectorization Workaround: %1").arg(enableAmdVectorizationWorkaround() ? "true" : "false")); KisUsageLogger::writeSysInfo(QString(" Canvas State: %1").arg(canvasState())); KisUsageLogger::writeSysInfo(QString(" Autosave Interval: %1").arg(autoSaveInterval())); KisUsageLogger::writeSysInfo(QString(" Use Backup Files: %1").arg(backupFile() ? "true" : "false")); KisUsageLogger::writeSysInfo(QString(" Number of Backups Kept: %1").arg(m_cfg.readEntry("numberofbackupfiles", 1))); KisUsageLogger::writeSysInfo(QString(" Backup 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::writeSysInfo(QString(" Backup Location: %1").arg(backupDir)); KisUsageLogger::writeSysInfo(QString(" Backup Location writable: %1").arg(QFileInfo(backupDir).isWritable() ? "true" : "false")); KisUsageLogger::writeSysInfo(QString(" Use Win8 Pointer Input: %1").arg(useWin8PointerInput() ? "true" : "false")); KisUsageLogger::writeSysInfo(QString(" Use RightMiddleTabletButton Workaround: %1").arg(useRightMiddleTabletButtonWorkaround() ? "true" : "false")); KisUsageLogger::writeSysInfo(QString(" Levels of Detail Enabled: %1").arg(levelOfDetailEnabled() ? "true" : "false")); KisUsageLogger::writeSysInfo(QString(" Use Zip64: %1").arg(useZip64() ? "true" : "false")); KisUsageLogger::writeSysInfo("\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); } +void KisConfig::setWidgetStyle(QString name) +{ + m_cfg.writeEntry("widgetStyle", name); +} + +QString KisConfig::widgetStyle(bool defaultValue) +{ + return (defaultValue ? "" : m_cfg.readEntry("widgetStyle", "")); +} + 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::trimKra(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("TrimKra", false)); } void KisConfig::setTrimKra(bool trim) { m_cfg.writeEntry("TrimKra", trim); } 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 { #ifdef Q_OS_ANDROID int defaultGesture = 0; // TouchGesture #else int defaultGesture = 2; // MiddleMouseButtonGesture #endif return (defaultValue ? defaultGesture : m_cfg.readEntry("KineticScrollingGesture", defaultGesture)); } 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::autoPinLayersToTimeline(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("autoPinLayers", true)); } void KisConfig::setAutoPinLayersToTimeline(bool value) { m_cfg.writeEntry("autoPinLayers", value); } 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); } bool KisConfig::convertLayerColorSpaceInProperties(bool defaultValue) const { return defaultValue ? true : m_cfg.readEntry("convertLayerColorSpaceInProperties", true); } void KisConfig::setConvertLayerColorSpaceInProperties(bool value) { m_cfg.writeEntry("convertLayerColorSpaceInProperties", 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_config.h b/libs/ui/kis_config.h index 9c1d3898aa..d62fb9173f 100644 --- a/libs/ui/kis_config.h +++ b/libs/ui/kis_config.h @@ -1,656 +1,659 @@ /* * 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 KIS_CONFIG_H_ #define KIS_CONFIG_H_ #include #include #include #include #include #include #include #include #include #include "kritaui_export.h" class KoColorProfile; class KoColorSpace; class KisSnapConfig; class QSettings; class KisOcioConfiguration; class KRITAUI_EXPORT KisConfig { public: /** * @brief KisConfig create a kisconfig object * @param readOnly if true, there will be no call to sync when the object is deleted. * Any KisConfig object created in a thread must be read-only. */ KisConfig(bool readOnly); ~KisConfig(); public Q_SLOTS: /// Log the most interesting settings to the usage log void logImportantSettings() const; public: bool disableTouchOnCanvas(bool defaultValue = false) const; void setDisableTouchOnCanvas(bool value) const; bool disableTouchRotation(bool defaultValue = false) const; void setDisableTouchRotation(bool value) const; // XXX Unused? bool useProjections(bool defaultValue = false) const; void setUseProjections(bool useProj) const; bool undoEnabled(bool defaultValue = false) const; void setUndoEnabled(bool undo) const; int undoStackLimit(bool defaultValue = false) const; void setUndoStackLimit(int limit) const; bool useCumulativeUndoRedo(bool defaultValue = false) const; void setCumulativeUndoRedo(bool value); double stackT1(bool defaultValue = false) const; void setStackT1(int T1); double stackT2(bool defaultValue = false) const; void setStackT2(int T2); int stackN(bool defaultValue = false) const; void setStackN(int N); qint32 defImageWidth(bool defaultValue = false) const; void defImageWidth(qint32 width) const; qint32 defImageHeight(bool defaultValue = false) const; void defImageHeight(qint32 height) const; qreal defImageResolution(bool defaultValue = false) const; void defImageResolution(qreal res) const; int preferredVectorImportResolutionPPI(bool defaultValue = false) const; void setPreferredVectorImportResolutionPPI(int value) const; /** * @return the id of the default color model used for creating new images. */ QString defColorModel(bool defaultValue = false) const; /** * set the id of the default color model used for creating new images. */ void defColorModel(const QString & model) const; /** * @return the id of the default color depth used for creating new images. */ QString defaultColorDepth(bool defaultValue = false) const; /** * set the id of the default color depth used for creating new images. */ void setDefaultColorDepth(const QString & depth) const; /** * @return the id of the default color profile used for creating new images. */ QString defColorProfile(bool defaultValue = false) const; /** * set the id of the default color profile used for creating new images. */ void defColorProfile(const QString & depth) const; CursorStyle newCursorStyle(bool defaultValue = false) const; void setNewCursorStyle(CursorStyle style); QColor getCursorMainColor(bool defaultValue = false) const; void setCursorMainColor(const QColor& v) const; OutlineStyle newOutlineStyle(bool defaultValue = false) const; void setNewOutlineStyle(OutlineStyle style); QRect colorPreviewRect() const; void setColorPreviewRect(const QRect &rect); /// get the profile the user has selected for the given screen QString monitorProfile(int screen) const; void setMonitorProfile(int screen, const QString & monitorProfile, bool override) const; QString monitorForScreen(int screen, const QString &defaultMonitor, bool defaultValue = true) const; void setMonitorForScreen(int screen, const QString& monitor); /// Get the actual profile to be used for the given screen, which is /// either the screen profile set by the color management system or /// the custom monitor profile set by the user, depending on the configuration const KoColorProfile *displayProfile(int screen) const; QString workingColorSpace(bool defaultValue = false) const; void setWorkingColorSpace(const QString & workingColorSpace) const; QString importProfile(bool defaultValue = false) const; void setImportProfile(const QString & importProfile) const; QString printerColorSpace(bool defaultValue = false) const; void setPrinterColorSpace(const QString & printerColorSpace) const; QString printerProfile(bool defaultValue = false) const; void setPrinterProfile(const QString & printerProfile) const; bool useBlackPointCompensation(bool defaultValue = false) const; void setUseBlackPointCompensation(bool useBlackPointCompensation) const; bool allowLCMSOptimization(bool defaultValue = false) const; void setAllowLCMSOptimization(bool allowLCMSOptimization); bool forcePaletteColors(bool defaultValue = false) const; void setForcePaletteColors(bool forcePaletteColors); void writeKoColor(const QString& name, const KoColor& color) const; KoColor readKoColor(const QString& name, const KoColor& color = KoColor()) const; bool showRulers(bool defaultValue = false) const; void setShowRulers(bool rulers) const; bool forceShowSaveMessages(bool defaultValue = true) const; void setForceShowSaveMessages(bool value) const; bool forceShowAutosaveMessages(bool defaultValue = true) const; void setForceShowAutosaveMessages(bool ShowAutosaveMessages) const; bool rulersTrackMouse(bool defaultValue = false) const; void setRulersTrackMouse(bool value) const; qint32 pasteBehaviour(bool defaultValue = false) const; void setPasteBehaviour(qint32 behaviour) const; qint32 monitorRenderIntent(bool defaultValue = false) const; void setRenderIntent(qint32 monitorRenderIntent) const; bool useOpenGL(bool defaultValue = false) const; void disableOpenGL() const; int openGLFilteringMode(bool defaultValue = false) const; void setOpenGLFilteringMode(int filteringMode); + void setWidgetStyle(QString name); + QString widgetStyle(bool defaultValue = false); + bool useOpenGLTextureBuffer(bool defaultValue = false) const; void setUseOpenGLTextureBuffer(bool useBuffer); // XXX Unused? bool disableVSync(bool defaultValue = false) const; void setDisableVSync(bool disableVSync); bool showAdvancedOpenGLSettings(bool defaultValue = false) const; bool forceOpenGLFenceWorkaround(bool defaultValue = false) const; int numMipmapLevels(bool defaultValue = false) const; int openGLTextureSize(bool defaultValue = false) const; int textureOverlapBorder() const; quint32 getGridMainStyle(bool defaultValue = false) const; void setGridMainStyle(quint32 v) const; quint32 getGridSubdivisionStyle(bool defaultValue = false) const; void setGridSubdivisionStyle(quint32 v) const; QColor getGridMainColor(bool defaultValue = false) const; void setGridMainColor(const QColor & v) const; QColor getGridSubdivisionColor(bool defaultValue = false) const; void setGridSubdivisionColor(const QColor & v) const; QColor getPixelGridColor(bool defaultValue = false) const; void setPixelGridColor(const QColor & v) const; qreal getPixelGridDrawingThreshold(bool defaultValue = false) const; void setPixelGridDrawingThreshold(qreal v) const; bool pixelGridEnabled(bool defaultValue = false) const; void enablePixelGrid(bool v) const; quint32 guidesLineStyle(bool defaultValue = false) const; void setGuidesLineStyle(quint32 v) const; QColor guidesColor(bool defaultValue = false) const; void setGuidesColor(const QColor & v) const; void loadSnapConfig(KisSnapConfig *config, bool defaultValue = false) const; void saveSnapConfig(const KisSnapConfig &config); qint32 checkSize(bool defaultValue = false) const; void setCheckSize(qint32 checkSize) const; bool scrollCheckers(bool defaultValue = false) const; void setScrollingCheckers(bool scollCheckers) const; QColor checkersColor1(bool defaultValue = false) const; void setCheckersColor1(const QColor & v) const; QColor checkersColor2(bool defaultValue = false) const; void setCheckersColor2(const QColor & v) const; QColor canvasBorderColor(bool defaultValue = false) const; void setCanvasBorderColor(const QColor &color) const; bool hideScrollbars(bool defaultValue = false) const; void setHideScrollbars(bool value) const; bool antialiasCurves(bool defaultValue = false) const; void setAntialiasCurves(bool v) const; bool antialiasSelectionOutline(bool defaultValue = false) const; void setAntialiasSelectionOutline(bool v) const; bool showRootLayer(bool defaultValue = false) const; void setShowRootLayer(bool showRootLayer) const; bool showGlobalSelection(bool defaultValue = false) const; void setShowGlobalSelection(bool showGlobalSelection) const; bool showOutlineWhilePainting(bool defaultValue = false) const; void setShowOutlineWhilePainting(bool showOutlineWhilePainting) const; bool forceAlwaysFullSizedOutline(bool defaultValue = false) const; void setForceAlwaysFullSizedOutline(bool value) const; enum SessionOnStartup { SOS_BlankSession, SOS_PreviousSession, SOS_ShowSessionManager }; SessionOnStartup sessionOnStartup(bool defaultValue = false) const; void setSessionOnStartup(SessionOnStartup value); bool saveSessionOnQuit(bool defaultValue) const; void setSaveSessionOnQuit(bool value); qreal outlineSizeMinimum(bool defaultValue = false) const; void setOutlineSizeMinimum(qreal outlineSizeMinimum) const; qreal selectionViewSizeMinimum(bool defaultValue = false) const; void setSelectionViewSizeMinimum(qreal outlineSizeMinimum) const; int autoSaveInterval(bool defaultValue = false) const; void setAutoSaveInterval(int seconds) const; bool backupFile(bool defaultValue = false) const; void setBackupFile(bool backupFile) const; bool showFilterGallery(bool defaultValue = false) const; void setShowFilterGallery(bool showFilterGallery) const; bool showFilterGalleryLayerMaskDialog(bool defaultValue = false) const; void setShowFilterGalleryLayerMaskDialog(bool showFilterGallery) const; // OPENGL_SUCCESS, TRY_OPENGL, OPENGL_NOT_TRIED, OPENGL_FAILED QString canvasState(bool defaultValue = false) const; void setCanvasState(const QString& state) const; bool toolOptionsPopupDetached(bool defaultValue = false) const; void setToolOptionsPopupDetached(bool detached) const; bool paintopPopupDetached(bool defaultValue = false) const; void setPaintopPopupDetached(bool detached) const; QString pressureTabletCurve(bool defaultValue = false) const; void setPressureTabletCurve(const QString& curveString) const; bool useWin8PointerInput(bool defaultValue = false) const; void setUseWin8PointerInput(bool value); static bool useWin8PointerInputNoApp(QSettings *settings, bool defaultValue = false); static void setUseWin8PointerInputNoApp(QSettings *settings, bool value); bool useRightMiddleTabletButtonWorkaround(bool defaultValue = false) const; void setUseRightMiddleTabletButtonWorkaround(bool value); qreal vastScrolling(bool defaultValue = false) const; void setVastScrolling(const qreal factor) const; int presetChooserViewMode(bool defaultValue = false) const; void setPresetChooserViewMode(const int mode) const; int presetIconSize(bool defaultValue = false) const; void setPresetIconSize(const int value) const; bool firstRun(bool defaultValue = false) const; void setFirstRun(const bool firstRun) const; bool clicklessSpacePan(bool defaultValue = false) const; void setClicklessSpacePan(const bool toggle) const; int horizontalSplitLines(bool defaultValue = false) const; void setHorizontalSplitLines(const int numberLines) const; int verticalSplitLines(bool defaultValue = false) const; void setVerticalSplitLines(const int numberLines) const; bool hideDockersFullscreen(bool defaultValue = false) const; void setHideDockersFullscreen(const bool value) const; bool showDockers(bool defaultValue = false) const; void setShowDockers(const bool value) const; bool showStatusBar(bool defaultValue = false) const; void setShowStatusBar(const bool value) const; bool hideMenuFullscreen(bool defaultValue = false) const; void setHideMenuFullscreen(const bool value) const; bool hideScrollbarsFullscreen(bool defaultValue = false) const; void setHideScrollbarsFullscreen(const bool value) const; bool hideStatusbarFullscreen(bool defaultValue = false) const; void setHideStatusbarFullscreen(const bool value) const; bool hideTitlebarFullscreen(bool defaultValue = false) const; void setHideTitlebarFullscreen(const bool value) const; bool hideToolbarFullscreen(bool defaultValue = false) const; void setHideToolbarFullscreen(const bool value) const; bool fullscreenMode(bool defaultValue = false) const; void setFullscreenMode(const bool value) const; QStringList favoriteCompositeOps(bool defaultValue = false) const; void setFavoriteCompositeOps(const QStringList& compositeOps) const; QString exportConfigurationXML(const QString &filterId, bool defaultValue = false) const; KisPropertiesConfigurationSP exportConfiguration(const QString &filterId, bool defaultValue = false) const; void setExportConfiguration(const QString &filterId, KisPropertiesConfigurationSP properties) const; QString importConfiguration(const QString &filterId, bool defaultValue = false) const; void setImportConfiguration(const QString &filterId, KisPropertiesConfigurationSP properties) const; bool useOcio(bool defaultValue = false) const; void setUseOcio(bool useOCIO) const; int favoritePresets(bool defaultValue = false) const; void setFavoritePresets(const int value); bool levelOfDetailEnabled(bool defaultValue = false) const; void setLevelOfDetailEnabled(bool value); KisOcioConfiguration ocioConfiguration(bool defaultValue = false) const; void setOcioConfiguration(const KisOcioConfiguration &cfg); enum OcioColorManagementMode { INTERNAL = 0, OCIO_CONFIG, OCIO_ENVIRONMENT }; OcioColorManagementMode ocioColorManagementMode(bool defaultValue = false) const; void setOcioColorManagementMode(OcioColorManagementMode mode) const; int ocioLutEdgeSize(bool defaultValue = false) const; void setOcioLutEdgeSize(int value); bool ocioLockColorVisualRepresentation(bool defaultValue = false) const; void setOcioLockColorVisualRepresentation(bool value); bool useSystemMonitorProfile(bool defaultValue = false) const; void setUseSystemMonitorProfile(bool _useSystemMonitorProfile) const; QString defaultPalette(bool defaultValue = false) const; void setDefaultPalette(const QString& name) const; QString toolbarSlider(int sliderNumber, bool defaultValue = false) const; void setToolbarSlider(int sliderNumber, const QString &slider); int layerThumbnailSize(bool defaultValue = false) const; void setLayerThumbnailSize(int size); bool sliderLabels(bool defaultValue = false) const; void setSliderLabels(bool enabled); QString currentInputProfile(bool defaultValue = false) const; void setCurrentInputProfile(const QString& name); bool presetStripVisible(bool defaultValue = false) const; void setPresetStripVisible(bool visible); bool scratchpadVisible(bool defaultValue = false) const; void setScratchpadVisible(bool visible); bool showSingleChannelAsColor(bool defaultValue = false) const; void setShowSingleChannelAsColor(bool asColor); bool hidePopups(bool defaultValue = false) const; void setHidePopups(bool hidepopups); int numDefaultLayers(bool defaultValue = false) const; void setNumDefaultLayers(int num); quint8 defaultBackgroundOpacity(bool defaultValue = false) const; void setDefaultBackgroundOpacity(quint8 value); QColor defaultBackgroundColor(bool defaultValue = false) const; void setDefaultBackgroundColor(const QColor &value); enum BackgroundStyle { RASTER_LAYER = 0, CANVAS_COLOR = 1, FILL_LAYER = 2 }; BackgroundStyle defaultBackgroundStyle(bool defaultValue = false) const; void setDefaultBackgroundStyle(BackgroundStyle value); int lineSmoothingType(bool defaultValue = false) const; void setLineSmoothingType(int value); qreal lineSmoothingDistance(bool defaultValue = false) const; void setLineSmoothingDistance(qreal value); qreal lineSmoothingTailAggressiveness(bool defaultValue = false) const; void setLineSmoothingTailAggressiveness(qreal value); bool lineSmoothingSmoothPressure(bool defaultValue = false) const; void setLineSmoothingSmoothPressure(bool value); bool lineSmoothingScalableDistance(bool defaultValue = false) const; void setLineSmoothingScalableDistance(bool value); qreal lineSmoothingDelayDistance(bool defaultValue = false) const; void setLineSmoothingDelayDistance(qreal value); bool lineSmoothingUseDelayDistance(bool defaultValue = false) const; void setLineSmoothingUseDelayDistance(bool value); bool lineSmoothingFinishStabilizedCurve(bool defaultValue = false) const; void setLineSmoothingFinishStabilizedCurve(bool value); bool lineSmoothingStabilizeSensors(bool defaultValue = false) const; void setLineSmoothingStabilizeSensors(bool value); int tabletEventsDelay(bool defaultValue = false) const; void setTabletEventsDelay(int value); bool trackTabletEventLatency(bool defaultValue = false) const; void setTrackTabletEventLatency(bool value); bool testingAcceptCompressedTabletEvents(bool defaultValue = false) const; void setTestingAcceptCompressedTabletEvents(bool value); bool shouldEatDriverShortcuts(bool defaultValue = false) const; bool testingCompressBrushEvents(bool defaultValue = false) const; void setTestingCompressBrushEvents(bool value); const KoColorSpace* customColorSelectorColorSpace(bool defaultValue = false) const; void setCustomColorSelectorColorSpace(const KoColorSpace *cs); bool useDirtyPresets(bool defaultValue = false) const; void setUseDirtyPresets(bool value); bool useEraserBrushSize(bool defaultValue = false) const; void setUseEraserBrushSize(bool value); bool useEraserBrushOpacity(bool defaultValue = false) const; void setUseEraserBrushOpacity(bool value); QString getMDIBackgroundColor(bool defaultValue = false) const; void setMDIBackgroundColor(const QString & v) const; QString getMDIBackgroundImage(bool defaultValue = false) const; void setMDIBackgroundImage(const QString & fileName) const; int workaroundX11SmoothPressureSteps(bool defaultValue = false) const; bool showCanvasMessages(bool defaultValue = false) const; void setShowCanvasMessages(bool show); bool compressKra(bool defaultValue = false) const; void setCompressKra(bool compress); bool trimKra(bool defaultValue = false) const; void setTrimKra(bool trim); bool toolOptionsInDocker(bool defaultValue = false) const; void setToolOptionsInDocker(bool inDocker); bool kineticScrollingEnabled(bool defaultValue = false) const; void setKineticScrollingEnabled(bool enabled); int kineticScrollingGesture(bool defaultValue = false) const; void setKineticScrollingGesture(int kineticScroll); int kineticScrollingSensitivity(bool defaultValue = false) const; void setKineticScrollingSensitivity(int sensitivity); bool kineticScrollingHiddenScrollbars(bool defaultValue = false) const; void setKineticScrollingHideScrollbars(bool scrollbar); void setEnableOpenGLFramerateLogging(bool value) const; bool enableOpenGLFramerateLogging(bool defaultValue = false) const; void setEnableBrushSpeedLogging(bool value) const; bool enableBrushSpeedLogging(bool defaultValue = false) const; void setEnableAmdVectorizationWorkaround(bool value); bool enableAmdVectorizationWorkaround(bool defaultValue = false) const; void setDisableAVXOptimizations(bool value); bool disableAVXOptimizations(bool defaultValue = false) const; bool animationDropFrames(bool defaultValue = false) const; void setAnimationDropFrames(bool value); bool autoPinLayersToTimeline(bool defaultValue = false) const; void setAutoPinLayersToTimeline(bool value); int scrubbingUpdatesDelay(bool defaultValue = false) const; void setScrubbingUpdatesDelay(int value); int scrubbingAudioUpdatesDelay(bool defaultValue = false) const; void setScrubbingAudioUpdatesDelay(int value); int audioOffsetTolerance(bool defaultValue = false) const; void setAudioOffsetTolerance(int value); bool switchSelectionCtrlAlt(bool defaultValue = false) const; void setSwitchSelectionCtrlAlt(bool value); bool convertToImageColorspaceOnImport(bool defaultValue = false) const; void setConvertToImageColorspaceOnImport(bool value); int stabilizerSampleSize(bool defaultValue = false) const; void setStabilizerSampleSize(int value); bool stabilizerDelayedPaint(bool defaultValue = false) const; void setStabilizerDelayedPaint(bool value); bool showBrushHud(bool defaultValue = false) const; void setShowBrushHud(bool value); QString brushHudSetting(bool defaultValue = false) const; void setBrushHudSetting(const QString &value) const; bool calculateAnimationCacheInBackground(bool defaultValue = false) const; void setCalculateAnimationCacheInBackground(bool value); QColor defaultAssistantsColor(bool defaultValue = false) const; void setDefaultAssistantsColor(const QColor &color) const; bool autoSmoothBezierCurves(bool defaultValue = false) const; void setAutoSmoothBezierCurves(bool value); bool activateTransformToolAfterPaste(bool defaultValue = false) const; void setActivateTransformToolAfterPaste(bool value); enum RootSurfaceFormat { BT709_G22 = 0, BT709_G10, BT2020_PQ }; RootSurfaceFormat rootSurfaceFormat(bool defaultValue = false) const; void setRootSurfaceFormat(RootSurfaceFormat value); static RootSurfaceFormat rootSurfaceFormat(QSettings *displayrc, bool defaultValue = false); static void setRootSurfaceFormat(QSettings *displayrc, RootSurfaceFormat value); bool useZip64(bool defaultValue = false) const; void setUseZip64(bool value); bool convertLayerColorSpaceInProperties(bool defaultValue = false) const; void setConvertLayerColorSpaceInProperties(bool value); template void writeEntry(const QString& name, const T& value) { m_cfg.writeEntry(name, value); } template void writeList(const QString& name, const QList& value) { m_cfg.writeEntry(name, value); } template T readEntry(const QString& name, const T& defaultValue=T()) { return m_cfg.readEntry(name, defaultValue); } template QList readList(const QString& name, const QList& defaultValue=QList()) { return m_cfg.readEntry(name, defaultValue); } /// get the profile the color management system has stored for the given screen static const KoColorProfile* getScreenProfile(int screen); private: KisConfig(const KisConfig&); KisConfig& operator=(const KisConfig&) const; private: mutable KConfigGroup m_cfg; bool m_readOnly; }; #endif // KIS_CONFIG_H_ diff --git a/libs/ui/kis_layer_manager.cc b/libs/ui/kis_layer_manager.cc index f2e611940e..ad94ff5480 100644 --- a/libs/ui/kis_layer_manager.cc +++ b/libs/ui/kis_layer_manager.cc @@ -1,995 +1,1003 @@ /* * 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 "kis_projection_leaf.h" #include "KisSaveGroupVisitor.h" KisLayerManager::KisLayerManager(KisViewManager * view) : m_view(view) , m_commandsAdapter(new KisNodeCommandsAdapter(m_view)) { } 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) { 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->cloneWithResourcesSnapshot(), configAfter->cloneWithResourcesSnapshot()); // 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(configBefore->cloneWithResourcesSnapshot()); adjustmentLayer->setDirty(); } } } else if (generatorLayer && !multipleLayersSelected) { KisFilterConfigurationSP configBefore(generatorLayer->filter()); Q_ASSERT(configBefore); 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(); + + QString newCompositeOp = + source->projectionLeaf()->isLayer() ? + source->compositeOpId() : COMPOSITE_OVER; + 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()); + clone->setDefaultPixel( + srcDevice->defaultPixel().convertedTo( + 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); } 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()); KisConfig cfg(true); layer->setPinnedToTimeline(cfg.autoPinLayersToTimeline()); 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 ? filter->cloneWithResourcesSnapshot() : 0, 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 ? generator->cloneWithResourcesSnapshot() : 0, 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().dynamicCast()); } 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().dynamicCast(); } else { oldStyle = toQShared(new KisPSDLayerStyle()); } KisDlgLayerStyle dlg(oldStyle->clone().dynamicCast(), 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_node_model.cpp b/libs/ui/kis_node_model.cpp index b6fe3bed6e..cc627f952e 100644 --- a/libs/ui/kis_node_model.cpp +++ b/libs/ui/kis_node_model.cpp @@ -1,753 +1,745 @@ /* * Copyright (c) 2007 Boudewijn Rempt * Copyright (c) 2008 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_node_model.h" #include #include #include #include #include #include #include "kis_mimedata.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_dummies_facade_base.h" #include "kis_node_dummies_graph.h" #include "kis_model_index_converter.h" #include "kis_model_index_converter_show_all.h" #include "kis_node_selection_adapter.h" #include "kis_node_insertion_adapter.h" #include "kis_node_manager.h" #include #include #include "kis_config.h" #include "kis_config_notifier.h" #include "kis_signal_auto_connection.h" #include "kis_signal_compressor.h" struct KisNodeModel::Private { Private() : updateCompressor(100, KisSignalCompressor::FIRST_ACTIVE) {} KisImageWSP image; KisShapeController *shapeController = 0; KisNodeSelectionAdapter *nodeSelectionAdapter = 0; KisNodeInsertionAdapter *nodeInsertionAdapter = 0; KisSelectionActionsAdapter *selectionActionsAdapter = 0; KisNodeDisplayModeAdapter *nodeDisplayModeAdapter = 0; KisNodeManager *nodeManager = 0; KisSignalAutoConnectionsStore nodeDisplayModeAdapterConnections; QList updateQueue; KisSignalCompressor updateCompressor; KisModelIndexConverterBase *indexConverter = 0; QPointer dummiesFacade = 0; bool needFinishRemoveRows = false; bool needFinishInsertRows = false; bool showRootLayer = false; bool showGlobalSelection = false; QPersistentModelIndex activeNodeIndex; QPointer parentOfRemovedNode = 0; QSet dropEnabled; }; KisNodeModel::KisNodeModel(QObject * parent) : QAbstractItemModel(parent) , m_d(new Private) { connect(&m_d->updateCompressor, SIGNAL(timeout()), SLOT(processUpdateQueue())); } KisNodeModel::~KisNodeModel() { delete m_d->indexConverter; delete m_d; } KisNodeSP KisNodeModel::nodeFromIndex(const QModelIndex &index) const { Q_ASSERT(index.isValid()); KisNodeDummy *dummy = m_d->indexConverter->dummyFromIndex(index); if (dummy) { return dummy->node(); } return 0; } QModelIndex KisNodeModel::indexFromNode(KisNodeSP node) const { KisNodeDummy *dummy = m_d->dummiesFacade->dummyForNode(node); if(dummy) return m_d->indexConverter->indexFromDummy(dummy); return QModelIndex(); } bool KisNodeModel::belongsToIsolatedGroup(KisImageSP image, KisNodeSP node, KisDummiesFacadeBase *dummiesFacade) { KisNodeSP isolatedRoot = image->isolatedModeRoot(); if (!isolatedRoot) return true; KisNodeDummy *isolatedRootDummy = dummiesFacade->dummyForNode(isolatedRoot); KisNodeDummy *dummy = dummiesFacade->dummyForNode(node); while (dummy) { if (dummy == isolatedRootDummy) { return true; } dummy = dummy->parent(); } return false; } bool KisNodeModel::belongsToIsolatedGroup(KisNodeSP node) const { return belongsToIsolatedGroup(m_d->image, node, m_d->dummiesFacade); } void KisNodeModel::resetIndexConverter() { delete m_d->indexConverter; m_d->indexConverter = 0; if(m_d->dummiesFacade) { m_d->indexConverter = createIndexConverter(); } } KisModelIndexConverterBase *KisNodeModel::createIndexConverter() { if(m_d->showRootLayer) { return new KisModelIndexConverterShowAll(m_d->dummiesFacade, this); } else { return new KisModelIndexConverter(m_d->dummiesFacade, this, m_d->showGlobalSelection); } } void KisNodeModel::regenerateItems(KisNodeDummy *dummy) { const QModelIndex &index = m_d->indexConverter->indexFromDummy(dummy); emit dataChanged(index, index); dummy = dummy->firstChild(); while (dummy) { regenerateItems(dummy); dummy = dummy->nextSibling(); } } void KisNodeModel::slotIsolatedModeChanged() { regenerateItems(m_d->dummiesFacade->rootDummy()); } bool KisNodeModel::showGlobalSelection() const { return m_d->nodeDisplayModeAdapter ? m_d->nodeDisplayModeAdapter->showGlobalSelectionMask() : false; } void KisNodeModel::setShowGlobalSelection(bool value) { if (m_d->nodeDisplayModeAdapter) { m_d->nodeDisplayModeAdapter->setShowGlobalSelectionMask(value); } } void KisNodeModel::slotNodeDisplayModeChanged(bool showRootNode, bool showGlobalSelectionMask) { const bool oldShowRootLayer = m_d->showRootLayer; const bool oldShowGlobalSelection = m_d->showGlobalSelection; m_d->showRootLayer = showRootNode; m_d->showGlobalSelection = showGlobalSelectionMask; if (m_d->showRootLayer != oldShowRootLayer || m_d->showGlobalSelection != oldShowGlobalSelection) { resetIndexConverter(); beginResetModel(); endResetModel(); } } void KisNodeModel::progressPercentageChanged(int, const KisNodeSP node) { if(!m_d->dummiesFacade) return; // Need to check here as the node might already be removed, but there might // still be some signals arriving from another thread if (m_d->dummiesFacade->hasDummyForNode(node)) { QModelIndex index = indexFromNode(node); emit dataChanged(index, index); } } KisModelIndexConverterBase * KisNodeModel::indexConverter() const { return m_d->indexConverter; } KisDummiesFacadeBase *KisNodeModel::dummiesFacade() const { return m_d->dummiesFacade; } void KisNodeModel::connectDummy(KisNodeDummy *dummy, bool needConnect) { KisNodeSP node = dummy->node(); if (!node) { qWarning() << "Dummy node has no node!" << dummy << dummy->node(); return; } KisNodeProgressProxy *progressProxy = node->nodeProgressProxy(); if(progressProxy) { if(needConnect) { connect(progressProxy, SIGNAL(percentageChanged(int,KisNodeSP)), SLOT(progressPercentageChanged(int,KisNodeSP))); } else { progressProxy->disconnect(this); } } } void KisNodeModel::connectDummies(KisNodeDummy *dummy, bool needConnect) { connectDummy(dummy, needConnect); dummy = dummy->firstChild(); while(dummy) { connectDummies(dummy, needConnect); dummy = dummy->nextSibling(); } } void KisNodeModel::setDummiesFacade(KisDummiesFacadeBase *dummiesFacade, KisImageWSP image, KisShapeController *shapeController, KisSelectionActionsAdapter *selectionActionsAdapter, KisNodeManager *nodeManager) { QPointer oldDummiesFacade(m_d->dummiesFacade); KisShapeController *oldShapeController = m_d->shapeController; m_d->shapeController = shapeController; m_d->nodeManager = nodeManager; m_d->nodeSelectionAdapter = nodeManager ? nodeManager->nodeSelectionAdapter() : nullptr; m_d->nodeInsertionAdapter = nodeManager ? nodeManager->nodeInsertionAdapter() : nullptr; m_d->selectionActionsAdapter = selectionActionsAdapter; m_d->nodeDisplayModeAdapterConnections.clear(); m_d->nodeDisplayModeAdapter = nodeManager ? nodeManager->nodeDisplayModeAdapter() : nullptr; if (m_d->nodeDisplayModeAdapter) { m_d->nodeDisplayModeAdapterConnections.addConnection( m_d->nodeDisplayModeAdapter, SIGNAL(sigNodeDisplayModeChanged(bool,bool)), this, SLOT(slotNodeDisplayModeChanged(bool,bool))); // cold initialization m_d->showGlobalSelection = m_d->nodeDisplayModeAdapter->showGlobalSelectionMask(); m_d->showRootLayer = false; } if (oldDummiesFacade && m_d->image) { m_d->image->disconnect(this); oldDummiesFacade->disconnect(this); connectDummies(m_d->dummiesFacade->rootDummy(), false); } m_d->image = image; m_d->dummiesFacade = dummiesFacade; m_d->parentOfRemovedNode = 0; resetIndexConverter(); if (m_d->dummiesFacade) { KisNodeDummy *rootDummy = m_d->dummiesFacade->rootDummy(); if (rootDummy) { connectDummies(rootDummy, true); } connect(m_d->dummiesFacade, SIGNAL(sigBeginInsertDummy(KisNodeDummy*,int,QString)), SLOT(slotBeginInsertDummy(KisNodeDummy*,int,QString))); connect(m_d->dummiesFacade, SIGNAL(sigEndInsertDummy(KisNodeDummy*)), SLOT(slotEndInsertDummy(KisNodeDummy*))); connect(m_d->dummiesFacade, SIGNAL(sigBeginRemoveDummy(KisNodeDummy*)), SLOT(slotBeginRemoveDummy(KisNodeDummy*))); connect(m_d->dummiesFacade, SIGNAL(sigEndRemoveDummy()), SLOT(slotEndRemoveDummy())); connect(m_d->dummiesFacade, SIGNAL(sigDummyChanged(KisNodeDummy*)), SLOT(slotDummyChanged(KisNodeDummy*))); if (m_d->image.isValid()) { connect(m_d->image, SIGNAL(sigIsolatedModeChanged()), SLOT(slotIsolatedModeChanged())); } } if (m_d->dummiesFacade != oldDummiesFacade || m_d->shapeController != oldShapeController) { beginResetModel(); endResetModel(); } } void KisNodeModel::slotBeginInsertDummy(KisNodeDummy *parent, int index, const QString &metaObjectType) { int row = 0; QModelIndex parentIndex; bool willAdd = m_d->indexConverter->indexFromAddedDummy(parent, index, metaObjectType, parentIndex, row); if(willAdd) { beginInsertRows(parentIndex, row, row); m_d->needFinishInsertRows = true; } } void KisNodeModel::slotEndInsertDummy(KisNodeDummy *dummy) { if(m_d->needFinishInsertRows) { connectDummy(dummy, true); endInsertRows(); m_d->needFinishInsertRows = false; } } void KisNodeModel::slotBeginRemoveDummy(KisNodeDummy *dummy) { if (!dummy) return; // FIXME: is it really what we want? m_d->updateCompressor.stop(); m_d->updateQueue.clear(); m_d->parentOfRemovedNode = dummy->parent(); QModelIndex parentIndex; if (m_d->parentOfRemovedNode) { parentIndex = m_d->indexConverter->indexFromDummy(m_d->parentOfRemovedNode); } QModelIndex itemIndex = m_d->indexConverter->indexFromDummy(dummy); if (itemIndex.isValid()) { connectDummy(dummy, false); beginRemoveRows(parentIndex, itemIndex.row(), itemIndex.row()); m_d->needFinishRemoveRows = true; } } void KisNodeModel::slotEndRemoveDummy() { if(m_d->needFinishRemoveRows) { endRemoveRows(); m_d->needFinishRemoveRows = false; } } void KisNodeModel::slotDummyChanged(KisNodeDummy *dummy) { if (!m_d->updateQueue.contains(dummy)) { m_d->updateQueue.append(dummy); } m_d->updateCompressor.start(); } void addChangedIndex(const QModelIndex &idx, QSet *indexes) { if (!idx.isValid() || indexes->contains(idx)) return; indexes->insert(idx); const int rowCount = idx.model()->rowCount(idx); for (int i = 0; i < rowCount; i++) { addChangedIndex(idx.model()->index(i, 0, idx), indexes); } } void KisNodeModel::processUpdateQueue() { QSet indexes; Q_FOREACH (KisNodeDummy *dummy, m_d->updateQueue) { QModelIndex index = m_d->indexConverter->indexFromDummy(dummy); addChangedIndex(index, &indexes); } Q_FOREACH (const QModelIndex &index, indexes) { emit dataChanged(index, index); } m_d->updateQueue.clear(); } QModelIndex KisNodeModel::index(int row, int col, const QModelIndex &parent) const { if(!m_d->dummiesFacade || !hasIndex(row, col, parent)) return QModelIndex(); QModelIndex itemIndex; KisNodeDummy *dummy = m_d->indexConverter->dummyFromRow(row, parent); if(dummy) { itemIndex = m_d->indexConverter->indexFromDummy(dummy); } return itemIndex; } int KisNodeModel::rowCount(const QModelIndex &parent) const { if(!m_d->dummiesFacade) return 0; return m_d->indexConverter->rowCount(parent); } int KisNodeModel::columnCount(const QModelIndex&) const { return 1; } QModelIndex KisNodeModel::parent(const QModelIndex &index) const { if(!m_d->dummiesFacade || !index.isValid()) return QModelIndex(); KisNodeDummy *dummy = m_d->indexConverter->dummyFromIndex(index); KisNodeDummy *parentDummy = dummy->parent(); QModelIndex parentIndex; if(parentDummy) { parentIndex = m_d->indexConverter->indexFromDummy(parentDummy); } return parentIndex; } QVariant KisNodeModel::data(const QModelIndex &index, int role) const { if (!m_d->dummiesFacade || !index.isValid() || !m_d->image.isValid()) return QVariant(); KisNodeSP node = nodeFromIndex(index); switch (role) { case Qt::DisplayRole: return node->name(); case Qt::DecorationRole: return node->icon(); case Qt::EditRole: return node->name(); case Qt::SizeHintRole: return m_d->image->size(); // FIXME case Qt::TextColorRole: return belongsToIsolatedGroup(node) && !node->projectionLeaf()->isDroppedNode() ? QVariant() : QVariant(QColor(Qt::gray)); case Qt::FontRole: { QFont baseFont; if (node->projectionLeaf()->isDroppedNode()) { baseFont.setStrikeOut(true); } if (m_d->activeNodeIndex == index) { baseFont.setBold(true); } return baseFont; } case KisNodeModel::PropertiesRole: return QVariant::fromValue(node->sectionModelProperties()); case KisNodeModel::AspectRatioRole: return double(m_d->image->width()) / m_d->image->height(); case KisNodeModel::ProgressRole: { KisNodeProgressProxy *proxy = node->nodeProgressProxy(); return proxy ? proxy->percentage() : -1; } case KisNodeModel::ActiveRole: { return m_d->activeNodeIndex == index; } case KisNodeModel::ShouldGrayOutRole: { return !node->visible(true); } case KisNodeModel::ColorLabelIndexRole: { return node->colorLabelIndex(); } case KisNodeModel::DropReasonRole: { QString result; KisProjectionLeaf::NodeDropReason reason = node->projectionLeaf()->dropReason(); if (reason == KisProjectionLeaf::DropPassThroughMask) { result = i18nc("@info:tooltip", "Disabled: masks on pass-through groups are not supported!"); } else if (reason == KisProjectionLeaf::DropPassThroughClone) { result = i18nc("@info:tooltip", "Disabled: cloning pass-through groups is not supported!"); } return result; } default: if (role >= int(KisNodeModel::BeginThumbnailRole) && belongsToIsolatedGroup(node)) { const int maxSize = role - int(KisNodeModel::BeginThumbnailRole); - - QSize size = node->extent().size(); - size.scale(maxSize, maxSize, Qt::KeepAspectRatio); - if (size.width() == 0 || size.height() == 0) { - // No thumbnail can be shown if there isn't width or height... - return QVariant(); - } - - return node->createThumbnail(size.width(), size.height()); + return node->createThumbnail(maxSize, maxSize, Qt::KeepAspectRatio); } else { return QVariant(); } } return QVariant(); } Qt::ItemFlags KisNodeModel::flags(const QModelIndex &index) const { if(!m_d->dummiesFacade || !index.isValid()) return Qt::ItemIsDropEnabled; Qt::ItemFlags flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsEditable; if (m_d->dropEnabled.contains(index.internalId())) { flags |= Qt::ItemIsDropEnabled; } return flags; } bool KisNodeModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (role == KisNodeModel::DropEnabled) { const QMimeData *mimeData = static_cast(value.value()); setDropEnabled(mimeData); return true; } if (role == KisNodeModel::ActiveRole || role == KisNodeModel::AlternateActiveRole) { QModelIndex parentIndex; if (!index.isValid() && m_d->parentOfRemovedNode && m_d->dummiesFacade && m_d->indexConverter) { parentIndex = m_d->indexConverter->indexFromDummy(m_d->parentOfRemovedNode); m_d->parentOfRemovedNode = 0; } KisNodeSP activatedNode; if (index.isValid() && value.toBool()) { activatedNode = nodeFromIndex(index); } else if (parentIndex.isValid() && value.toBool()) { activatedNode = nodeFromIndex(parentIndex); } else { activatedNode = 0; } QModelIndex newActiveNode = activatedNode ? indexFromNode(activatedNode) : QModelIndex(); if (role == KisNodeModel::ActiveRole && value.toBool() && m_d->activeNodeIndex == newActiveNode) { return true; } m_d->activeNodeIndex = newActiveNode; if (m_d->nodeSelectionAdapter) { m_d->nodeSelectionAdapter->setActiveNode(activatedNode); } if (role == KisNodeModel::AlternateActiveRole) { emit toggleIsolateActiveNode(); } emit dataChanged(index, index); return true; } if(!m_d->dummiesFacade || !index.isValid()) return false; bool result = true; bool shouldUpdate = true; bool shouldUpdateRecursively = false; KisNodeSP node = nodeFromIndex(index); switch (role) { case Qt::DisplayRole: case Qt::EditRole: m_d->nodeManager->setNodeName(node, value.toString()); break; case KisNodeModel::PropertiesRole: { // don't record undo/redo for visibility, locked or alpha locked changes KisBaseNode::PropertyList proplist = value.value(); m_d->nodeManager->trySetNodeProperties(node, m_d->image, proplist); shouldUpdateRecursively = true; break; } case KisNodeModel::SelectOpaqueRole: if (node && m_d->selectionActionsAdapter) { SelectionAction action = SelectionAction(value.toInt()); m_d->selectionActionsAdapter->selectOpaqueOnNode(node, action); } shouldUpdate = false; break; default: result = false; } if (result && shouldUpdate) { if (shouldUpdateRecursively) { QSet indexes; addChangedIndex(index, &indexes); Q_FOREACH (const QModelIndex &index, indexes) { emit dataChanged(index, index); } } else { emit dataChanged(index, index); } } return result; } Qt::DropActions KisNodeModel::supportedDragActions() const { return Qt::CopyAction | Qt::MoveAction; } Qt::DropActions KisNodeModel::supportedDropActions() const { return Qt::MoveAction | Qt::CopyAction; } bool KisNodeModel::hasDummiesFacade() { return m_d->dummiesFacade != 0; } QStringList KisNodeModel::mimeTypes() const { QStringList types; types << QLatin1String("application/x-krita-node"); types << QLatin1String("application/x-qt-image"); return types; } QMimeData * KisNodeModel::mimeData(const QModelIndexList &indexes) const { KisNodeList nodes; Q_FOREACH (const QModelIndex &idx, indexes) { nodes << nodeFromIndex(idx); } return KisMimeData::mimeForLayers(nodes, m_d->image); } bool KisNodeModel::dropMimeData(const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent) { Q_UNUSED(column); bool copyNode = (action == Qt::CopyAction); KisNodeDummy *parentDummy = 0; KisNodeDummy *aboveThisDummy = 0; parentDummy = parent.isValid() ? m_d->indexConverter->dummyFromIndex(parent) : m_d->dummiesFacade->rootDummy(); if (row == -1) { aboveThisDummy = parent.isValid() ? parentDummy->lastChild() : 0; } else { aboveThisDummy = row < m_d->indexConverter->rowCount(parent) ? m_d->indexConverter->dummyFromRow(row, parent) : 0; } return KisMimeData::insertMimeLayers(data, m_d->image, m_d->shapeController, parentDummy, aboveThisDummy, copyNode, m_d->nodeInsertionAdapter); } bool KisNodeModel::canDropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) const { if (parent.isValid()) { // drop occurred on an item. always return true as returning false will mess up // QT5's drag handling (see KisNodeModel::setDropEnabled). return true; } else { return QAbstractItemModel::canDropMimeData(data, action, row, column, parent); } } void KisNodeModel::setDropEnabled(const QMimeData *data) { // what happens here should really happen in KisNodeModel::canDropMimeData(), but QT5 // will mess up if an item's Qt::ItemIsDropEnabled does not match what is returned by // canDropMimeData; specifically, if we set the flag, but decide in canDropMimeData() // later on that an "onto" drag is not allowed, QT will display an drop indicator for // insertion, but not perform any drop when the mouse is released. // the only robust implementation seems to set all flags correctly, which is done here. bool copyNode = false; KisNodeList nodes = KisMimeData::loadNodesFast(data, m_d->image, m_d->shapeController, copyNode); m_d->dropEnabled.clear(); updateDropEnabled(nodes); } void KisNodeModel::updateDropEnabled(const QList &nodes, QModelIndex parent) { for (int r = 0; r < rowCount(parent); r++) { QModelIndex idx = index(r, 0, parent); KisNodeSP target = nodeFromIndex(idx); bool dropEnabled = true; Q_FOREACH (const KisNodeSP &node, nodes) { if (!target->allowAsChild(node)) { dropEnabled = false; break; } } if (dropEnabled) { m_d->dropEnabled.insert(idx.internalId()); } emit dataChanged(idx, idx); // indicate to QT that flags() have changed if (hasChildren(idx)) { updateDropEnabled(nodes, idx); } } } diff --git a/libs/ui/kis_selection_decoration.cc b/libs/ui/kis_selection_decoration.cc index c6253a2b03..c7589f36d3 100644 --- a/libs/ui/kis_selection_decoration.cc +++ b/libs/ui/kis_selection_decoration.cc @@ -1,226 +1,226 @@ /* * Copyright (c) 2008 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 "kis_selection_decoration.h" #include #include #include #include #include "kis_types.h" #include "KisViewManager.h" #include "kis_selection.h" #include "kis_image.h" #include "flake/kis_shape_selection.h" #include "kis_pixel_selection.h" #include "kis_update_outline_job.h" #include "kis_selection_manager.h" #include "canvas/kis_canvas2.h" #include "kis_canvas_resource_provider.h" #include "kis_coordinates_converter.h" #include "kis_config.h" #include "kis_config_notifier.h" #include "kis_image_config.h" #include "KisImageConfigNotifier.h" #include "kis_painting_tweaks.h" #include "KisView.h" #include "kis_selection_mask.h" #include static const unsigned int ANT_LENGTH = 4; static const unsigned int ANT_SPACE = 4; static const unsigned int ANT_ADVANCE_WIDTH = ANT_LENGTH + ANT_SPACE; KisSelectionDecoration::KisSelectionDecoration(QPointerview) : KisCanvasDecoration("selection", view), m_signalCompressor(40 /*ms*/, KisSignalCompressor::FIRST_ACTIVE), m_offset(0), m_mode(Ants) { KisPaintingTweaks::initAntsPen(&m_antsPen, &m_outlinePen, ANT_LENGTH, ANT_SPACE); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); connect(KisImageConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); slotConfigChanged(); m_antsTimer = new QTimer(this); m_antsTimer->setInterval(150); m_antsTimer->setSingleShot(false); connect(m_antsTimer, SIGNAL(timeout()), SLOT(antsAttackEvent())); connect(&m_signalCompressor, SIGNAL(timeout()), SLOT(slotStartUpdateSelection())); // selections should be at the top of the stack setPriority(100); } KisSelectionDecoration::~KisSelectionDecoration() { } KisSelectionDecoration::Mode KisSelectionDecoration::mode() const { return m_mode; } void KisSelectionDecoration::setMode(Mode mode) { m_mode = mode; selectionChanged(); } bool KisSelectionDecoration::selectionIsActive() { KisSelectionSP selection = view()->selection(); return visible() && selection && - (selection->hasPixelSelection() || selection->hasShapeSelection()) && + (selection->hasNonEmptyPixelSelection() || selection->hasNonEmptyShapeSelection()) && selection->isVisible(); } void KisSelectionDecoration::selectionChanged() { KisSelectionMaskSP mask = qobject_cast(view()->currentNode().data()); if (!mask || !mask->active() || !mask->visible(true)) { mask = 0; } if (!view()->isCurrent() || view()->viewManager()->mainWindow() == KisPart::instance()->currentMainwindow()) { view()->image()->setOverlaySelectionMask(mask); } KisSelectionSP selection = view()->selection(); if (!mask && selection && selectionIsActive()) { if ((m_mode == Ants && selection->outlineCacheValid()) || (m_mode == Mask && selection->thumbnailImageValid())) { m_signalCompressor.stop(); if (m_mode == Ants) { m_outlinePath = selection->outlineCache(); m_antsTimer->start(); } else { m_thumbnailImage = selection->thumbnailImage(); m_thumbnailImageTransform = selection->thumbnailImageTransform(); } if (view() && view()->canvasBase()) { view()->canvasBase()->updateCanvas(); } } else { m_signalCompressor.start(); } } else { m_signalCompressor.stop(); m_outlinePath = QPainterPath(); m_thumbnailImage = QImage(); m_thumbnailImageTransform = QTransform(); view()->canvasBase()->updateCanvas(); m_antsTimer->stop(); } } void KisSelectionDecoration::slotStartUpdateSelection() { KisSelectionSP selection = view()->selection(); if (!selection) return; view()->image()->addSpontaneousJob(new KisUpdateOutlineJob(selection, m_mode == Mask, m_maskColor)); } void KisSelectionDecoration::slotConfigChanged() { KisImageConfig imageConfig(true); KisConfig cfg(true); m_maskColor = imageConfig.selectionOverlayMaskColor(); m_antialiasSelectionOutline = cfg.antialiasSelectionOutline(); } void KisSelectionDecoration::antsAttackEvent() { KisSelectionSP selection = view()->selection(); if (!selection) return; if (selectionIsActive()) { m_offset = (m_offset + 1) % ANT_ADVANCE_WIDTH; m_antsPen.setDashOffset(m_offset); view()->canvasBase()->updateCanvas(); } } void KisSelectionDecoration::drawDecoration(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter *converter, KisCanvas2 *canvas) { Q_UNUSED(updateRect); Q_UNUSED(canvas); if (!selectionIsActive()) return; if ((m_mode == Ants && m_outlinePath.isEmpty()) || (m_mode == Mask && m_thumbnailImage.isNull())) return; QTransform transform = converter->imageToWidgetTransform(); gc.save(); gc.setTransform(transform, false); if (m_mode == Mask) { gc.setRenderHints(QPainter::SmoothPixmapTransform | QPainter::HighQualityAntialiasing, false); gc.setTransform(m_thumbnailImageTransform, true); gc.drawImage(QPoint(), m_thumbnailImage); QRect r1 = m_thumbnailImageTransform.inverted().mapRect(view()->image()->bounds()); QRect r2 = m_thumbnailImage.rect(); QPainterPath p1; p1.addRect(r1); QPainterPath p2; p2.addRect(r2); gc.setBrush(m_maskColor); gc.setPen(Qt::NoPen); gc.drawPath(p1 - p2); } else /* if (m_mode == Ants) */ { gc.setRenderHints(QPainter::Antialiasing | QPainter::HighQualityAntialiasing, m_antialiasSelectionOutline); // render selection outline in white gc.setPen(m_outlinePen); gc.drawPath(m_outlinePath); // render marching ants in black (above the white outline) gc.setPen(m_antsPen); gc.drawPath(m_outlinePath); } gc.restore(); } void KisSelectionDecoration::setVisible(bool v) { KisCanvasDecoration::setVisible(v); selectionChanged(); } diff --git a/libs/ui/kis_selection_manager.cc b/libs/ui/kis_selection_manager.cc index b469249eb5..9044ff8b5b 100644 --- a/libs/ui/kis_selection_manager.cc +++ b/libs/ui/kis_selection_manager.cc @@ -1,740 +1,748 @@ /* * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2007 Sven Langkamp * * The outline algorithm uses the limn algorithm of fontutils by * Karl Berry and Kathryn Hargreaves * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public 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_manager.h" #include #include #include #include #include #include #include #include #include #include #include "KoCanvasController.h" #include "KoChannelInfo.h" #include "KoIntegerMaths.h" #include #include #include #include #include #include #include #include #include #include #include #include "kis_adjustment_layer.h" #include "kis_node_manager.h" #include "canvas/kis_canvas2.h" #include "kis_config.h" #include "kis_convolution_painter.h" #include "kis_convolution_kernel.h" #include "kis_debug.h" #include "kis_fill_painter.h" #include "kis_group_layer.h" #include "kis_layer.h" #include "kis_statusbar.h" #include "kis_paint_device.h" #include "kis_paint_layer.h" #include "kis_painter.h" #include "kis_transaction.h" #include "kis_selection.h" #include "kis_types.h" #include "kis_canvas_resource_provider.h" #include "kis_undo_adapter.h" #include "kis_pixel_selection.h" #include "flake/kis_shape_selection.h" #include "commands/kis_selection_commands.h" #include "kis_selection_mask.h" #include "flake/kis_shape_layer.h" #include "kis_selection_decoration.h" #include "canvas/kis_canvas_decoration.h" #include "kis_node_commands_adapter.h" #include "kis_iterator_ng.h" #include "kis_clipboard.h" #include "KisViewManager.h" #include "kis_selection_filters.h" #include "kis_figure_painting_tool_helper.h" #include "KisView.h" #include "dialogs/kis_dlg_stroke_selection_properties.h" #include "actions/kis_selection_action_factories.h" #include "actions/KisPasteActionFactories.h" #include "kis_action.h" #include "kis_action_manager.h" #include "operations/kis_operation_configuration.h" //new #include "kis_node_query_path.h" #include "kis_tool_shape.h" KisSelectionManager::KisSelectionManager(KisViewManager * view) : m_view(view) , m_adapter(new KisNodeCommandsAdapter(view)) { m_clipboard = KisClipboard::instance(); } KisSelectionManager::~KisSelectionManager() { } void KisSelectionManager::setup(KisActionManager* actionManager) { m_cut = actionManager->createStandardAction(KStandardAction::Cut, this, SLOT(cut())); m_copy = actionManager->createStandardAction(KStandardAction::Copy, this, SLOT(copy())); m_paste = actionManager->createStandardAction(KStandardAction::Paste, this, SLOT(paste())); KisAction *action = actionManager->createAction("copy_sharp"); connect(action, SIGNAL(triggered()), this, SLOT(copySharp())); action = actionManager->createAction("cut_sharp"); connect(action, SIGNAL(triggered()), this, SLOT(cutSharp())); m_pasteNew = actionManager->createAction("paste_new"); connect(m_pasteNew, SIGNAL(triggered()), this, SLOT(pasteNew())); m_pasteAt = actionManager->createAction("paste_at"); connect(m_pasteAt, SIGNAL(triggered()), this, SLOT(pasteAt())); m_pasteAsReference = actionManager->createAction("paste_as_reference"); connect(m_pasteAsReference, SIGNAL(triggered()), this, SLOT(pasteAsReference())); m_copyMerged = actionManager->createAction("copy_merged"); connect(m_copyMerged, SIGNAL(triggered()), this, SLOT(copyMerged())); m_selectAll = actionManager->createAction("select_all"); connect(m_selectAll, SIGNAL(triggered()), this, SLOT(selectAll())); m_deselect = actionManager->createAction("deselect"); connect(m_deselect, SIGNAL(triggered()), this, SLOT(deselect())); m_clear = actionManager->createAction("clear"); connect(m_clear, SIGNAL(triggered()), SLOT(clear())); m_reselect = actionManager->createAction("reselect"); connect(m_reselect, SIGNAL(triggered()), this, SLOT(reselect())); m_invert = actionManager->createAction("invert_selection"); m_invert->setOperationID("invertselection"); actionManager->registerOperation(new KisInvertSelectionOperation); m_copyToNewLayer = actionManager->createAction("copy_selection_to_new_layer"); connect(m_copyToNewLayer, SIGNAL(triggered()), this, SLOT(copySelectionToNewLayer())); m_cutToNewLayer = actionManager->createAction("cut_selection_to_new_layer"); connect(m_cutToNewLayer, SIGNAL(triggered()), this, SLOT(cutToNewLayer())); m_fillForegroundColor = actionManager->createAction("fill_selection_foreground_color"); connect(m_fillForegroundColor, SIGNAL(triggered()), this, SLOT(fillForegroundColor())); m_fillBackgroundColor = actionManager->createAction("fill_selection_background_color"); connect(m_fillBackgroundColor, SIGNAL(triggered()), this, SLOT(fillBackgroundColor())); m_fillPattern = actionManager->createAction("fill_selection_pattern"); connect(m_fillPattern, SIGNAL(triggered()), this, SLOT(fillPattern())); m_fillForegroundColorOpacity = actionManager->createAction("fill_selection_foreground_color_opacity"); connect(m_fillForegroundColorOpacity, SIGNAL(triggered()), this, SLOT(fillForegroundColorOpacity())); m_fillBackgroundColorOpacity = actionManager->createAction("fill_selection_background_color_opacity"); connect(m_fillBackgroundColorOpacity, SIGNAL(triggered()), this, SLOT(fillBackgroundColorOpacity())); m_fillPatternOpacity = actionManager->createAction("fill_selection_pattern_opacity"); connect(m_fillPatternOpacity, SIGNAL(triggered()), this, SLOT(fillPatternOpacity())); m_strokeShapes = actionManager->createAction("stroke_shapes"); connect(m_strokeShapes, SIGNAL(triggered()), this, SLOT(paintSelectedShapes())); m_toggleDisplaySelection = actionManager->createAction("toggle_display_selection"); connect(m_toggleDisplaySelection, SIGNAL(triggered()), this, SLOT(toggleDisplaySelection())); m_toggleDisplaySelection->setChecked(true); m_imageResizeToSelection = actionManager->createAction("resizeimagetoselection"); connect(m_imageResizeToSelection, SIGNAL(triggered()), this, SLOT(imageResizeToSelection())); action = actionManager->createAction("edit_selection"); connect(action, SIGNAL(triggered()), SLOT(editSelection())); action = actionManager->createAction("convert_to_vector_selection"); connect(action, SIGNAL(triggered()), SLOT(convertToVectorSelection())); action = actionManager->createAction("convert_to_raster_selection"); connect(action, SIGNAL(triggered()), SLOT(convertToRasterSelection())); action = actionManager->createAction("convert_shapes_to_vector_selection"); connect(action, SIGNAL(triggered()), SLOT(convertShapesToVectorSelection())); action = actionManager->createAction("convert_selection_to_shape"); connect(action, SIGNAL(triggered()), SLOT(convertToShape())); m_toggleSelectionOverlayMode = actionManager->createAction("toggle-selection-overlay-mode"); connect(m_toggleSelectionOverlayMode, SIGNAL(triggered()), SLOT(slotToggleSelectionDecoration())); m_strokeSelected = actionManager->createAction("stroke_selection"); connect(m_strokeSelected, SIGNAL(triggered()), SLOT(slotStrokeSelection())); QClipboard *cb = QApplication::clipboard(); connect(cb, SIGNAL(dataChanged()), SLOT(clipboardDataChanged())); } void KisSelectionManager::setView(QPointerimageView) { if (m_imageView && m_imageView->canvasBase()) { disconnect(m_imageView->canvasBase()->toolProxy(), SIGNAL(toolChanged(QString)), this, SLOT(clipboardDataChanged())); KoSelection *selection = m_imageView->canvasBase()->globalShapeManager()->selection(); selection->disconnect(this, SLOT(shapeSelectionChanged())); KisSelectionDecoration *decoration = qobject_cast(m_imageView->canvasBase()->decoration("selection").data()); if (decoration) { disconnect(SIGNAL(currentSelectionChanged()), decoration); } m_imageView->image()->undoAdapter()->disconnect(this); m_selectionDecoration = 0; } m_imageView = imageView; if (m_imageView) { connect(m_imageView->canvasBase()->selectedShapesProxy(), SIGNAL(selectionChanged()), this, SLOT(shapeSelectionChanged()), Qt::UniqueConnection); KisSelectionDecoration* decoration = qobject_cast(m_imageView->canvasBase()->decoration("selection").data()); if (!decoration) { decoration = new KisSelectionDecoration(m_imageView); decoration->setVisible(true); m_imageView->canvasBase()->addDecoration(decoration); } m_selectionDecoration = decoration; connect(this, SIGNAL(currentSelectionChanged()), decoration, SLOT(selectionChanged())); connect(m_imageView->image()->undoAdapter(), SIGNAL(selectionChanged()), SLOT(selectionChanged())); connect(m_imageView->canvasBase()->toolProxy(), SIGNAL(toolChanged(QString)), SLOT(clipboardDataChanged())); } } void KisSelectionManager::clipboardDataChanged() { m_view->updateGUI(); } bool KisSelectionManager::havePixelsSelected() { KisSelectionSP activeSelection = m_view->selection(); return activeSelection && !activeSelection->selectedRect().isEmpty(); } bool KisSelectionManager::havePixelsInClipboard() { return m_clipboard->hasClip(); } bool KisSelectionManager::haveShapesSelected() { if (m_view && m_view->canvasBase()) { return m_view->canvasBase()->selectedShapesProxy()->selection()->count() > 0; } return false; } bool KisSelectionManager::haveShapesInClipboard() { KoSvgPaste paste; return paste.hasShapes(); } bool KisSelectionManager::haveAnySelectionWithPixels() { KisSelectionSP selection = m_view->selection(); - return selection && selection->hasPixelSelection(); + return selection && selection->hasNonEmptyPixelSelection(); } bool KisSelectionManager::haveShapeSelectionWithShapes() { KisSelectionSP selection = m_view->selection(); - return selection && selection->hasShapeSelection(); + return selection && selection->hasNonEmptyShapeSelection(); } bool KisSelectionManager::haveRasterSelectionWithPixels() { KisSelectionSP selection = m_view->selection(); - return selection && selection->hasPixelSelection() && !selection->hasShapeSelection(); + return selection && selection->hasNonEmptyPixelSelection() && !selection->hasNonEmptyShapeSelection(); } void KisSelectionManager::updateGUI() { Q_ASSERT(m_view); Q_ASSERT(m_clipboard); if (!m_view || !m_clipboard) return; bool havePixelsSelected = this->havePixelsSelected(); bool havePixelsInClipboard = this->havePixelsInClipboard(); bool haveShapesSelected = this->haveShapesSelected(); bool haveShapesInClipboard = this->haveShapesInClipboard(); bool haveDevice = m_view->activeDevice(); KisLayerSP activeLayer = m_view->activeLayer(); KisImageWSP image = activeLayer ? activeLayer->image() : 0; bool canReselect = image && image->canReselectGlobalSelection(); bool canDeselect = image && image->globalSelection(); m_clear->setEnabled(haveDevice || havePixelsSelected || haveShapesSelected); m_cut->setEnabled(havePixelsSelected || haveShapesSelected); m_copy->setEnabled(havePixelsSelected || haveShapesSelected); m_paste->setEnabled(havePixelsInClipboard || haveShapesInClipboard); m_pasteAt->setEnabled(havePixelsInClipboard || haveShapesInClipboard); // FIXME: how about pasting shapes? m_pasteNew->setEnabled(havePixelsInClipboard); m_pasteAsReference->setEnabled(haveDevice); m_selectAll->setEnabled(true); m_deselect->setEnabled(canDeselect); m_reselect->setEnabled(canReselect); // m_load->setEnabled(true); // m_save->setEnabled(havePixelsSelected); updateStatusBar(); emit signalUpdateGUI(); } void KisSelectionManager::updateStatusBar() { if (m_view && m_view->statusBar()) { m_view->statusBar()->setSelection(m_view->image()); } } void KisSelectionManager::selectionChanged() { m_view->updateGUI(); emit currentSelectionChanged(); } void KisSelectionManager::cut() { KisCutCopyActionFactory factory; factory.run(true, false, m_view); } void KisSelectionManager::copy() { KisCutCopyActionFactory factory; factory.run(false, false, m_view); } void KisSelectionManager::cutSharp() { KisCutCopyActionFactory factory; factory.run(true, true, m_view); } void KisSelectionManager::copySharp() { KisCutCopyActionFactory factory; factory.run(false, true, m_view); } void KisSelectionManager::copyMerged() { KisCopyMergedActionFactory factory; factory.run(m_view); } void KisSelectionManager::paste() { KisPasteActionFactory factory; factory.run(false, m_view); } void KisSelectionManager::pasteAt() { KisPasteActionFactory factory; factory.run(true, m_view); } void KisSelectionManager::pasteAsReference() { KisPasteReferenceActionFactory factory; factory.run(m_view); } void KisSelectionManager::pasteNew() { KisPasteNewActionFactory factory; factory.run(m_view); } void KisSelectionManager::selectAll() { KisSelectAllActionFactory factory; factory.run(m_view); } void KisSelectionManager::deselect() { KisDeselectActionFactory factory; factory.run(m_view); } void KisSelectionManager::invert() { if(m_invert) m_invert->trigger(); } void KisSelectionManager::reselect() { KisReselectActionFactory factory; factory.run(m_view); } #include #include void KisSelectionManager::editSelection() { KisSelectionSP selection = m_view->selection(); if (!selection) return; KisAction *action = m_view->actionManager()->actionByName("show-global-selection-mask"); KIS_SAFE_ASSERT_RECOVER_RETURN(action); if (!action->isChecked()) { action->setChecked(true); emit action->toggled(true); emit action->triggered(true); } KisNodeSP node = selection->parentNode(); KIS_SAFE_ASSERT_RECOVER_RETURN(node); m_view->nodeManager()->slotNonUiActivatedNode(node); if (selection->hasShapeSelection()) { KisShapeSelection *shapeSelection = dynamic_cast(selection->shapeSelection()); KIS_SAFE_ASSERT_RECOVER_RETURN(shapeSelection); KoToolManager::instance()->switchToolRequested(KoInteractionTool_ID); QList shapes = shapeSelection->shapes(); if (shapes.isEmpty()) { KIS_SAFE_ASSERT_RECOVER_NOOP(0 && "no shapes"); return; } Q_FOREACH (KoShape *shape, shapes) { m_view->canvasBase()->selectedShapesProxy()->selection()->select(shape); } } else { KoToolManager::instance()->switchToolRequested("KisToolTransform"); } } void KisSelectionManager::convertToVectorSelection() { KisSelectionToVectorActionFactory factory; factory.run(m_view); } void KisSelectionManager::convertToRasterSelection() { KisSelectionToRasterActionFactory factory; factory.run(m_view); } void KisSelectionManager::convertShapesToVectorSelection() { KisShapesToVectorSelectionActionFactory factory; factory.run(m_view); } void KisSelectionManager::convertToShape() { KisSelectionToShapeActionFactory factory; factory.run(m_view); } void KisSelectionManager::clear() { KisClearActionFactory factory; factory.run(m_view); } void KisSelectionManager::fillForegroundColor() { KisFillActionFactory factory; factory.run("fg", m_view); } void KisSelectionManager::fillBackgroundColor() { KisFillActionFactory factory; factory.run("bg", m_view); } void KisSelectionManager::fillPattern() { KisFillActionFactory factory; factory.run("pattern", m_view); } void KisSelectionManager::fillForegroundColorOpacity() { KisFillActionFactory factory; factory.run("fg_opacity", m_view); } void KisSelectionManager::fillBackgroundColorOpacity() { KisFillActionFactory factory; factory.run("bg_opacity", m_view); } void KisSelectionManager::fillPatternOpacity() { KisFillActionFactory factory; factory.run("pattern_opacity", m_view); } void KisSelectionManager::copySelectionToNewLayer() { copy(); paste(); } void KisSelectionManager::cutToNewLayer() { cut(); paste(); } void KisSelectionManager::toggleDisplaySelection() { KIS_ASSERT_RECOVER_RETURN(m_selectionDecoration); m_selectionDecoration->toggleVisibility(); m_toggleDisplaySelection->blockSignals(true); m_toggleDisplaySelection->setChecked(m_selectionDecoration->visible()); m_toggleDisplaySelection->blockSignals(false); emit displaySelectionChanged(); } bool KisSelectionManager::displaySelection() { return m_toggleDisplaySelection->isChecked(); } void KisSelectionManager::shapeSelectionChanged() { KoShapeManager* shapeManager = m_view->canvasBase()->globalShapeManager(); KoSelection * selection = shapeManager->selection(); QList selectedShapes = selection->selectedShapes(); KoShapeStrokeSP border(new KoShapeStroke(0, Qt::lightGray)); Q_FOREACH (KoShape* shape, shapeManager->shapes()) { if (dynamic_cast(shape->parent())) { if (selectedShapes.contains(shape)) shape->setStroke(border); else shape->setStroke(KoShapeStrokeSP()); } } m_view->updateGUI(); } void KisSelectionManager::imageResizeToSelection() { KisImageResizeToSelectionActionFactory factory; factory.run(m_view); } void KisSelectionManager::paintSelectedShapes() { KisImageWSP image = m_view->image(); if (!image) return; KisLayerSP layer = m_view->activeLayer(); if (!layer) return; QList shapes = m_view->canvasBase()->shapeManager()->selection()->selectedShapes(); KisPaintLayerSP paintLayer = new KisPaintLayer(image, i18n("Stroked Shapes"), OPACITY_OPAQUE_U8); KUndo2MagicString actionName = kundo2_i18n("Stroke Shapes"); m_adapter->beginMacro(actionName); m_adapter->addNode(paintLayer.data(), layer->parent().data(), layer.data()); KisFigurePaintingToolHelper helper(actionName, image, paintLayer.data(), m_view->canvasResourceProvider()->resourceManager(), KisToolShapeUtils::StrokeStyleForeground, KisToolShapeUtils::FillStyleNone); Q_FOREACH (KoShape* shape, shapes) { QTransform matrix = shape->absoluteTransformation() * QTransform::fromScale(image->xRes(), image->yRes()); QPainterPath mapedOutline = matrix.map(shape->outline()); helper.paintPainterPath(mapedOutline); } m_adapter->endMacro(); } void KisSelectionManager::slotToggleSelectionDecoration() { KIS_ASSERT_RECOVER_RETURN(m_selectionDecoration); KisSelectionDecoration::Mode mode = m_selectionDecoration->mode() ? KisSelectionDecoration::Ants : KisSelectionDecoration::Mask; m_selectionDecoration->setMode(mode); emit displaySelectionChanged(); } bool KisSelectionManager::showSelectionAsMask() const { if (m_selectionDecoration) { return m_selectionDecoration->mode() == KisSelectionDecoration::Mask; } return false; } void KisSelectionManager::slotStrokeSelection() { KisImageWSP image = m_view->image(); if (!image ) { return; } KisNodeSP currentNode = m_view->canvasResourceProvider()->resourceManager()->resource(KisCanvasResourceProvider::CurrentKritaNode).value(); bool isVectorLayer = false; if (currentNode->inherits("KisShapeLayer")) { isVectorLayer = true; } QPointer dlg = new KisDlgStrokeSelection(image, m_view, isVectorLayer); if (dlg->exec() == QDialog::Accepted) { StrokeSelectionOptions params = dlg->getParams(); if (params.brushSelected){ KisStrokeBrushSelectionActionFactory factory; factory.run(m_view, params); } else { KisStrokeSelectionActionFactory factory; factory.run(m_view, params); } } delete dlg; } #include "kis_image_barrier_locker.h" #include "kis_selection_tool_helper.h" void KisSelectionManager::selectOpaqueOnNode(KisNodeSP node, SelectionAction action) { KisImageSP image = m_view->image(); if (!m_view->blockUntilOperationsFinished(image)) { return; } KUndo2MagicString actionName; KisPixelSelectionSP tmpSel = KisPixelSelectionSP(new KisPixelSelection()); KisCanvas2 *canvas = m_view->canvasBase(); { KisImageBarrierLocker locker(image); KisPaintDeviceSP device = node->projection(); if (!device) device = node->paintDevice(); if (!device) device = node->original(); if (!device) return; QRect rc = device->exactBounds(); - if (rc.isEmpty()) return; + if (rc.isEmpty()) { + + if (action == SELECTION_REPLACE || action == SELECTION_INTERSECT) { + KUndo2Command *deselectCommand = new KisDeselectActiveSelectionCommand(m_view->selection(), m_view->image()); + KisProcessingApplicator::runSingleCommandStroke(m_view->image(), deselectCommand); + } + + return; + } KIS_ASSERT_RECOVER_RETURN(canvas); /** * If there is nothing selected, just create a new selection */ if (!canvas->imageView()->selection()) { action = SELECTION_REPLACE; } switch (action) { case SELECTION_ADD: actionName = kundo2_i18n("Select Opaque (Add)"); break; case SELECTION_SUBTRACT: actionName = kundo2_i18n("Select Opaque (Subtract)"); break; case SELECTION_INTERSECT: actionName = kundo2_i18n("Select Opaque (Intersect)"); break; case SELECTION_SYMMETRICDIFFERENCE: actionName = kundo2_i18n("Select Opaque (Symmetric Difference)"); break; default: actionName = kundo2_i18n("Select Opaque"); break; } qint32 x, y, w, h; rc.getRect(&x, &y, &w, &h); const KoColorSpace * cs = device->colorSpace(); KisHLineConstIteratorSP deviter = device->createHLineConstIteratorNG(x, y, w); KisHLineIteratorSP selIter = tmpSel ->createHLineIteratorNG(x, y, w); for (int row = y; row < h + y; ++row) { do { *selIter->rawData() = cs->opacityU8(deviter->oldRawData()); } while (deviter->nextPixel() && selIter->nextPixel()); deviter->nextRow(); selIter->nextRow(); } } KisSelectionToolHelper helper(canvas, actionName); tmpSel->invalidateOutlineCache(); helper.selectPixelSelection(tmpSel, action); } diff --git a/libs/ui/opengl/KisOpenGLModeProber.cpp b/libs/ui/opengl/KisOpenGLModeProber.cpp index 6589e67034..c47b106346 100644 --- a/libs/ui/opengl/KisOpenGLModeProber.cpp +++ b/libs/ui/opengl/KisOpenGLModeProber.cpp @@ -1,333 +1,334 @@ /* * Copyright (c) 2017 Alvin Wong * 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 "KisOpenGLModeProber.h" #include #include #include #include #include #include Q_GLOBAL_STATIC(KisOpenGLModeProber, s_instance) KisOpenGLModeProber::KisOpenGLModeProber() { } KisOpenGLModeProber::~KisOpenGLModeProber() { } KisOpenGLModeProber *KisOpenGLModeProber::instance() { return s_instance; } bool KisOpenGLModeProber::useHDRMode() const { return isFormatHDR(QSurfaceFormat::defaultFormat()); } QSurfaceFormat KisOpenGLModeProber::surfaceformatInUse() const { // TODO: use information provided by KisOpenGL instead QOpenGLContext *sharedContext = QOpenGLContext::globalShareContext(); QSurfaceFormat format = sharedContext ? sharedContext->format() : QSurfaceFormat::defaultFormat(); return format; } const KoColorProfile *KisOpenGLModeProber::rootSurfaceColorProfile() const { const KoColorProfile *profile = KoColorSpaceRegistry::instance()->p709SRGBProfile(); #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) const KisSurfaceColorSpace surfaceColorSpace = surfaceformatInUse().colorSpace(); if (surfaceColorSpace == KisSurfaceColorSpace::sRGBColorSpace) { // use the default one! #ifdef HAVE_HDR } else if (surfaceColorSpace == KisSurfaceColorSpace::scRGBColorSpace) { profile = KoColorSpaceRegistry::instance()->p709G10Profile(); } else if (surfaceColorSpace == KisSurfaceColorSpace::bt2020PQColorSpace) { profile = KoColorSpaceRegistry::instance()->p2020PQProfile(); #endif } #endif return profile; } namespace { struct AppAttributeSetter { AppAttributeSetter(Qt::ApplicationAttribute attribute, bool useOpenGLES) : m_attribute(attribute), m_oldValue(QCoreApplication::testAttribute(attribute)) { QCoreApplication::setAttribute(attribute, useOpenGLES); } ~AppAttributeSetter() { QCoreApplication::setAttribute(m_attribute, m_oldValue); } private: Qt::ApplicationAttribute m_attribute; bool m_oldValue = false; }; struct SurfaceFormatSetter { SurfaceFormatSetter(const QSurfaceFormat &format) : m_oldFormat(QSurfaceFormat::defaultFormat()) { QSurfaceFormat::setDefaultFormat(format); } ~SurfaceFormatSetter() { QSurfaceFormat::setDefaultFormat(m_oldFormat); } private: QSurfaceFormat m_oldFormat; }; #if (QT_VERSION < QT_VERSION_CHECK(5, 10, 0)) QString qEnvironmentVariable(const char *varName) { return qgetenv(varName); } #endif struct EnvironmentSetter { EnvironmentSetter(const QLatin1String &env, const QString &value) : m_env(env) { if (qEnvironmentVariableIsEmpty(m_env.latin1())) { m_oldValue = qgetenv(env.latin1()); } if (!value.isEmpty()) { qputenv(env.latin1(), value.toLatin1()); } else { qunsetenv(env.latin1()); } } ~EnvironmentSetter() { if (m_oldValue) { qputenv(m_env.latin1(), (*m_oldValue).toLatin1()); } else { qunsetenv(m_env.latin1()); } } private: const QLatin1String m_env; boost::optional m_oldValue; }; } boost::optional KisOpenGLModeProber::probeFormat(const KisOpenGL::RendererConfig &rendererConfig, bool adjustGlobalState) { const QSurfaceFormat &format = rendererConfig.format; QScopedPointer sharedContextSetter; QScopedPointer glSetter; QScopedPointer glesSetter; QScopedPointer formatSetter; QScopedPointer rendererSetter; QScopedPointer application; int argc = 1; QByteArray probeAppName("krita"); char *argv = probeAppName.data(); if (adjustGlobalState) { sharedContextSetter.reset(new AppAttributeSetter(Qt::AA_ShareOpenGLContexts, false)); if (format.renderableType() != QSurfaceFormat::DefaultRenderableType) { glSetter.reset(new AppAttributeSetter(Qt::AA_UseDesktopOpenGL, format.renderableType() != QSurfaceFormat::OpenGLES)); glesSetter.reset(new AppAttributeSetter(Qt::AA_UseOpenGLES, format.renderableType() == QSurfaceFormat::OpenGLES)); } rendererSetter.reset(new EnvironmentSetter(QLatin1String("QT_ANGLE_PLATFORM"), angleRendererToString(rendererConfig.angleRenderer))); formatSetter.reset(new SurfaceFormatSetter(format)); QGuiApplication::setDesktopSettingsAware(false); application.reset(new QGuiApplication(argc, &argv)); QGuiApplication::setDesktopSettingsAware(true); } QWindow surface; surface.setFormat(format); surface.setSurfaceType(QSurface::OpenGLSurface); surface.create(); QOpenGLContext context; context.setFormat(format); if (!context.create()) { dbgOpenGL << "OpenGL context cannot be created"; return boost::none; } if (!context.isValid()) { dbgOpenGL << "OpenGL context is not valid while checking Qt's OpenGL status"; return boost::none; } if (!context.makeCurrent(&surface)) { dbgOpenGL << "OpenGL context cannot be made current"; return boost::none; } #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) if (!fuzzyCompareColorSpaces(context.format().colorSpace(), format.colorSpace())) { dbgOpenGL << "Failed to create an OpenGL context with requested color space. Requested:" << format.colorSpace() << "Actual:" << context.format().colorSpace(); return boost::none; } #endif return Result(context); } bool KisOpenGLModeProber::fuzzyCompareColorSpaces(const KisSurfaceColorSpace &lhs, const KisSurfaceColorSpace &rhs) { return lhs == rhs || ((lhs == KisSurfaceColorSpace::DefaultColorSpace || lhs == KisSurfaceColorSpace::sRGBColorSpace) && (rhs == KisSurfaceColorSpace::DefaultColorSpace || rhs == KisSurfaceColorSpace::sRGBColorSpace)); } void KisOpenGLModeProber::initSurfaceFormatFromConfig(KisConfig::RootSurfaceFormat config, QSurfaceFormat *format) { #ifdef HAVE_HDR if (config == KisConfig::BT2020_PQ) { format->setRedBufferSize(10); format->setGreenBufferSize(10); format->setBlueBufferSize(10); format->setAlphaBufferSize(2); format->setColorSpace(KisSurfaceColorSpace::bt2020PQColorSpace); } else if (config == KisConfig::BT709_G10) { format->setRedBufferSize(16); format->setGreenBufferSize(16); format->setBlueBufferSize(16); format->setAlphaBufferSize(16); format->setColorSpace(KisSurfaceColorSpace::scRGBColorSpace); } else #else if (config == KisConfig::BT2020_PQ) { qWarning() << "WARNING: Bt.2020 PQ surface type is not supported by this build of Krita"; } else if (config == KisConfig::BT709_G10) { qWarning() << "WARNING: scRGB surface type is not supported by this build of Krita"; } #endif { format->setRedBufferSize(8); format->setGreenBufferSize(8); format->setBlueBufferSize(8); #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) format->setAlphaBufferSize(8); #else format->setAlphaBufferSize(0); #endif #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) // TODO: check if we can use real sRGB space here format->setColorSpace(KisSurfaceColorSpace::DefaultColorSpace); #endif } } bool KisOpenGLModeProber::isFormatHDR(const QSurfaceFormat &format) { #ifdef HAVE_HDR bool isBt2020PQ = format.colorSpace() == KisSurfaceColorSpace::bt2020PQColorSpace && format.redBufferSize() == 10 && format.greenBufferSize() == 10 && format.blueBufferSize() == 10 && format.alphaBufferSize() == 2; bool isBt709G10 = format.colorSpace() == KisSurfaceColorSpace::scRGBColorSpace && format.redBufferSize() == 16 && format.greenBufferSize() == 16 && format.blueBufferSize() == 16 && format.alphaBufferSize() == 16; return isBt2020PQ || isBt709G10; #else Q_UNUSED(format); return false; #endif } QString KisOpenGLModeProber::angleRendererToString(KisOpenGL::AngleRenderer renderer) { QString value; switch (renderer) { case KisOpenGL::AngleRendererDefault: break; case KisOpenGL::AngleRendererD3d9: value = "d3d9"; break; case KisOpenGL::AngleRendererD3d11: value = "d3d11"; break; case KisOpenGL::AngleRendererD3d11Warp: value = "warp"; break; }; return value; } KisOpenGLModeProber::Result::Result(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_vendorString = QString(reinterpret_cast(funcs->glGetString(GL_VENDOR))); m_shadingLanguageString = QString(reinterpret_cast(funcs->glGetString(GL_SHADING_LANGUAGE_VERSION))); m_glMajorVersion = context.format().majorVersion(); m_glMinorVersion = context.format().minorVersion(); m_supportsDeprecatedFunctions = (context.format().options() & QSurfaceFormat::DeprecatedFunctions); m_isOpenGLES = context.isOpenGLES(); m_format = context.format(); + m_supportsFBO = context.functions()->hasOpenGLFeature(QOpenGLFunctions::Framebuffers); } diff --git a/libs/ui/opengl/KisOpenGLModeProber.h b/libs/ui/opengl/KisOpenGLModeProber.h index d1aa59b30a..409aff25c9 100644 --- a/libs/ui/opengl/KisOpenGLModeProber.h +++ b/libs/ui/opengl/KisOpenGLModeProber.h @@ -1,148 +1,153 @@ /* * Copyright (c) 2017 Alvin Wong * 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 KISOPENGLMODEPROBER_H #define KISOPENGLMODEPROBER_H #include "kritaui_export.h" #include "kis_config.h" #include #include "KisSurfaceColorSpace.h" #include #include "kis_opengl.h" class KoColorProfile; class KRITAUI_EXPORT KisOpenGLModeProber { public: class Result; public: KisOpenGLModeProber(); ~KisOpenGLModeProber(); static KisOpenGLModeProber* instance(); bool useHDRMode() const; QSurfaceFormat surfaceformatInUse() const; const KoColorProfile *rootSurfaceColorProfile() const; boost::optional probeFormat(const KisOpenGL::RendererConfig &rendererConfig, bool adjustGlobalState = true); static bool fuzzyCompareColorSpaces(const KisSurfaceColorSpace &lhs, const KisSurfaceColorSpace &rhs); static QString angleRendererToString(KisOpenGL::AngleRenderer renderer); public: static void initSurfaceFormatFromConfig(KisConfig::RootSurfaceFormat config, QSurfaceFormat *format); static bool isFormatHDR(const QSurfaceFormat &format); }; class KisOpenGLModeProber::Result { public: Result(QOpenGLContext &context); int glMajorVersion() const { return m_glMajorVersion; } int glMinorVersion() const { return m_glMinorVersion; } bool supportsDeprecatedFunctions() const { return m_supportsDeprecatedFunctions; } bool isOpenGLES() const { return m_isOpenGLES; } QString rendererString() const { return m_rendererString; } QString driverVersionString() const { return m_driverVersionString; } bool isSupportedVersion() const { return #ifdef Q_OS_MACOS ((m_glMajorVersion * 100 + m_glMinorVersion) >= 302) #else (m_glMajorVersion >= 3 && (m_supportsDeprecatedFunctions || m_isOpenGLES)) || ((m_glMajorVersion * 100 + m_glMinorVersion) == 201) #endif ; } bool supportsLoD() const { return (m_glMajorVersion * 100 + m_glMinorVersion) >= 300; } bool hasOpenGL3() const { return (m_glMajorVersion * 100 + m_glMinorVersion) >= 302; } bool supportsFenceSync() const { return m_glMajorVersion >= 3; } + bool supportsFBO() const { + return m_supportsFBO; + } + #ifdef Q_OS_WIN // This is only for detecting whether ANGLE is being used. // For detecting generic OpenGL ES please check isOpenGLES bool isUsingAngle() const { return m_rendererString.startsWith("ANGLE", Qt::CaseInsensitive); } #endif QString shadingLanguageString() const { return m_shadingLanguageString; } QString vendorString() const { return m_vendorString; } QSurfaceFormat format() const { return m_format; } private: int m_glMajorVersion = 0; int m_glMinorVersion = 0; bool m_supportsDeprecatedFunctions = false; bool m_isOpenGLES = false; + bool m_supportsFBO = false; QString m_rendererString; QString m_driverVersionString; QString m_vendorString; QString m_shadingLanguageString; QSurfaceFormat m_format; }; #endif // KISOPENGLMODEPROBER_H diff --git a/libs/ui/opengl/kis_opengl.cpp b/libs/ui/opengl/kis_opengl.cpp index de2c4a0918..48211adb27 100644 --- a/libs/ui/opengl/kis_opengl.cpp +++ b/libs/ui/opengl/kis_opengl.cpp @@ -1,915 +1,921 @@ /* * 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 =="; dbgOpenGL.noquote().nospace() << g_debugText; KisUsageLogger::writeSysInfo(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::supportsRenderToFBO() +{ + initialize(); + return openGLCheckResult && openGLCheckResult->supportsFBO(); +} + 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; #ifndef Q_OS_ANDROID renderersToTest.insert(RendererDesktopGL, Info()); #endif 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.h b/libs/ui/opengl/kis_opengl.h index 365003a93b..4300de3696 100644 --- a/libs/ui/opengl/kis_opengl.h +++ b/libs/ui/opengl/kis_opengl.h @@ -1,137 +1,146 @@ /* * 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. */ #ifndef KIS_OPENGL_H_ #define KIS_OPENGL_H_ /** @file */ #include #include #include #include "kis_config.h" #include "kritaui_export.h" class QOpenGLContext; class QString; class QStringList; class QSurfaceFormat; /** * This class manages a shared OpenGL context and provides utility * functions for checking capabilities and error reporting. */ class KRITAUI_EXPORT KisOpenGL { public: enum FilterMode { NearestFilterMode, // nearest BilinearFilterMode, // linear, no mipmap TrilinearFilterMode, // LINEAR_MIPMAP_LINEAR HighQualityFiltering // Mipmaps + custom shader }; enum OpenGLRenderer { RendererNone = 0x00, RendererAuto = 0x01, RendererDesktopGL = 0x02, RendererOpenGLES = 0x04, RendererSoftware = 0x08 }; Q_DECLARE_FLAGS(OpenGLRenderers, OpenGLRenderer) enum AngleRenderer { AngleRendererDefault = 0x0000, AngleRendererD3d11 = 0x0002, AngleRendererD3d9 = 0x0004, AngleRendererD3d11Warp = 0x0008, // "Windows Advanced Rasterization Platform" }; struct RendererConfig { QSurfaceFormat format; AngleRenderer angleRenderer = AngleRendererDefault; OpenGLRenderer rendererId() const; }; public: static RendererConfig selectSurfaceConfig(KisOpenGL::OpenGLRenderer preferredRenderer, KisConfig::RootSurfaceFormat preferredRootSurfaceFormat, bool enableDebug); static void setDefaultSurfaceConfig(const RendererConfig &config); static OpenGLRenderer getCurrentOpenGLRenderer(); static OpenGLRenderer getQtPreferredOpenGLRenderer(); static OpenGLRenderers getSupportedOpenGLRenderers(); static OpenGLRenderer getUserPreferredOpenGLRendererConfig(); static void setUserPreferredOpenGLRendererConfig(OpenGLRenderer renderer); static QString convertOpenGLRendererToConfig(OpenGLRenderer renderer); static OpenGLRenderer convertConfigToOpenGLRenderer(QString renderer); /// Request OpenGL version 3.2 static void initialize(); /// Initialize shared OpenGL context static void initializeContext(QOpenGLContext *ctx); static const QString &getDebugText(); static QStringList getOpenGLWarnings(); static bool supportsLoD(); static bool hasOpenGL3(); static bool hasOpenGLES(); /// Check for OpenGL static bool hasOpenGL(); /** * @brief supportsFilter * @return True if OpenGL provides fence sync methods. */ static bool supportsFenceSync(); + /** + * @brief supportsRenderToFBO + * @return True if OpenGL can render to FBO, used + * currently for rendering cursor with image overlay + * fx. + */ + static bool supportsRenderToFBO(); + + /** * Returns true if we have a driver that has bugged support to sync objects (a fence) * and false otherwise. */ static bool needsFenceWorkaround(); /** * @see a comment in initializeContext() */ static bool needsPixmapCacheWorkaround(); static void testingInitializeDefaultSurfaceFormat(); static void setDebugSynchronous(bool value); private: static void fakeInitWindowsOpenGL(KisOpenGL::OpenGLRenderers supportedRenderers, KisOpenGL::OpenGLRenderer preferredByQt); KisOpenGL(); }; #ifdef Q_OS_WIN Q_DECLARE_OPERATORS_FOR_FLAGS(KisOpenGL::OpenGLRenderers); #endif #endif // KIS_OPENGL_H_ diff --git a/libs/ui/opengl/kis_opengl_canvas2.cpp b/libs/ui/opengl/kis_opengl_canvas2.cpp index 59f2181f07..cf48d4bc55 100644 --- a/libs/ui/opengl/kis_opengl_canvas2.cpp +++ b/libs/ui/opengl/kis_opengl_canvas2.cpp @@ -1,1062 +1,1119 @@ /* 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 "kis_algebra_2d.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 #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; + delete overlayInvertedShader; Sync::deleteSync(glSyncObject); } bool canvasInitialized{false}; KisOpenGLImageTexturesSP openGLImageTextures; KisOpenGLShaderLoader shaderLoader; KisShaderProgram *displayShader{0}; KisShaderProgram *checkerShader{0}; KisShaderProgram *solidColorShader{0}; + KisShaderProgram *overlayInvertedShader{0}; + + QScopedPointer canvasFBO; + 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; + QOpenGLBuffer lineVertexBuffer; + QOpenGLBuffer lineTexCoordBuffer; 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); + glEnableVertexAttribArray(PROGRAM_TEXCOORD_ATTRIBUTE); // The outline buffer has a StreamDraw usage pattern, because it changes constantly - d->lineBuffer.create(); - d->lineBuffer.setUsagePattern(QOpenGLBuffer::StreamDraw); - d->lineBuffer.bind(); + d->lineVertexBuffer.create(); + d->lineVertexBuffer.setUsagePattern(QOpenGLBuffer::StreamDraw); + d->lineVertexBuffer.bind(); glVertexAttribPointer(PROGRAM_VERTEX_ATTRIBUTE, 3, GL_FLOAT, GL_FALSE, 0, 0); + + d->lineTexCoordBuffer.create(); + d->lineTexCoordBuffer.setUsagePattern(QOpenGLBuffer::StreamDraw); + d->lineTexCoordBuffer.bind(); + glVertexAttribPointer(PROGRAM_TEXCOORD_ATTRIBUTE, 2, 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; + delete d->overlayInvertedShader; d->checkerShader = 0; d->solidColorShader = 0; + d->overlayInvertedShader = 0; try { d->checkerShader = d->shaderLoader.loadCheckerShader(); d->solidColorShader = d->shaderLoader.loadSolidColorShader(); + d->overlayInvertedShader = d->shaderLoader.loadOverlayInvertedShader(); } 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*/) +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. + if (KisOpenGL::supportsRenderToFBO()) { + d->canvasFBO.reset(new QOpenGLFramebufferObject(QSize(width * devicePixelRatio(), height * devicePixelRatio()))); + } coordinatesConverter()->setCanvasWidgetSize(widgetSizeAlignedToDevicePixel()); paintGL(); } void KisOpenGLCanvas2::paintGL() { if (!OPENGL_SUCCESS) { KisConfig cfg(false); cfg.writeEntry("canvasState", "OPENGL_PAINT_STARTED"); } KisOpenglCanvasDebugger::instance()->nofityPaintRequested(); + if (d->canvasFBO) { + d->canvasFBO->bind(); + } + renderCanvasGL(); + if (d->canvasFBO) { + d->canvasFBO->release(); + QOpenGLFramebufferObject::blitFramebuffer(nullptr, d->canvasFBO.data(), GL_COLOR_BUFFER_BIT, GL_NEAREST); + QOpenGLFramebufferObject::bindDefault(); + } + 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()) { + if (!d->overlayInvertedShader->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 + // Set view/projection & texture matrices QMatrix4x4 modelMatrix(coordinatesConverter()->flakeToWidgetTransform()); modelMatrix.optimize(); modelMatrix = projectionMatrix * modelMatrix; - d->solidColorShader->setUniformValue(d->solidColorShader->location(Uniform::ModelViewProjection), modelMatrix); + d->overlayInvertedShader->setUniformValue(d->overlayInvertedShader->location(Uniform::ModelViewProjection), modelMatrix); - if (!KisOpenGL::hasOpenGLES()) { -#ifndef HAS_ONLY_OPENGL_ES - glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); + d->overlayInvertedShader->setUniformValue( + d->overlayInvertedShader->location(Uniform::FragmentColor), + QVector4D(d->cursorColor.redF(), d->cursorColor.greenF(), d->cursorColor.blueF(), 1.0f)); + + // NOTE: Texture matrix transforms flake space -> widget space -> OpenGL UV texcoord space.. + const QMatrix4x4 widgetToFBOTexCoordTransform = KisAlgebra2D::mapToRectInverse(QRect(QPoint(0, this->height()), + QSize(this->width(), -1 * this->height()))); + const QMatrix4x4 textureMatrix = widgetToFBOTexCoordTransform * + QMatrix4x4(coordinatesConverter()->flakeToWidgetTransform()); + d->overlayInvertedShader->setUniformValue(d->overlayInvertedShader->location(Uniform::TextureMatrix), textureMatrix); + + // For the legacy shader, we should use old fixed function + // blending operations if available. + if (!KisOpenGL::hasOpenGL3() && !KisOpenGL::hasOpenGLES()) { + #ifndef HAS_ONLY_OPENGL_ES + glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); glEnable(GL_COLOR_LOGIC_OP); -#ifndef Q_OS_MACOS + + #ifndef Q_OS_MACOS if (d->glFn201) { d->glFn201->glLogicOp(GL_XOR); } -#else + #else // Q_OS_MACOS glLogicOp(GL_XOR); -#endif // Q_OS_OSX + #endif // Q_OS_MACOS -#else // HAS_ONLY_OPENGL_ES + #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); + "Unexpected KisOpenGL::hasOpenGLES returned false"); + #endif // HAS_ONLY_OPENGL_ES } - 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(); + d->lineVertexBuffer.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); + for (int polyIndex = 0; polyIndex < subPathPolygons.size(); polyIndex++) { + const QPolygonF& polygon = subPathPolygons.at(polyIndex); QVector vertices; + QVector texCoords; 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()); + texCoords.resize(polygon.count()); + + for (int vertIndex = 0; vertIndex < polygon.count(); vertIndex++) { + QPointF point = polygon.at(vertIndex); + vertices[vertIndex].setX(point.x()); + vertices[vertIndex].setY(point.y()); + texCoords[vertIndex].setX(point.x()); + texCoords[vertIndex].setY(point.y()); } if (KisOpenGL::hasOpenGL3()) { - d->lineBuffer.allocate(vertices.constData(), 3 * vertices.size() * sizeof(float)); + d->lineVertexBuffer.bind(); + d->lineVertexBuffer.allocate(vertices.constData(), 3 * vertices.size() * sizeof(float)); + d->lineTexCoordBuffer.bind(); + d->lineTexCoordBuffer.allocate(texCoords.constData(), 2 * texCoords.size() * sizeof(float)); } else { - d->solidColorShader->enableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE); - d->solidColorShader->setAttributeArray(PROGRAM_VERTEX_ATTRIBUTE, vertices.constData()); + d->overlayInvertedShader->enableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE); + d->overlayInvertedShader->setAttributeArray(PROGRAM_VERTEX_ATTRIBUTE, vertices.constData()); + d->overlayInvertedShader->enableAttributeArray(PROGRAM_TEXCOORD_ATTRIBUTE); + d->overlayInvertedShader->setAttributeArray(PROGRAM_TEXCOORD_ATTRIBUTE, texCoords.constData()); } - glDrawArrays(GL_LINE_STRIP, 0, vertices.size()); + const bool usingLegacyShader = !((KisOpenGL::hasOpenGL3() || KisOpenGL::hasOpenGLES()) && KisOpenGL::supportsRenderToFBO()); + if (usingLegacyShader){ + glDrawArrays(GL_LINE_STRIP, 0, vertices.size()); + } else { + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, d->canvasFBO->texture()); + + glDrawArrays(GL_LINE_STRIP, 0, vertices.size()); + + glBindTexture(GL_TEXTURE_2D, 0); + } } if (KisOpenGL::hasOpenGL3()) { - d->lineBuffer.release(); + d->lineVertexBuffer.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(); + d->overlayInvertedShader->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(); + d->lineVertexBuffer.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)); + d->lineVertexBuffer.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->lineVertexBuffer.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(); 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); // Data returned by KoRgbU8ColorSpace comes in the order: blue, green, red. glClearColor(channels[2], channels[1], channels[0], 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 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_canvas2.h b/libs/ui/opengl/kis_opengl_canvas2.h index fc08867b5f..de46a89ce0 100644 --- a/libs/ui/opengl/kis_opengl_canvas2.h +++ b/libs/ui/opengl/kis_opengl_canvas2.h @@ -1,135 +1,136 @@ /* * Copyright (C) Boudewijn Rempt , (C) 2006 * Copyright (C) Michael Abrahams , (C) 2015 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public 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_CANVAS_2_H #define KIS_OPENGL_CANVAS_2_H #include #ifndef Q_OS_MACOS #include #else #include #endif #include "canvas/kis_canvas_widget_base.h" #include "opengl/kis_opengl_image_textures.h" #include "kritaui_export.h" #include "kis_ui_types.h" class KisCanvas2; class KisDisplayColorConverter; class QOpenGLShaderProgram; class QPainterPath; #ifndef Q_MOC_RUN #ifndef Q_OS_MACOS #define GLFunctions QOpenGLFunctions #else #define GLFunctions QOpenGLFunctions_3_2_Core #endif #endif /** * KisOpenGLCanvas is the widget that shows the actual image using OpenGL * * NOTE: if you change something in the event handling here, also change it * in the qpainter canvas. * */ class KRITAUI_EXPORT KisOpenGLCanvas2 : public QOpenGLWidget #ifndef Q_MOC_RUN , protected GLFunctions #endif , public KisCanvasWidgetBase { Q_OBJECT public: KisOpenGLCanvas2(KisCanvas2 *canvas, KisCoordinatesConverter *coordinatesConverter, QWidget *parent, KisImageWSP image, KisDisplayColorConverter *colorConverter); ~KisOpenGLCanvas2() override; public: // QOpenGLWidget void resizeGL(int width, int height) override; void initializeGL() override; void paintGL() override; QVariant inputMethodQuery(Qt::InputMethodQuery query) const override; void inputMethodEvent(QInputMethodEvent *event) override; public: void renderCanvasGL(); void renderDecorations(QPainter *painter); void paintToolOutline(const QPainterPath &path); + public: // Implement kis_abstract_canvas_widget interface void setDisplayFilter(QSharedPointer displayFilter) override; void notifyImageColorSpaceChanged(const KoColorSpace *cs) override; void setWrapAroundViewingMode(bool value) override; void channelSelectionChanged(const QBitArray &channelFlags) override; void setDisplayColorConverter(KisDisplayColorConverter *colorConverter) override; void finishResizingImage(qint32 w, qint32 h) override; KisUpdateInfoSP startUpdateCanvasProjection(const QRect & rc, const QBitArray &channelFlags) override; QRect updateCanvasProjection(KisUpdateInfoSP info) override; QVector updateCanvasProjection(const QVector &infoObjects) override; QWidget *widget() override { return this; } bool isBusy() const override; void setLodResetInProgress(bool value) override; void setDisplayFilterImpl(QSharedPointer displayFilter, bool initializing); KisOpenGLImageTexturesSP openGLImageTextures() const; public Q_SLOTS: void slotConfigChanged(); void slotPixelGridModeChanged(); private Q_SLOTS: void slotShowFloatingMessage(const QString &message, int timeout, bool priority); protected: // KisCanvasWidgetBase bool callFocusNextPrevChild(bool next) override; private: void initializeShaders(); void initializeDisplayShader(); void reportFailedShaderCompilation(const QString &context); void drawImage(); void drawCheckers(); void drawGrid(); QSize viewportDevicePixelSize() const; QSizeF widgetSizeAlignedToDevicePixel() const; private: struct Private; Private * const d; }; #endif // KIS_OPENGL_CANVAS_2_H diff --git a/libs/ui/opengl/kis_opengl_shader_loader.cpp b/libs/ui/opengl/kis_opengl_shader_loader.cpp index 306e3a56c0..574c0626b3 100644 --- a/libs/ui/opengl/kis_opengl_shader_loader.cpp +++ b/libs/ui/opengl/kis_opengl_shader_loader.cpp @@ -1,211 +1,229 @@ /* This file is part of the KDE project * Copyright (C) Julian Thijssen , (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 "kis_opengl_shader_loader.h" #include "opengl/kis_opengl.h" #include "kis_config.h" #include #include #include #define PROGRAM_VERTEX_ATTRIBUTE 0 #define PROGRAM_TEXCOORD_ATTRIBUTE 1 // Mapping of uniforms to uniform names std::map KisShaderProgram::names = { {ModelViewProjection, "modelViewProjection"}, {TextureMatrix, "textureMatrix"}, {ViewportScale, "viewportScale"}, {TexelSize, "texelSize"}, {Texture0, "texture0"}, {Texture1, "texture1"}, {FixedLodLevel, "fixedLodLevel"}, {FragmentColor, "fragColor"} }; /** * Generic shader loading function that will compile a shader program given * a vertex shader and fragment shader resource path. Extra code can be prepended * to each shader respectively using the header parameters. * * @param vertPath Resource path to a vertex shader * @param fragPath Resource path to a fragment shader * @param vertHeader Extra code which will be prepended to the vertex shader * @param fragHeader Extra code which will be prepended to the fragment shader */ KisShaderProgram *KisOpenGLShaderLoader::loadShader(QString vertPath, QString fragPath, QByteArray vertHeader, QByteArray fragHeader) { bool result; KisShaderProgram *shader = new KisShaderProgram(); // Load vertex shader QByteArray vertSource; // XXX Check can be removed and set to the MAC version after we move to Qt5.7 #ifdef Q_OS_MACOS vertSource.append(KisOpenGL::hasOpenGL3() ? "#version 150 core\n" : "#version 120\n"); // OpenColorIO doesn't support the new GLSL version yet. vertSource.append("#define texture2D texture\n"); vertSource.append("#define texture3D texture\n"); #else if (KisOpenGL::hasOpenGLES()) { vertSource.append("#version 300 es\n"); } else { vertSource.append(KisOpenGL::supportsLoD() ? "#version 130\n" : "#version 120\n"); } #endif vertSource.append(vertHeader); QFile vertexShaderFile(":/" + vertPath); vertexShaderFile.open(QIODevice::ReadOnly); vertSource.append(vertexShaderFile.readAll()); result = shader->addShaderFromSourceCode(QOpenGLShader::Vertex, vertSource); if (!result) throw ShaderLoaderException(QString("%1: %2 - Cause: %3").arg("Failed to add vertex shader source from file", vertPath, shader->log())); // Load fragment shader QByteArray fragSource; // XXX Check can be removed and set to the MAC version after we move to Qt5.7 #ifdef Q_OS_MACOS fragSource.append(KisOpenGL::hasOpenGL3() ? "#version 150 core\n" : "#version 120\n"); // OpenColorIO doesn't support the new GLSL version yet. fragSource.append("#define texture2D texture\n"); fragSource.append("#define texture3D texture\n"); #else if (KisOpenGL::hasOpenGLES()) { fragSource.append( "#version 300 es\n" "precision mediump float;\n" "precision mediump sampler3D;\n"); // OpenColorIO doesn't support the new GLSL version yet. fragSource.append("#define texture2D texture\n"); fragSource.append("#define texture3D texture\n"); } else { fragSource.append(KisOpenGL::supportsLoD() ? "#version 130\n" : "#version 120\n"); } #endif fragSource.append(fragHeader); QFile fragmentShaderFile(":/" + fragPath); fragmentShaderFile.open(QIODevice::ReadOnly); fragSource.append(fragmentShaderFile.readAll()); result = shader->addShaderFromSourceCode(QOpenGLShader::Fragment, fragSource); if (!result) throw ShaderLoaderException(QString("%1: %2 - Cause: %3").arg("Failed to add fragment shader source from file", fragPath, shader->log())); // Bind attributes shader->bindAttributeLocation("a_vertexPosition", PROGRAM_VERTEX_ATTRIBUTE); shader->bindAttributeLocation("a_textureCoordinate", PROGRAM_TEXCOORD_ATTRIBUTE); // Link result = shader->link(); if (!result) throw ShaderLoaderException(QString("Failed to link shader: ").append(vertPath)); Q_ASSERT(shader->isLinked()); return shader; } /** * Specific display shader loading function. It adds the appropriate extra code * to the fragment shader depending on what is available on the target machine. * Additionally, it picks the appropriate shader files depending on the availability * of OpenGL3. */ KisShaderProgram *KisOpenGLShaderLoader::loadDisplayShader(QSharedPointer displayFilter, bool useHiQualityFiltering) { QByteArray fragHeader; if (KisOpenGL::supportsLoD()) { fragHeader.append("#define DIRECT_LOD_FETCH\n"); if (useHiQualityFiltering) { fragHeader.append("#define HIGHQ_SCALING\n"); } } // If we have an OCIO display filter and it contains a function we add // it to our shader header which will sit on top of the fragment code. bool haveDisplayFilter = displayFilter && !displayFilter->program().isEmpty(); if (haveDisplayFilter) { fragHeader.append("#define USE_OCIO\n"); fragHeader.append(displayFilter->program().toLatin1()); } QString vertPath, fragPath; // Select appropriate shader files if (KisOpenGL::supportsLoD()) { vertPath = "matrix_transform.vert"; fragPath = "highq_downscale.frag"; } else { vertPath = "matrix_transform_legacy.vert"; fragPath = "simple_texture_legacy.frag"; } KisShaderProgram *shader = loadShader(vertPath, fragPath, QByteArray(), fragHeader); return shader; } /** * Specific checker shader loading function. It picks the appropriate shader * files depending on the availability of OpenGL3 on the target machine. */ KisShaderProgram *KisOpenGLShaderLoader::loadCheckerShader() { QString vertPath, fragPath; // Select appropriate shader files if (KisOpenGL::supportsLoD()) { vertPath = "matrix_transform.vert"; fragPath = "simple_texture.frag"; } else { vertPath = "matrix_transform_legacy.vert"; fragPath = "simple_texture_legacy.frag"; } KisShaderProgram *shader = loadShader(vertPath, fragPath, QByteArray(), QByteArray()); return shader; } /** * Specific uniform shader loading function. It picks the appropriate shader * files depending on the availability of OpenGL3 on the target machine. */ KisShaderProgram *KisOpenGLShaderLoader::loadSolidColorShader() { QString vertPath, fragPath; // Select appropriate shader files if (KisOpenGL::supportsLoD()) { vertPath = "matrix_transform.vert"; fragPath = "solid_color.frag"; } else { vertPath = "matrix_transform_legacy.vert"; fragPath = "solid_color_legacy.frag"; } KisShaderProgram *shader = loadShader(vertPath, fragPath, QByteArray(), QByteArray()); return shader; } + +KisShaderProgram *KisOpenGLShaderLoader::loadOverlayInvertedShader() +{ + QString vertPath, fragPath; + + // Select appropriate shader files + if ((KisOpenGL::hasOpenGL3() || KisOpenGL::hasOpenGLES()) && KisOpenGL::supportsRenderToFBO()) { + vertPath = "matrix_transform.vert"; + fragPath = "overlay_inverted.frag"; + } else { + vertPath = "matrix_transform_legacy.vert"; + fragPath = "solid_color_legacy.frag"; + } + + KisShaderProgram *shader = loadShader(vertPath, fragPath, QByteArray(), QByteArray()); + + return shader; +} diff --git a/libs/ui/opengl/kis_opengl_shader_loader.h b/libs/ui/opengl/kis_opengl_shader_loader.h index 0ab064032d..801bc05dca 100644 --- a/libs/ui/opengl/kis_opengl_shader_loader.h +++ b/libs/ui/opengl/kis_opengl_shader_loader.h @@ -1,91 +1,92 @@ /* * Copyright (C) Julian Thijssen , (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 "canvas/kis_display_filter.h" #include #include #include #include #include #include #include /** * An enum for storing all uniform names used in shaders */ enum Uniform { ModelViewProjection, TextureMatrix, ViewportScale, TexelSize, Texture0, Texture1, FixedLodLevel, FragmentColor }; /** * A wrapper class over Qt's QOpenGLShaderProgram to * provide access to uniform locations without * having to store them as constants next to the shader. */ class KisShaderProgram : public QOpenGLShaderProgram { public: /** * Stores the mapping of uniform enums to actual shader uniform names. * The actual shader names are necessary for calls to uniformLocation. */ static std::map names; /** * Stores uniform location in cache if it is called for the first time * and retrieves the location from the map on subsequent calls. */ int location(Uniform uniform) { std::map::const_iterator it = locationMap.find(uniform); if (it != locationMap.end()) { return it->second; } else { int location = uniformLocation(names[uniform]); locationMap[uniform] = location; return location; } } private: std::map locationMap; }; /** * A wrapper class over C++ Runtime Error, specifically to record * failures in shader compilation. Only thrown in KisOpenGLShaderLoader. */ class ShaderLoaderException : public std::runtime_error { public: ShaderLoaderException(QString error) : std::runtime_error(error.toStdString()) { } }; /** * A utility class for loading various shaders we use in Krita. It provides * specific methods for shaders that pick the correct vertex and fragment files * depending on the availability of OpenGL3. Additionally, it provides a generic * shader loading method to prevent duplication. */ class KisOpenGLShaderLoader { public: KisShaderProgram *loadDisplayShader(QSharedPointer displayFilter, bool useHiQualityFiltering); KisShaderProgram *loadCheckerShader(); KisShaderProgram *loadSolidColorShader(); + KisShaderProgram *loadOverlayInvertedShader(); private: KisShaderProgram *loadShader(QString vertPath, QString fragPath, QByteArray vertHeader, QByteArray fragHeader); }; diff --git a/libs/ui/processing/fill_processing_visitor.cpp b/libs/ui/processing/fill_processing_visitor.cpp index a4e1f25079..a32b635baf 100644 --- a/libs/ui/processing/fill_processing_visitor.cpp +++ b/libs/ui/processing/fill_processing_visitor.cpp @@ -1,148 +1,148 @@ /* * 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 "fill_processing_visitor.h" #include #include #include #include #include "lazybrush/kis_colorize_mask.h" FillProcessingVisitor::FillProcessingVisitor(KisPaintDeviceSP refPaintDevice, const QPoint &startPoint, KisSelectionSP selection, KisResourcesSnapshotSP resources, bool useFastMode, bool usePattern, bool selectionOnly, int feather, int sizemod, int fillThreshold, bool unmerged, bool useBgColor) : m_refPaintDevice(refPaintDevice), m_startPoint(startPoint), m_selection(selection), m_useFastMode(useFastMode), m_selectionOnly(selectionOnly), m_usePattern(usePattern), m_resources(resources), m_feather(feather), m_sizemod(sizemod), m_fillThreshold(fillThreshold), m_unmerged(unmerged), m_useBgColor(useBgColor) { } void FillProcessingVisitor::visitExternalLayer(KisExternalLayer *layer, KisUndoAdapter *undoAdapter) { Q_UNUSED(layer); Q_UNUSED(undoAdapter); } void FillProcessingVisitor::visitNodeWithPaintDevice(KisNode *node, KisUndoAdapter *undoAdapter) { KisPaintDeviceSP device = node->paintDevice(); Q_ASSERT(device); ProgressHelper helper(node); fillPaintDevice(device, undoAdapter, helper); } void FillProcessingVisitor::fillPaintDevice(KisPaintDeviceSP device, KisUndoAdapter *undoAdapter, ProgressHelper &helper) { QRect fillRect = m_resources->image()->bounds(); if (!device->defaultBounds()->wrapAroundMode() && !fillRect.contains(m_startPoint)) { return; } if (m_selectionOnly) { KisPaintDeviceSP filledDevice = device->createCompositionSourceDevice(); KisFillPainter fillPainter(filledDevice); fillPainter.setProgress(helper.updater()); if (m_usePattern) { - fillPainter.fillRect(fillRect, m_resources->currentPattern()); + fillPainter.fillRect(fillRect, m_resources->currentPattern(), m_resources->fillTransform()); } else if (m_useBgColor) { fillPainter.fillRect(fillRect, m_resources->currentBgColor(), m_resources->opacity()); } else { fillPainter.fillRect(fillRect, m_resources->currentFgColor(), m_resources->opacity()); } QVector dirtyRect = fillPainter.takeDirtyRegion(); KisPainter painter(device, m_selection); painter.beginTransaction(); m_resources->setupPainter(&painter); Q_FOREACH (const QRect &rc, dirtyRect) { painter.bitBlt(rc.topLeft(), filledDevice, rc); } painter.endTransaction(undoAdapter); } else { QPoint startPoint = m_startPoint; if (device->defaultBounds()->wrapAroundMode()) { startPoint = KisWrappedRect::ptToWrappedPt(startPoint, device->defaultBounds()->bounds()); } KisFillPainter fillPainter(device, m_selection); fillPainter.beginTransaction(); m_resources->setupPainter(&fillPainter); fillPainter.setProgress(helper.updater()); fillPainter.setSizemod(m_sizemod); fillPainter.setFeather(m_feather); fillPainter.setFillThreshold(m_fillThreshold); fillPainter.setCareForSelection(true); fillPainter.setWidth(fillRect.width()); fillPainter.setHeight(fillRect.height()); fillPainter.setUseCompositioning(!m_useFastMode); KisPaintDeviceSP sourceDevice = m_unmerged ? device : m_refPaintDevice; if (m_usePattern) { - fillPainter.fillPattern(startPoint.x(), startPoint.y(), sourceDevice); + fillPainter.fillPattern(startPoint.x(), startPoint.y(), sourceDevice, m_resources->fillTransform()); } else { fillPainter.fillColor(startPoint.x(), startPoint.y(), sourceDevice); } fillPainter.endTransaction(undoAdapter); } } void FillProcessingVisitor::visitColorizeMask(KisColorizeMask *mask, KisUndoAdapter *undoAdapter) { // we fill only the coloring project so the user can work // with the mask like with a usual paint layer ProgressHelper helper(mask); fillPaintDevice(mask->coloringProjection(), undoAdapter, helper); } diff --git a/libs/ui/tests/kis_shape_selection_test.cpp b/libs/ui/tests/kis_shape_selection_test.cpp index d49cffac52..9e45c2dd93 100644 --- a/libs/ui/tests/kis_shape_selection_test.cpp +++ b/libs/ui/tests/kis_shape_selection_test.cpp @@ -1,192 +1,341 @@ /* * Copyright (c) 2008 Sven Langkamp * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_shape_selection_test.h" #include #include #include #include #include #include #include "kis_selection.h" #include "kis_pixel_selection.h" #include "flake/kis_shape_selection.h" #include "kis_image.h" #include "testutil.h" #include "kistest.h" #include #include #include "kis_transaction.h" void KisShapeSelectionTest::testAddChild() { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); QScopedPointer doc(KisPart::instance()->createDocument()); QColor qc(Qt::white); qc.setAlpha(0); KoColor bgColor(qc, cs); doc->newImage("test", 300, 300, cs, bgColor, KisConfig::CANVAS_COLOR, 1, "test", 100); KisImageSP image = doc->image(); KisSelectionSP selection = new KisSelection(); - QVERIFY(selection->hasPixelSelection() == false); - QVERIFY(selection->hasShapeSelection() == false); + QVERIFY(!selection->hasNonEmptyPixelSelection()); + QVERIFY(!selection->hasNonEmptyShapeSelection()); KisPixelSelectionSP pixelSelection = selection->pixelSelection(); pixelSelection->select(QRect(0, 0, 100, 100)); QCOMPARE(TestUtil::alphaDevicePixel(pixelSelection, 25, 25), MAX_SELECTED); QCOMPARE(selection->selectedExactRect(), QRect(0, 0, 100, 100)); QRectF rect(50, 50, 100, 100); QTransform matrix; matrix.scale(1 / image->xRes(), 1 / image->yRes()); rect = matrix.mapRect(rect); KoPathShape* shape = new KoPathShape(); shape->setShapeId(KoPathShapeId); shape->moveTo(rect.topLeft()); shape->lineTo(rect.topRight()); shape->lineTo(rect.bottomRight()); shape->lineTo(rect.bottomLeft()); shape->close(); KisShapeSelection * shapeSelection = new KisShapeSelection(doc->shapeController(), image, selection); - selection->setShapeSelection(shapeSelection); + selection->convertToVectorSelectionNoUndo(shapeSelection); shapeSelection->addShape(shape); - QVERIFY(selection->hasShapeSelection()); + QVERIFY(selection->hasNonEmptyShapeSelection()); selection->updateProjection(); image->waitForDone(); QCOMPARE(selection->selectedExactRect(), QRect(50, 50, 100, 100)); } KoPathShape *createRectangularShape(const QRectF &rect) { KoPathShape* shape = new KoPathShape(); shape->setShapeId(KoPathShapeId); shape->moveTo(rect.topLeft()); shape->lineTo(rect.topRight()); shape->lineTo(rect.bottomRight()); shape->lineTo(rect.bottomLeft()); shape->close(); return shape; } void KisShapeSelectionTest::testUndoFlattening() { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); QScopedPointer doc(KisPart::instance()->createDocument()); KoColor bgColor(QColor(255, 255, 255, 0), cs); doc->newImage("test", 300, 300, cs, bgColor, KisConfig::CANVAS_COLOR, 1, "test", 100); KisImageSP image = doc->image(); QCOMPARE(image->locked(), false); KisSelectionSP selection = new KisSelection(); - QCOMPARE(selection->hasPixelSelection(), false); - QCOMPARE(selection->hasShapeSelection(), false); + QCOMPARE(selection->hasNonEmptyPixelSelection(), false); + QCOMPARE(selection->hasNonEmptyShapeSelection(), false); selection->setParentNode(image->root()); KisPixelSelectionSP pixelSelection = selection->pixelSelection(); pixelSelection->select(QRect(0, 0, 100, 100)); QCOMPARE(TestUtil::alphaDevicePixel(pixelSelection, 25, 25), MAX_SELECTED); QCOMPARE(selection->selectedExactRect(), QRect(0, 0, 100, 100)); QTransform matrix; matrix.scale(1 / image->xRes(), 1 / image->yRes()); const QRectF srcRect1(50, 50, 100, 100); const QRectF rect1 = matrix.mapRect(srcRect1); - KisShapeSelection * shapeSelection = new KisShapeSelection(doc->shapeController(), image, selection); - selection->setShapeSelection(shapeSelection); + KisShapeSelection * shapeSelection1 = new KisShapeSelection(doc->shapeController(), image, selection); + selection->convertToVectorSelectionNoUndo(shapeSelection1); KoPathShape *shape1 = createRectangularShape(rect1); - shapeSelection->addShape(shape1); + shapeSelection1->addShape(shape1); - QVERIFY(selection->hasShapeSelection()); + QVERIFY(selection->hasNonEmptyShapeSelection()); selection->pixelSelection()->clear(); QCOMPARE(selection->selectedExactRect(), QRect()); selection->updateProjection(); image->waitForDone(); QCOMPARE(selection->selectedExactRect(), srcRect1.toRect()); QCOMPARE(selection->outlineCacheValid(), true); QCOMPARE(selection->outlineCache().boundingRect(), srcRect1); - QCOMPARE(selection->hasShapeSelection(), true); + QCOMPARE(selection->hasNonEmptyShapeSelection(), true); - KisTransaction t1(selection->pixelSelection()); + KisSelectionTransaction t1(selection->pixelSelection()); selection->pixelSelection()->clear(); KUndo2Command *cmd1 = t1.endAndTake(); + cmd1->redo(); // first redo QTest::qWait(400); image->waitForDone(); QCOMPARE(selection->selectedExactRect(), QRect()); QCOMPARE(selection->outlineCacheValid(), true); QCOMPARE(selection->outlineCache().boundingRect(), QRectF()); - QCOMPARE(selection->hasShapeSelection(), false); + QCOMPARE(selection->hasNonEmptyShapeSelection(), false); const QRectF srcRect2(10, 10, 20, 20); const QRectF rect2 = matrix.mapRect(srcRect2); KoPathShape *shape2 = createRectangularShape(rect2); - shapeSelection->addShape(shape2); + + KisShapeSelection * shapeSelection2 = new KisShapeSelection(doc->shapeController(), image, selection); + KUndo2Command *cmd2 = selection->convertToVectorSelection(shapeSelection2); + cmd2->redo(); // first redo + + shapeSelection2->addShape(shape2); QTest::qWait(400); image->waitForDone(); QCOMPARE(selection->selectedExactRect(), srcRect2.toRect()); QCOMPARE(selection->outlineCacheValid(), true); QCOMPARE(selection->outlineCache().boundingRect(), srcRect2); - QCOMPARE(selection->hasShapeSelection(), true); + QCOMPARE(selection->hasNonEmptyShapeSelection(), true); - shapeSelection->removeShape(shape2); + shapeSelection1->removeShape(shape2); QTest::qWait(400); image->waitForDone(); QCOMPARE(selection->selectedExactRect(), QRect()); QCOMPARE(selection->outlineCacheValid(), true); QCOMPARE(selection->outlineCache().boundingRect(), QRectF()); - QCOMPARE(selection->hasShapeSelection(), false); + QCOMPARE(selection->hasNonEmptyShapeSelection(), false); + cmd2->undo(); cmd1->undo(); QTest::qWait(400); image->waitForDone(); QCOMPARE(selection->selectedExactRect(), srcRect1.toRect()); QCOMPARE(selection->outlineCacheValid(), true); QCOMPARE(selection->outlineCache().boundingRect(), srcRect1); - QCOMPARE(selection->hasShapeSelection(), true); + QCOMPARE(selection->hasNonEmptyShapeSelection(), true); + + delete cmd2; + delete cmd1; + QTest::qWait(400); + +} + +#include "kis_paint_device_debug_utils.h" + +void KisShapeSelectionTest::testHistoryOnFlattening() +{ + const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); + QScopedPointer doc(KisPart::instance()->createDocument()); + KoColor bgColor(QColor(255, 255, 255, 0), cs); + doc->newImage("test", 300, 300, cs, bgColor, KisConfig::CANVAS_COLOR, 1, "test", 100); + KisImageSP image = doc->image(); + + QCOMPARE(image->locked(), false); + + KisSelectionSP selection = new KisSelection(); + QCOMPARE(selection->hasNonEmptyPixelSelection(), false); + QCOMPARE(selection->hasNonEmptyShapeSelection(), false); + + selection->setParentNode(image->root()); + + KisPixelSelectionSP pixelSelection = selection->pixelSelection(); + + KisSelectionTransaction t0(pixelSelection); + pixelSelection->select(QRect(70, 70, 180, 20)); + QScopedPointer cmd0(t0.endAndTake()); + cmd0->redo(); // first redo + + KisSelectionTransaction t1(pixelSelection); + pixelSelection->clear(); + pixelSelection->select(QRect(0, 0, 100, 100)); + QScopedPointer cmd1(t1.endAndTake()); + cmd1->redo(); // first redo + + // KIS_DUMP_DEVICE_2(pixelSelection, QRect(0,0,300,300), "00_0pixel", "dd"); + QCOMPARE(TestUtil::alphaDevicePixel(pixelSelection, 25, 25), MAX_SELECTED); + QCOMPARE(selection->selectedExactRect(), QRect(0, 0, 100, 100)); + + QTransform matrix; + matrix.scale(1 / image->xRes(), 1 / image->yRes()); + const QRectF srcRect1(50, 50, 100, 100); + const QRectF rect1 = matrix.mapRect(srcRect1); + + KisShapeSelection * shapeSelection = new KisShapeSelection(doc->shapeController(), image, selection); + + QScopedPointer cmd2(selection->convertToVectorSelection(shapeSelection)); + cmd2->redo(); + + QVERIFY(!selection->hasNonEmptyShapeSelection()); + QTest::qWait(200); + image->waitForDone(); + + // KIS_DUMP_DEVICE_2(pixelSelection, QRect(0,0,300,300), "00_1converted", "dd"); + QCOMPARE(selection->selectedExactRect(), QRect()); + + KoPathShape *shape1 = createRectangularShape(rect1); + shapeSelection->addShape(shape1); + + QVERIFY(selection->hasNonEmptyShapeSelection()); + QTest::qWait(200); + image->waitForDone(); + + // KIS_DUMP_DEVICE_2(pixelSelection, QRect(0,0,300,300), "01_vector", "dd"); + QCOMPARE(selection->selectedExactRect(), QRect(50, 50, 100, 100)); + + KisSelectionTransaction flatteningTransaction(pixelSelection); + pixelSelection->select(QRect(80, 80, 100, 83)); + + QScopedPointer cmd3(flatteningTransaction.endAndTake()); + cmd3->redo(); // first redo! + + // KIS_DUMP_DEVICE_2(pixelSelection, QRect(0,0,300,300), "02_flattened", "dd"); + QCOMPARE(selection->selectedExactRect(), QRect(50, 50, 130, 113)); + QVERIFY(!selection->hasNonEmptyShapeSelection()); + + cmd3->undo(); + QTest::qWait(200); + image->waitForDone(); + + // KIS_DUMP_DEVICE_2(pixelSelection, QRect(0,0,300,300), "03_undo_flattening", "dd"); + QCOMPARE(selection->selectedExactRect(), QRect(50, 50, 100, 100)); + QVERIFY(selection->hasNonEmptyShapeSelection()); + + cmd3->redo(); + QTest::qWait(200); + image->waitForDone(); + + // KIS_DUMP_DEVICE_2(pixelSelection, QRect(0,0,300,300), "04_redo_flattening", "dd"); + QCOMPARE(selection->selectedExactRect(), QRect(50, 50, 130, 113)); + QVERIFY(!selection->hasNonEmptyShapeSelection()); + + cmd3->undo(); + QTest::qWait(200); + image->waitForDone(); + + // KIS_DUMP_DEVICE_2(pixelSelection, QRect(0,0,300,300), "05_2ndundo_flattening", "dd"); + QCOMPARE(selection->selectedExactRect(), QRect(50, 50, 100, 100)); + QVERIFY(selection->hasNonEmptyShapeSelection()); + + shapeSelection->removeShape(shape1); + + QTest::qWait(200); + image->waitForDone(); + + // KIS_DUMP_DEVICE_2(pixelSelection, QRect(0,0,300,300), "06_undo_add_shape", "dd"); + QVERIFY(selection->shapeSelection()); + QVERIFY(!selection->hasNonEmptyShapeSelection()); + QCOMPARE(selection->selectedExactRect(), QRect()); + + cmd2->undo(); + + QTest::qWait(200); + image->waitForDone(); + + // KIS_DUMP_DEVICE_2(pixelSelection, QRect(0,0,300,300), "07_undo_conversion", "dd"); + QVERIFY(!selection->shapeSelection()); + QVERIFY(!selection->hasNonEmptyShapeSelection()); + QCOMPARE(selection->selectedExactRect(), QRect(0, 0, 100, 100)); + + cmd1->undo(); + + QTest::qWait(200); + image->waitForDone(); + + // KIS_DUMP_DEVICE_2(pixelSelection, QRect(0,0,300,300), "08_undo_initial_paint", "dd"); + QVERIFY(!selection->shapeSelection()); + QVERIFY(!selection->hasNonEmptyShapeSelection()); + QCOMPARE(selection->selectedExactRect(), QRect(70, 70, 180, 20)); + + cmd0->undo(); + + QTest::qWait(200); + image->waitForDone(); + + // KIS_DUMP_DEVICE_2(pixelSelection, QRect(0,0,300,300), "09_undo_zero_paint", "dd"); + QVERIFY(!selection->shapeSelection()); + QVERIFY(!selection->hasNonEmptyShapeSelection()); + QCOMPARE(selection->selectedExactRect(), QRect()); } KISTEST_MAIN(KisShapeSelectionTest) diff --git a/libs/ui/tests/kis_shape_selection_test.h b/libs/ui/tests/kis_shape_selection_test.h index d7340b9692..57f0d5294b 100644 --- a/libs/ui/tests/kis_shape_selection_test.h +++ b/libs/ui/tests/kis_shape_selection_test.h @@ -1,36 +1,38 @@ /* * Copyright (c) 2008 Sven Langkamp * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_SHAPE_SELETION_TEST_H #define KIS_SHAPE_SELETION_TEST_H #include class KisShapeSelectionTest : public QObject { Q_OBJECT private Q_SLOTS: void testAddChild(); void testUndoFlattening(); + + void testHistoryOnFlattening(); }; #endif diff --git a/libs/ui/tests/util.h b/libs/ui/tests/util.h index f7e8a5b64b..7d01674acd 100644 --- a/libs/ui/tests/util.h +++ b/libs/ui/tests/util.h @@ -1,230 +1,230 @@ /* * Copyright (c) 2008 Boudewijn Rempt boud@valdyas.org * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _UTIL_H_ #define _UTIL_H_ #include #include #include #include #include #include #include #include #include #include "kis_types.h" #include "filter/kis_filter_registry.h" #include "filter/kis_filter_configuration.h" #include "filter/kis_filter.h" #include "KisPart.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 "kis_default_bounds.h" #include "kis_transform_mask_params_interface.h" #include "kis_shape_controller.h" #include KisSelectionSP createPixelSelection(KisPaintDeviceSP paintDevice) { KisSelectionSP pixelSelection = new KisSelection(new KisSelectionDefaultBounds(paintDevice)); KisFillPainter gc(pixelSelection->pixelSelection()); gc.fillRect(10, 10, 200, 200, KoColor(gc.device()->colorSpace())); gc.fillRect(150, 150, 200, 200, KoColor(QColor(100, 100, 100, 100), gc.device()->colorSpace())); gc.end(); return pixelSelection; } KisSelectionSP createVectorSelection(KisPaintDeviceSP paintDevice, KisImageWSP image, KoShapeControllerBase *shapeController) { KisSelectionSP vectorSelection = new KisSelection(new KisSelectionDefaultBounds(paintDevice)); KoPathShape* path = new KoPathShape(); path->setShapeId(KoPathShapeId); path->moveTo(QPointF(10, 10)); path->lineTo(QPointF(10, 10) + QPointF(100, 0)); path->lineTo(QPointF(100, 100)); path->lineTo(QPointF(10, 10) + QPointF(0, 100)); path->close(); path->normalize(); KisShapeSelection* shapeSelection = new KisShapeSelection(shapeController, image, vectorSelection); shapeSelection->addShape(path); - vectorSelection->setShapeSelection(shapeSelection); + vectorSelection->convertToVectorSelectionNoUndo(shapeSelection); return vectorSelection; } QTransform createTestingTransform() { return QTransform(1,2,3,4,5,6,7,8,9); } KisDocument* createCompleteDocument(bool shouldMaskToShapeLayer = false) { KisImageWSP image = new KisImage(0, 1024, 1024, KoColorSpaceRegistry::instance()->rgb8(), "test for roundtrip"); KisDocument *doc = qobject_cast(KisPart::instance()->createDocument()); doc->setCurrentImage(image); doc->documentInfo()->setAboutInfo("title", image->objectName()); KisGroupLayerSP group1 = new KisGroupLayer(image, "group1", 50); KisGroupLayerSP group2 = new KisGroupLayer(image, "group2", 100); KisPaintLayerSP paintLayer1 = new KisPaintLayer(image, "paintlayer1", OPACITY_OPAQUE_U8); paintLayer1->setUserLocked(true); QBitArray channelFlags(4); channelFlags[0] = true; channelFlags[2] = true; paintLayer1->setChannelFlags(channelFlags); { KisFillPainter gc(paintLayer1->paintDevice()); gc.fillRect(10, 10, 200, 200, KoColor(Qt::red, paintLayer1->paintDevice()->colorSpace())); gc.end(); } KisPaintLayerSP paintLayer2 = new KisPaintLayer(image, "paintlayer2", OPACITY_TRANSPARENT_U8, KoColorSpaceRegistry::instance()->lab16()); paintLayer2->setVisible(false); { KisFillPainter gc(paintLayer2->paintDevice()); gc.fillRect(0, 0, 900, 1024, KoColor(QColor(10, 20, 30), paintLayer2->paintDevice()->colorSpace())); gc.end(); } KisCloneLayerSP cloneLayer1 = new KisCloneLayer(group1, image, "clonelayer1", 150); cloneLayer1->setX(100); cloneLayer1->setY(100); KisSelectionSP pixelSelection = createPixelSelection(paintLayer1->paintDevice()); KisFilterConfigurationSP kfc = KisFilterRegistry::instance()->get("pixelize")->defaultConfiguration(KisGlobalResourcesInterface::instance()); Q_ASSERT(kfc); KisAdjustmentLayerSP adjustmentLayer1 = new KisAdjustmentLayer(image, "adjustmentLayer1", kfc->cloneWithResourcesSnapshot(), pixelSelection); KisSelectionSP vectorSelection = createVectorSelection(paintLayer2->paintDevice(), image, doc->shapeController()); KisAdjustmentLayerSP adjustmentLayer2 = new KisAdjustmentLayer(image, "adjustmentLayer2", kfc->cloneWithResourcesSnapshot(), vectorSelection); image->addNode(paintLayer1); image->addNode(group1); image->addNode(paintLayer2, group1); image->addNode(group2); image->addNode(cloneLayer1, group2); image->addNode(adjustmentLayer1, group2); // KoShapeContainer * parentContainer = // dynamic_cast(doc->shapeForNode(group1)); KoPathShape* path = new KoPathShape(); path->setShapeId(KoPathShapeId); path->moveTo(QPointF(10, 10)); path->lineTo(QPointF(10, 10) + QPointF(100, 0)); path->lineTo(QPointF(100, 100)); path->lineTo(QPointF(10, 10) + QPointF(0, 100)); path->close(); path->normalize(); KisShapeLayerSP shapeLayer = new KisShapeLayer(doc->shapeController(), image, "shapeLayer1", 75); shapeLayer->addShape(path); image->addNode(shapeLayer, group1); image->addNode(adjustmentLayer2, group1); KisFilterMaskSP filterMask1 = new KisFilterMask(); filterMask1->setName("filterMask1"); kfc = KisFilterRegistry::instance()->get("pixelize")->defaultConfiguration(KisGlobalResourcesInterface::instance()); filterMask1->setFilter(kfc->cloneWithResourcesSnapshot()); kfc = 0; // kfc cannot be shared! filterMask1->setSelection(createPixelSelection(paintLayer1->paintDevice())); image->addNode(filterMask1, paintLayer1); KisFilterMaskSP filterMask2 = new KisFilterMask(); filterMask2->setName("filterMask2"); kfc = KisFilterRegistry::instance()->get("pixelize")->defaultConfiguration(KisGlobalResourcesInterface::instance()); filterMask2->setFilter(kfc->cloneWithResourcesSnapshot()); kfc = 0; // kfc cannot be shared! filterMask2->setSelection(createVectorSelection(paintLayer2->paintDevice(), image, doc->shapeController())); image->addNode(filterMask2, paintLayer2); KisTransparencyMaskSP transparencyMask1 = new KisTransparencyMask(); transparencyMask1->setName("transparencyMask1"); transparencyMask1->setSelection(createPixelSelection(paintLayer1->paintDevice())); image->addNode(transparencyMask1, group1); KisTransparencyMaskSP transparencyMask2 = new KisTransparencyMask(); transparencyMask2->setName("transparencyMask2"); transparencyMask2->setSelection(createPixelSelection(paintLayer1->paintDevice())); image->addNode(transparencyMask2, group2); KisSelectionMaskSP selectionMask1 = new KisSelectionMask(image); image->addNode(selectionMask1, paintLayer1); selectionMask1->setName("selectionMask1"); selectionMask1->setSelection(createPixelSelection(paintLayer1->paintDevice())); KisSelectionMaskSP selectionMask2 = new KisSelectionMask(image); selectionMask2->setName("selectionMask2"); selectionMask2->setSelection(createPixelSelection(paintLayer2->paintDevice())); image->addNode(selectionMask2, paintLayer2); KisTransformMaskSP transformMask = new KisTransformMask(); transformMask->setName("testTransformMask"); transformMask->setTransformParams(KisTransformMaskParamsInterfaceSP( new KisDumbTransformMaskParams(createTestingTransform()))); image->addNode(transformMask, paintLayer2); if (shouldMaskToShapeLayer) { // add all-visible transparency mask to crash a shape layer KisTransparencyMaskSP transparencyMask3 = new KisTransparencyMask(); transparencyMask3->setName("crashy-transparency-mask"); transparencyMask3->initSelection(shapeLayer); image->addNode(transparencyMask3, shapeLayer); } return doc; } KisDocument *createEmptyDocument() { KisImageWSP image = new KisImage(0, 1024, 1024, KoColorSpaceRegistry::instance()->rgb8(), "test for roundtrip"); KisDocument *doc = qobject_cast(KisPart::instance()->createDocument()); doc->setCurrentImage(image); doc->documentInfo()->setAboutInfo("title", image->objectName()); return doc; } #endif diff --git a/libs/ui/tool/kis_figure_painting_tool_helper.cpp b/libs/ui/tool/kis_figure_painting_tool_helper.cpp index 11919b1be9..68222184f8 100644 --- a/libs/ui/tool/kis_figure_painting_tool_helper.cpp +++ b/libs/ui/tool/kis_figure_painting_tool_helper.cpp @@ -1,191 +1,195 @@ /* * Copyright (c) 2011 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_figure_painting_tool_helper.h" #include #include "kis_resources_snapshot.h" #include #include "kis_image.h" #include "kis_painter.h" #include #include "KisAsyncronousStrokeUpdateHelper.h" KisFigurePaintingToolHelper::KisFigurePaintingToolHelper(const KUndo2MagicString &name, KisImageWSP image, KisNodeSP currentNode, KoCanvasResourceProvider *resourceManager, KisToolShapeUtils::StrokeStyle strokeStyle, - KisToolShapeUtils::FillStyle fillStyle) + KisToolShapeUtils::FillStyle fillStyle, + QTransform fillTransform) { m_strokesFacade = image.data(); m_resources = new KisResourcesSnapshot(image, currentNode, resourceManager); - setupPaintStyles(m_resources, strokeStyle, fillStyle); + setupPaintStyles(m_resources, strokeStyle, fillStyle, fillTransform); KisFreehandStrokeInfo *strokeInfo = new KisFreehandStrokeInfo(); KisStrokeStrategy *stroke = new FreehandStrokeStrategy(m_resources, strokeInfo, name); m_strokeId = m_strokesFacade->startStroke(stroke); } void KisFigurePaintingToolHelper::setupPaintStyles(KisResourcesSnapshotSP resources, KisToolShapeUtils::StrokeStyle strokeStyle, - KisToolShapeUtils::FillStyle fillStyle) + KisToolShapeUtils::FillStyle fillStyle, + QTransform fillTransform) { using namespace KisToolShapeUtils; const KoColor fgColor = resources->currentFgColor(); const KoColor bgColor = resources->currentBgColor(); switch (strokeStyle) { case StrokeStyleNone: resources->setStrokeStyle(KisPainter::StrokeStyleNone); break; case StrokeStyleForeground: resources->setStrokeStyle(KisPainter::StrokeStyleBrush); break; case StrokeStyleBackground: resources->setStrokeStyle(KisPainter::StrokeStyleBrush); resources->setFGColorOverride(bgColor); resources->setBGColorOverride(fgColor); if (fillStyle == FillStyleForegroundColor) { fillStyle = FillStyleBackgroundColor; } else if (fillStyle == FillStyleBackgroundColor) { fillStyle = FillStyleForegroundColor; } break; }; switch (fillStyle) { case FillStyleForegroundColor: resources->setFillStyle(KisPainter::FillStyleForegroundColor); break; case FillStyleBackgroundColor: resources->setFillStyle(KisPainter::FillStyleBackgroundColor); break; case FillStylePattern: resources->setFillStyle(KisPainter::FillStylePattern); break; case FillStyleNone: resources->setFillStyle(KisPainter::FillStyleNone); break; } + + resources->setFillTransform(fillTransform); } KisFigurePaintingToolHelper::~KisFigurePaintingToolHelper() { m_strokesFacade->addJob(m_strokeId, new KisAsyncronousStrokeUpdateHelper::UpdateData(true)); m_strokesFacade->endStroke(m_strokeId); } void KisFigurePaintingToolHelper::paintLine(const KisPaintInformation &pi0, const KisPaintInformation &pi1) { m_strokesFacade->addJob(m_strokeId, new FreehandStrokeStrategy::Data(0, pi0, pi1)); } void KisFigurePaintingToolHelper::paintPolyline(const vQPointF &points) { m_strokesFacade->addJob(m_strokeId, new FreehandStrokeStrategy::Data(0, FreehandStrokeStrategy::Data::POLYLINE, points)); } void KisFigurePaintingToolHelper::paintPolygon(const vQPointF &points) { m_strokesFacade->addJob(m_strokeId, new FreehandStrokeStrategy::Data(0, FreehandStrokeStrategy::Data::POLYGON, points)); } void KisFigurePaintingToolHelper::paintRect(const QRectF &rect) { m_strokesFacade->addJob(m_strokeId, new FreehandStrokeStrategy::Data(0, FreehandStrokeStrategy::Data::RECT, rect)); } void KisFigurePaintingToolHelper::paintEllipse(const QRectF &rect) { m_strokesFacade->addJob(m_strokeId, new FreehandStrokeStrategy::Data(0, FreehandStrokeStrategy::Data::ELLIPSE, rect)); } void KisFigurePaintingToolHelper::paintPainterPath(const QPainterPath &path) { m_strokesFacade->addJob(m_strokeId, new FreehandStrokeStrategy::Data(0, FreehandStrokeStrategy::Data::PAINTER_PATH, path)); } void KisFigurePaintingToolHelper::setFGColorOverride(const KoColor &color) { m_resources->setFGColorOverride(color); } void KisFigurePaintingToolHelper::setBGColorOverride(const KoColor &color) { m_resources->setBGColorOverride(color); } void KisFigurePaintingToolHelper::setSelectionOverride(KisSelectionSP m_selection) { m_resources->setSelectionOverride(m_selection); } void KisFigurePaintingToolHelper::setBrush(const KisPaintOpPresetSP &brush) { m_resources->setBrush(brush); } void KisFigurePaintingToolHelper::paintPainterPathQPen(const QPainterPath path, const QPen &pen, const KoColor &color) { m_strokesFacade->addJob(m_strokeId, new FreehandStrokeStrategy::Data(0, FreehandStrokeStrategy::Data::QPAINTER_PATH, path, pen, color)); } void KisFigurePaintingToolHelper::paintPainterPathQPenFill(const QPainterPath path, const QPen &pen, const KoColor &color) { m_strokesFacade->addJob(m_strokeId, new FreehandStrokeStrategy::Data(0, FreehandStrokeStrategy::Data::QPAINTER_PATH_FILL, path, pen, color)); } diff --git a/libs/ui/tool/kis_figure_painting_tool_helper.h b/libs/ui/tool/kis_figure_painting_tool_helper.h index 5c5348a64e..0cf7e5ba72 100644 --- a/libs/ui/tool/kis_figure_painting_tool_helper.h +++ b/libs/ui/tool/kis_figure_painting_tool_helper.h @@ -1,67 +1,69 @@ /* * 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_FIGURE_PAINTING_TOOL_HELPER_H #define __KIS_FIGURE_PAINTING_TOOL_HELPER_H #include "kis_types.h" #include "kritaui_export.h" #include #include "strokes/freehand_stroke.h" #include "KisToolShapeUtils.h" class KoCanvasResourceProvider; class KisStrokesFacade; class KRITAUI_EXPORT KisFigurePaintingToolHelper { public: KisFigurePaintingToolHelper(const KUndo2MagicString &name, KisImageWSP image, KisNodeSP currentNode, KoCanvasResourceProvider *resourceManager, KisToolShapeUtils::StrokeStyle strokeStyle, - KisToolShapeUtils::FillStyle fillStyle); + KisToolShapeUtils::FillStyle fillStyle, + QTransform fillTransform = QTransform()); ~KisFigurePaintingToolHelper(); void paintLine(const KisPaintInformation &pi0, const KisPaintInformation &pi1); void paintPolyline(const vQPointF &points); void paintPolygon(const vQPointF &points); void paintRect(const QRectF &rect); void paintEllipse(const QRectF &rect); void paintPainterPath(const QPainterPath &path); void setFGColorOverride(const KoColor &color); void setBGColorOverride(const KoColor &color); void setSelectionOverride(KisSelectionSP m_selection); void setBrush(const KisPaintOpPresetSP &brush); void paintPainterPathQPen(const QPainterPath, const QPen &pen, const KoColor &color); void paintPainterPathQPenFill(const QPainterPath, const QPen &pen, const KoColor &color); private: void setupPaintStyles(KisResourcesSnapshotSP resources, KisToolShapeUtils::StrokeStyle strokeStyle, - KisToolShapeUtils::FillStyle fillStyle); + KisToolShapeUtils::FillStyle fillStyle, + QTransform fillTransform); private: KisStrokeId m_strokeId; KisResourcesSnapshotSP m_resources; KisStrokesFacade *m_strokesFacade; }; #endif /* __KIS_FIGURE_PAINTING_TOOL_HELPER_H */ diff --git a/libs/ui/tool/kis_resources_snapshot.cpp b/libs/ui/tool/kis_resources_snapshot.cpp index 7112fb0352..d42043ebab 100644 --- a/libs/ui/tool/kis_resources_snapshot.cpp +++ b/libs/ui/tool/kis_resources_snapshot.cpp @@ -1,433 +1,446 @@ /* * Copyright (c) 2011 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_resources_snapshot.h" #include #include #include #include #include #include #include #include #include "kis_canvas_resource_provider.h" #include "filter/kis_filter_configuration.h" #include "kis_image.h" #include "kis_paint_device.h" #include "kis_paint_layer.h" #include "kis_selection.h" #include "kis_selection_mask.h" #include "kis_algebra_2d.h" struct KisResourcesSnapshot::Private { Private() : currentPattern(0) , currentGradient(0) , currentGenerator(0) , compositeOp(0) { } KisImageSP image; KisDefaultBoundsBaseSP bounds; KoColor currentFgColor; KoColor currentBgColor; KoPatternSP currentPattern; KoAbstractGradientSP currentGradient; KisPaintOpPresetSP currentPaintOpPreset; KisNodeSP currentNode; qreal currentExposure; KisFilterConfigurationSP currentGenerator; QPointF axesCenter; bool mirrorMaskHorizontal = false; bool mirrorMaskVertical = false; quint8 opacity = OPACITY_OPAQUE_U8; QString compositeOpId = COMPOSITE_OVER; const KoCompositeOp *compositeOp; KisPainter::StrokeStyle strokeStyle = KisPainter::StrokeStyleBrush; KisPainter::FillStyle fillStyle = KisPainter::FillStyleForegroundColor; + QTransform fillTransform = QTransform(); bool globalAlphaLock = false; qreal effectiveZoom = 1.0; bool presetAllowsLod = false; KisSelectionSP selectionOverride; bool hasOverrideSelection = false; }; KisResourcesSnapshot::KisResourcesSnapshot(KisImageSP image, KisNodeSP currentNode, KoCanvasResourceProvider *resourceManager, KisDefaultBoundsBaseSP bounds) : m_d(new Private()) { m_d->image = image; if (!bounds) { bounds = new KisDefaultBounds(m_d->image); } m_d->bounds = bounds; m_d->currentFgColor = resourceManager->resource(KoCanvasResourceProvider::ForegroundColor).value(); m_d->currentBgColor = resourceManager->resource(KoCanvasResourceProvider::BackgroundColor).value(); m_d->currentPattern = resourceManager->resource(KisCanvasResourceProvider::CurrentPattern).value(); m_d->currentGradient = resourceManager->resource(KisCanvasResourceProvider::CurrentGradient).value(); /** * We should deep-copy the preset, so that long-running actions * will have correct brush parameters. Theoretically this cloning * can be expensive, but according to measurements, it takes * something like 0.1 ms for an average preset. */ KisPaintOpPresetSP p = resourceManager->resource(KisCanvasResourceProvider::CurrentPaintOpPreset).value(); if (p) { m_d->currentPaintOpPreset = resourceManager->resource(KisCanvasResourceProvider::CurrentPaintOpPreset).value()->cloneWithResourcesSnapshot(); } #ifdef HAVE_THREADED_TEXT_RENDERING_WORKAROUND KisPaintOpRegistry::instance()->preinitializePaintOpIfNeeded(m_d->currentPaintOpPreset); #endif /* HAVE_THREADED_TEXT_RENDERING_WORKAROUND */ m_d->currentExposure = resourceManager->resource(KisCanvasResourceProvider::HdrExposure).toDouble(); m_d->currentGenerator = resourceManager->resource(KisCanvasResourceProvider::CurrentGeneratorConfiguration).value(); if (m_d->currentGenerator) { m_d->currentGenerator = m_d->currentGenerator->cloneWithResourcesSnapshot(); } QPointF relativeAxesCenter(0.5, 0.5); if (m_d->image) { relativeAxesCenter = m_d->image->mirrorAxesCenter(); } m_d->axesCenter = KisAlgebra2D::relativeToAbsolute(relativeAxesCenter, m_d->bounds->bounds()); m_d->mirrorMaskHorizontal = resourceManager->resource(KisCanvasResourceProvider::MirrorHorizontal).toBool(); m_d->mirrorMaskVertical = resourceManager->resource(KisCanvasResourceProvider::MirrorVertical).toBool(); qreal normOpacity = resourceManager->resource(KisCanvasResourceProvider::Opacity).toDouble(); m_d->opacity = quint8(normOpacity * OPACITY_OPAQUE_U8); m_d->compositeOpId = resourceManager->resource(KisCanvasResourceProvider::CurrentEffectiveCompositeOp).toString(); setCurrentNode(currentNode); /** * Fill and Stroke styles are not a part of the resource manager * so the tools should set them manually * TODO: port stroke and fill styles to be a part * of the resource manager */ m_d->strokeStyle = KisPainter::StrokeStyleBrush; m_d->fillStyle = KisPainter::FillStyleNone; m_d->globalAlphaLock = resourceManager->resource(KisCanvasResourceProvider::GlobalAlphaLock).toBool(); m_d->effectiveZoom = resourceManager->resource(KisCanvasResourceProvider::EffectiveZoom).toDouble(); m_d->presetAllowsLod = resourceManager->resource(KisCanvasResourceProvider::EffectiveLodAvailablility).toBool(); } KisResourcesSnapshot::KisResourcesSnapshot(KisImageSP image, KisNodeSP currentNode, KisDefaultBoundsBaseSP bounds) : m_d(new Private()) { m_d->image = image; if (!bounds) { bounds = new KisDefaultBounds(m_d->image); } m_d->bounds = bounds; #ifdef HAVE_THREADED_TEXT_RENDERING_WORKAROUND KisPaintOpRegistry::instance()->preinitializePaintOpIfNeeded(m_d->currentPaintOpPreset); #endif /* HAVE_THREADED_TEXT_RENDERING_WORKAROUND */ QPointF relativeAxesCenter(0.5, 0.5); if (m_d->image) { relativeAxesCenter = m_d->image->mirrorAxesCenter(); } m_d->axesCenter = KisAlgebra2D::relativeToAbsolute(relativeAxesCenter, m_d->bounds->bounds()); m_d->opacity = OPACITY_OPAQUE_U8; setCurrentNode(currentNode); /** * Fill and Stroke styles are not a part of the resource manager * so the tools should set them manually * TODO: port stroke and fill styles to be a part * of the resource manager */ m_d->strokeStyle = KisPainter::StrokeStyleBrush; m_d->fillStyle = KisPainter::FillStyleNone; } KisResourcesSnapshot::~KisResourcesSnapshot() { delete m_d; } void KisResourcesSnapshot::setupPainter(KisPainter* painter) { painter->setPaintColor(m_d->currentFgColor); painter->setBackgroundColor(m_d->currentBgColor); painter->setGenerator(m_d->currentGenerator); painter->setPattern(m_d->currentPattern); painter->setGradient(m_d->currentGradient); QBitArray lockflags = channelLockFlags(); if (lockflags.size() > 0) { painter->setChannelFlags(lockflags); } painter->setOpacity(m_d->opacity); painter->setCompositeOp(m_d->compositeOp); painter->setMirrorInformation(m_d->axesCenter, m_d->mirrorMaskHorizontal, m_d->mirrorMaskVertical); painter->setStrokeStyle(m_d->strokeStyle); painter->setFillStyle(m_d->fillStyle); + painter->setPatternTransform(m_d->fillTransform); + /** * The paintOp should be initialized the last, because it may * ask the painter for some options while initialization */ painter->setPaintOpPreset(m_d->currentPaintOpPreset, m_d->currentNode, m_d->image); } void KisResourcesSnapshot::setupMaskingBrushPainter(KisPainter *painter) { KIS_SAFE_ASSERT_RECOVER_RETURN(painter->device()); KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->currentPaintOpPreset->hasMaskingPreset()); painter->setPaintColor(KoColor(Qt::white, painter->device()->colorSpace())); painter->setBackgroundColor(KoColor(Qt::black, painter->device()->colorSpace())); painter->setOpacity(OPACITY_OPAQUE_U8); painter->setChannelFlags(QBitArray()); // masked brush always paints in indirect mode painter->setCompositeOp(COMPOSITE_ALPHA_DARKEN); painter->setMirrorInformation(m_d->axesCenter, m_d->mirrorMaskHorizontal, m_d->mirrorMaskVertical); painter->setStrokeStyle(m_d->strokeStyle); /** * The paintOp should be initialized the last, because it may * ask the painter for some options while initialization */ painter->setPaintOpPreset(m_d->currentPaintOpPreset->createMaskingPreset(), m_d->currentNode, m_d->image); } KisPostExecutionUndoAdapter* KisResourcesSnapshot::postExecutionUndoAdapter() const { return m_d->image ? m_d->image->postExecutionUndoAdapter() : 0; } void KisResourcesSnapshot::setCurrentNode(KisNodeSP node) { m_d->currentNode = node; KisPaintDeviceSP device; if(m_d->currentNode && (device = m_d->currentNode->paintDevice())) { m_d->compositeOp = device->colorSpace()->compositeOp(m_d->compositeOpId); if(!m_d->compositeOp) { m_d->compositeOp = device->colorSpace()->compositeOp(COMPOSITE_OVER); } } } void KisResourcesSnapshot::setStrokeStyle(KisPainter::StrokeStyle strokeStyle) { m_d->strokeStyle = strokeStyle; } void KisResourcesSnapshot::setFillStyle(KisPainter::FillStyle fillStyle) { m_d->fillStyle = fillStyle; } +void KisResourcesSnapshot::setFillTransform(QTransform transform) +{ + m_d->fillTransform = transform; +} + KisNodeSP KisResourcesSnapshot::currentNode() const { return m_d->currentNode; } KisImageSP KisResourcesSnapshot::image() const { return m_d->image; } bool KisResourcesSnapshot::needsIndirectPainting() const { return !m_d->currentPaintOpPreset->settings()->paintIncremental(); } QString KisResourcesSnapshot::indirectPaintingCompositeOp() const { return m_d->currentPaintOpPreset ? m_d->currentPaintOpPreset->settings()->indirectPaintingCompositeOp() : COMPOSITE_ALPHA_DARKEN; } bool KisResourcesSnapshot::needsMaskingBrushRendering() const { return m_d->currentPaintOpPreset && m_d->currentPaintOpPreset->hasMaskingPreset(); } KisSelectionSP KisResourcesSnapshot::activeSelection() const { /** * It is possible to have/use the snapshot without the image. Such * usecase is present for example in the scratchpad. */ if (m_d->hasOverrideSelection) { return m_d->selectionOverride; } KisSelectionSP selection = m_d->image ? m_d->image->globalSelection() : 0; KisLayerSP layer = qobject_cast(m_d->currentNode.data()); KisSelectionMaskSP mask; if((layer = qobject_cast(m_d->currentNode.data()))) { selection = layer->selection(); } else if ((mask = dynamic_cast(m_d->currentNode.data())) && mask->selection() == selection) { selection = 0; } return selection; } bool KisResourcesSnapshot::needsAirbrushing() const { return ( m_d->currentPaintOpPreset && m_d->currentPaintOpPreset->settings() && m_d->currentPaintOpPreset->settings()->isAirbrushing()); } qreal KisResourcesSnapshot::airbrushingInterval() const { return ( m_d->currentPaintOpPreset && m_d->currentPaintOpPreset->settings() && m_d->currentPaintOpPreset->settings()->airbrushInterval()); } bool KisResourcesSnapshot::needsSpacingUpdates() const { return ( m_d->currentPaintOpPreset && m_d->currentPaintOpPreset->settings() && m_d->currentPaintOpPreset->settings()->useSpacingUpdates()); } void KisResourcesSnapshot::setOpacity(qreal opacity) { m_d->opacity = opacity * OPACITY_OPAQUE_U8; } quint8 KisResourcesSnapshot::opacity() const { return m_d->opacity; } const KoCompositeOp* KisResourcesSnapshot::compositeOp() const { return m_d->compositeOp; } QString KisResourcesSnapshot::compositeOpId() const { return m_d->compositeOpId; } KoPatternSP KisResourcesSnapshot::currentPattern() const { return m_d->currentPattern; } KoColor KisResourcesSnapshot::currentFgColor() const { return m_d->currentFgColor; } KoColor KisResourcesSnapshot::currentBgColor() const { return m_d->currentBgColor; } KisPaintOpPresetSP KisResourcesSnapshot::currentPaintOpPreset() const { return m_d->currentPaintOpPreset; } +QTransform KisResourcesSnapshot::fillTransform() const +{ + return m_d->fillTransform; +} + QBitArray KisResourcesSnapshot::channelLockFlags() const { QBitArray channelFlags; KisPaintLayer *paintLayer; if ((paintLayer = dynamic_cast(m_d->currentNode.data()))) { channelFlags = paintLayer->channelLockFlags(); if (m_d->globalAlphaLock) { if (channelFlags.isEmpty()) { channelFlags = paintLayer->colorSpace()->channelFlags(true, true); } channelFlags &= paintLayer->colorSpace()->channelFlags(true, false); } } return channelFlags; } qreal KisResourcesSnapshot::effectiveZoom() const { return m_d->effectiveZoom; } bool KisResourcesSnapshot::presetAllowsLod() const { return m_d->presetAllowsLod; } bool KisResourcesSnapshot::presetNeedsAsynchronousUpdates() const { return m_d->currentPaintOpPreset && m_d->currentPaintOpPreset->settings()->needsAsynchronousUpdates(); } void KisResourcesSnapshot::setFGColorOverride(const KoColor &color) { m_d->currentFgColor = color; } void KisResourcesSnapshot::setBGColorOverride(const KoColor &color) { m_d->currentBgColor = color; } void KisResourcesSnapshot::setSelectionOverride(KisSelectionSP selection) { m_d->selectionOverride = selection; m_d->hasOverrideSelection = true; // needed if selection passed is null to ignore selection } void KisResourcesSnapshot::setBrush(const KisPaintOpPresetSP &brush) { m_d->currentPaintOpPreset = brush->cloneWithResourcesSnapshot(); #ifdef HAVE_THREADED_TEXT_RENDERING_WORKAROUND KisPaintOpRegistry::instance()->preinitializePaintOpIfNeeded(m_d->currentPaintOpPreset); #endif /* HAVE_THREADED_TEXT_RENDERING_WORKAROUND */ } diff --git a/libs/ui/tool/kis_resources_snapshot.h b/libs/ui/tool/kis_resources_snapshot.h index 4e8db07509..8dbf87fe6a 100644 --- a/libs/ui/tool/kis_resources_snapshot.h +++ b/libs/ui/tool/kis_resources_snapshot.h @@ -1,106 +1,109 @@ /* * 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_RESOURCES_SNAPSHOT_H #define __KIS_RESOURCES_SNAPSHOT_H #include "kis_shared.h" #include "kis_shared_ptr.h" #include "kis_types.h" #include "kritaui_export.h" #include "kis_painter.h" #include "kis_default_bounds.h" class KoCanvasResourceProvider; class KoCompositeOp; class KisPainter; class KisPostExecutionUndoAdapter; class KoPattern; /** * @brief The KisResourcesSnapshot class takes a snapshot of the various resources * like colors and settings used at the begin of a stroke so subsequent * changes don't impact the running stroke. The main reason for the snapshot is that the * user can *change* the options while the stroke is being executed in the background. */ class KRITAUI_EXPORT KisResourcesSnapshot : public KisShared { public: KisResourcesSnapshot(KisImageSP image, KisNodeSP currentNode, KoCanvasResourceProvider *resourceManager, KisDefaultBoundsBaseSP bounds = 0); KisResourcesSnapshot(KisImageSP image, KisNodeSP currentNode, KisDefaultBoundsBaseSP bounds = 0); ~KisResourcesSnapshot(); void setupPainter(KisPainter *painter); void setupMaskingBrushPainter(KisPainter *painter); KisPostExecutionUndoAdapter* postExecutionUndoAdapter() const; void setCurrentNode(KisNodeSP node); void setStrokeStyle(KisPainter::StrokeStyle strokeStyle); void setFillStyle(KisPainter::FillStyle fillStyle); + void setFillTransform(QTransform transform); KisNodeSP currentNode() const; KisImageSP image() const; bool needsIndirectPainting() const; QString indirectPaintingCompositeOp() const; bool needsMaskingBrushRendering() const; /** * \return currently active selection. Note that it will return * null if current node *is* the current selection. This * is done to avoid recursive selection application when * painting on selectgion masks. */ KisSelectionSP activeSelection() const; bool needsAirbrushing() const; qreal airbrushingInterval() const; bool needsSpacingUpdates() const; void setOpacity(qreal opacity); quint8 opacity() const; const KoCompositeOp* compositeOp() const; QString compositeOpId() const; KoPatternSP currentPattern() const; KoColor currentFgColor() const; KoColor currentBgColor() const; KisPaintOpPresetSP currentPaintOpPreset() const; + QTransform fillTransform() const; + /// @return the channel lock flags of the current node with the global override applied QBitArray channelLockFlags() const; qreal effectiveZoom() const; bool presetAllowsLod() const; bool presetNeedsAsynchronousUpdates() const; void setFGColorOverride(const KoColor &color); void setBGColorOverride(const KoColor &color); void setSelectionOverride(KisSelectionSP selection); void setBrush(const KisPaintOpPresetSP &brush); private: struct Private; Private * const m_d; }; typedef KisSharedPtr KisResourcesSnapshotSP; #endif /* __KIS_RESOURCES_SNAPSHOT_H */ diff --git a/libs/ui/tool/kis_selection_tool_helper.cpp b/libs/ui/tool/kis_selection_tool_helper.cpp index e562325c66..dea4878c7a 100644 --- a/libs/ui/tool/kis_selection_tool_helper.cpp +++ b/libs/ui/tool/kis_selection_tool_helper.cpp @@ -1,408 +1,414 @@ /* * 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_tool_helper.h" #include #include #include #include #include "kis_pixel_selection.h" #include "kis_shape_selection.h" #include "kis_image.h" #include "canvas/kis_canvas2.h" #include "KisViewManager.h" #include "kis_selection_manager.h" #include "kis_transaction.h" #include "commands/kis_selection_commands.h" #include "kis_shape_controller.h" #include #include "kis_processing_applicator.h" #include "commands_new/kis_transaction_based_command.h" #include "kis_gui_context_command.h" #include "kis_command_utils.h" #include "commands/kis_deselect_global_selection_command.h" #include "kis_algebra_2d.h" #include "kis_config.h" #include "kis_action_manager.h" #include "kis_action.h" #include KisSelectionToolHelper::KisSelectionToolHelper(KisCanvas2* canvas, const KUndo2MagicString& name) : m_canvas(canvas) , m_name(name) { m_image = m_canvas->viewManager()->image(); } KisSelectionToolHelper::~KisSelectionToolHelper() { } struct LazyInitGlobalSelection : public KisTransactionBasedCommand { LazyInitGlobalSelection(KisView *view) : m_view(view) {} KisView *m_view; KUndo2Command* paint() override { return !m_view->selection() ? new KisSetEmptyGlobalSelectionCommand(m_view->image()) : 0; } }; void KisSelectionToolHelper::selectPixelSelection(KisPixelSelectionSP selection, SelectionAction action) { KisView* view = m_canvas->imageView(); KisProcessingApplicator applicator(view->image(), 0 /* we need no automatic updates */, KisProcessingApplicator::SUPPORTS_WRAPAROUND_MODE, KisImageSignalVector() << ModifiedSignal, m_name); selectPixelSelection(applicator, selection, action); applicator.end(); } void KisSelectionToolHelper::selectPixelSelection(KisProcessingApplicator& applicator, KisPixelSelectionSP selection, SelectionAction action) { KisView* view = m_canvas->imageView(); QPointer canvas = m_canvas; applicator.applyCommand(new LazyInitGlobalSelection(view), KisStrokeJobData::SEQUENTIAL); struct ApplyToPixelSelection : public KisTransactionBasedCommand { ApplyToPixelSelection(KisView *view, KisPixelSelectionSP selection, SelectionAction action, QPointer canvas) : m_view(view), m_selection(selection), m_action(action), m_canvas(canvas) {} KisView *m_view; KisPixelSelectionSP m_selection; SelectionAction m_action; QPointer m_canvas; KUndo2Command* paint() override { KUndo2Command *savedCommand = 0; if (!m_selection->selectedExactRect().isEmpty()) { KisSelectionSP selection = m_view->selection(); KIS_SAFE_ASSERT_RECOVER(selection) { return 0; } KisPixelSelectionSP pixelSelection = selection->pixelSelection(); KIS_SAFE_ASSERT_RECOVER(pixelSelection) { return 0; } bool hasSelection = !pixelSelection->isEmpty(); KisSelectionTransaction transaction(pixelSelection); if (!hasSelection && m_action == SELECTION_SYMMETRICDIFFERENCE) { m_action = SELECTION_REPLACE; } if (!hasSelection && m_action == SELECTION_SUBTRACT) { pixelSelection->invert(); } pixelSelection->applySelection(m_selection, m_action); QRect dirtyRect = m_view->image()->bounds(); if (hasSelection && m_action != SELECTION_REPLACE && m_action != SELECTION_INTERSECT && m_action != SELECTION_SYMMETRICDIFFERENCE) { dirtyRect = m_selection->selectedRect(); } m_view->selection()->updateProjection(dirtyRect); savedCommand = transaction.endAndTake(); pixelSelection->setDirty(dirtyRect); // release resources: transaction will care about // undo/redo, we don't need the selection anymore m_selection.clear(); } if (m_view->selection()->selectedExactRect().isEmpty()) { KUndo2Command *deselectCommand = new KisDeselectActiveSelectionCommand(m_view->selection(), m_view->image()); if (savedCommand) { KisCommandUtils::CompositeCommand *cmd = new KisCommandUtils::CompositeCommand(); cmd->addCommand(savedCommand); cmd->addCommand(deselectCommand); savedCommand = cmd; } else { savedCommand = deselectCommand; } } return savedCommand; } }; applicator.applyCommand(new ApplyToPixelSelection(view, selection, action, canvas), KisStrokeJobData::SEQUENTIAL); } void KisSelectionToolHelper::addSelectionShape(KoShape* shape, SelectionAction action) { QList shapes; shapes.append(shape); addSelectionShapes(shapes, action); } #include "krita_utils.h" void KisSelectionToolHelper::addSelectionShapes(QList< KoShape* > shapes, SelectionAction action) { KisView *view = m_canvas->imageView(); if (view->image()->wrapAroundModePermitted()) { view->showFloatingMessage( i18n("Shape selection does not fully " "support wraparound mode. Please " "use pixel selection instead"), KisIconUtils::loadIcon("selection-info")); } KisProcessingApplicator applicator(view->image(), 0 /* we need no automatic updates */, KisProcessingApplicator::NONE, KisImageSignalVector() << ModifiedSignal, m_name); applicator.applyCommand(new LazyInitGlobalSelection(view)); struct ClearPixelSelection : public KisTransactionBasedCommand { ClearPixelSelection(KisView *view) : m_view(view) {} KisView *m_view; KUndo2Command* paint() override { KisPixelSelectionSP pixelSelection = m_view->selection()->pixelSelection(); KIS_ASSERT_RECOVER(pixelSelection) { return 0; } KisSelectionTransaction transaction(pixelSelection); pixelSelection->clear(); return transaction.endAndTake(); } }; if (action == SELECTION_REPLACE || action == SELECTION_DEFAULT) { applicator.applyCommand(new ClearPixelSelection(view)); } struct AddSelectionShape : public KisTransactionBasedCommand { - AddSelectionShape(KisView *view, KoShape* shape, SelectionAction action) + AddSelectionShape(KisView *view, QList shapes, SelectionAction action) : m_view(view), - m_shape(shape), + m_shapes(shapes), m_action(action) {} KisView *m_view; - KoShape* m_shape; + QList m_shapes; SelectionAction m_action; KUndo2Command* paint() override { KUndo2Command *resultCommand = 0; KisSelectionSP selection = m_view->selection(); if (selection) { KisShapeSelection * shapeSelection = static_cast(selection->shapeSelection()); if (shapeSelection) { QList existingShapes = shapeSelection->shapes(); - if (existingShapes.size() == 1) { - KoShape *currentShape = existingShapes.first(); - QPainterPath path1 = currentShape->absoluteTransformation().map(currentShape->outline()); - QPainterPath path2 = m_shape->absoluteTransformation().map(m_shape->outline()); + QPainterPath path1; + path1.setFillRule(Qt::WindingFill); + Q_FOREACH(KoShape *shape, existingShapes) { + path1 += shape->absoluteTransformation().map(shape->outline()); + } - const QTransform booleanWorkaroundTransform = - KritaUtils::pathShapeBooleanSpaceWorkaround(m_view->image()); + QPainterPath path2; + path2.setFillRule(Qt::WindingFill); + Q_FOREACH(KoShape *shape, m_shapes) { + path2 += shape->absoluteTransformation().map(shape->outline()); + } - path1 = booleanWorkaroundTransform.map(path1); - path2 = booleanWorkaroundTransform.map(path2); + const QTransform booleanWorkaroundTransform = + KritaUtils::pathShapeBooleanSpaceWorkaround(m_view->image()); - QPainterPath path = path2; + path1 = booleanWorkaroundTransform.map(path1); + path2 = booleanWorkaroundTransform.map(path2); - switch (m_action) { - case SELECTION_DEFAULT: - case SELECTION_REPLACE: - path = path2; - break; + QPainterPath path = path2; - case SELECTION_INTERSECT: - path = path1 & path2; - break; + switch (m_action) { + case SELECTION_DEFAULT: + case SELECTION_REPLACE: + path = path2; + break; - case SELECTION_ADD: - path = path1 | path2; - break; + case SELECTION_INTERSECT: + path = path1 & path2; + break; - case SELECTION_SUBTRACT: - path = path1 - path2; - break; - case SELECTION_SYMMETRICDIFFERENCE: - path = (path1 | path2) - (path1 & path2); - break; - } + case SELECTION_ADD: + path = path1 | path2; + break; - path = booleanWorkaroundTransform.inverted().map(path); + case SELECTION_SUBTRACT: + path = path1 - path2; + break; + case SELECTION_SYMMETRICDIFFERENCE: + path = (path1 | path2) - (path1 & path2); + break; + } - KoShape *newShape = KoPathShape::createShapeFromPainterPath(path); - newShape->setUserData(new KisShapeSelectionMarker); + path = booleanWorkaroundTransform.inverted().map(path); - KUndo2Command *parentCommand = new KUndo2Command(); + KoShape *newShape = KoPathShape::createShapeFromPainterPath(path); + newShape->setUserData(new KisShapeSelectionMarker); - m_view->canvasBase()->shapeController()->removeShape(currentShape, parentCommand); - m_view->canvasBase()->shapeController()->addShape(newShape, 0, parentCommand); + KUndo2Command *parentCommand = new KUndo2Command(); - if (path.isEmpty()) { - KisCommandUtils::CompositeCommand *cmd = new KisCommandUtils::CompositeCommand(); - cmd->addCommand(parentCommand); - cmd->addCommand(new KisDeselectActiveSelectionCommand(m_view->selection(), m_view->image())); - parentCommand = cmd; - } + m_view->canvasBase()->shapeController()->removeShapes(existingShapes, parentCommand); + m_view->canvasBase()->shapeController()->addShape(newShape, 0, parentCommand); - resultCommand = parentCommand; + if (path.isEmpty()) { + KisCommandUtils::CompositeCommand *cmd = new KisCommandUtils::CompositeCommand(); + cmd->addCommand(parentCommand); + cmd->addCommand(new KisDeselectActiveSelectionCommand(m_view->selection(), m_view->image())); + parentCommand = cmd; } + + resultCommand = parentCommand; } } if (!resultCommand) { /** - * Mark a shape that it belongs to a shape selection + * Mark the shapes that they belong to a shape selection */ - if(!m_shape->userData()) { - m_shape->setUserData(new KisShapeSelectionMarker); + Q_FOREACH(KoShape *shape, m_shapes) { + if(!shape->userData()) { + shape->setUserData(new KisShapeSelectionMarker); + } } - resultCommand = m_view->canvasBase()->shapeController()->addShape(m_shape, 0); + resultCommand = m_view->canvasBase()->shapeController()->addShapesDirect(m_shapes, 0); } return resultCommand; } }; - Q_FOREACH (KoShape* shape, shapes) { - applicator.applyCommand( - new KisGuiContextCommand(new AddSelectionShape(view, shape, action), view)); - } + applicator.applyCommand( + new KisGuiContextCommand(new AddSelectionShape(view, shapes, action), view)); applicator.end(); } bool KisSelectionToolHelper::canShortcutToDeselect(const QRect &rect, SelectionAction action) { return rect.isEmpty() && (action == SELECTION_INTERSECT || action == SELECTION_REPLACE); } bool KisSelectionToolHelper::canShortcutToNoop(const QRect &rect, SelectionAction action) { return rect.isEmpty() && action == SELECTION_ADD; } bool KisSelectionToolHelper::tryDeselectCurrentSelection(const QRectF selectionViewRect, SelectionAction action) { bool result = false; if (KisAlgebra2D::maxDimension(selectionViewRect) < KisConfig(true).selectionViewSizeMinimum() && (action == SELECTION_INTERSECT || action == SELECTION_SYMMETRICDIFFERENCE || action == SELECTION_REPLACE)) { // Queueing this action to ensure we avoid a race condition when unlocking the node system QTimer::singleShot(0, m_canvas->viewManager()->selectionManager(), SLOT(deselect())); result = true; } return result; } QMenu* KisSelectionToolHelper::getSelectionContextMenu(KisCanvas2* canvas) { QMenu *m_contextMenu = new QMenu(); KActionCollection *actionCollection = canvas->viewManager()->actionCollection(); m_contextMenu->addSection(i18n("Selection Actions")); m_contextMenu->addSeparator(); m_contextMenu->addAction(actionCollection->action("deselect")); m_contextMenu->addAction(actionCollection->action("invert")); m_contextMenu->addAction(actionCollection->action("select_all")); m_contextMenu->addSeparator(); m_contextMenu->addAction(actionCollection->action("cut_selection_to_new_layer")); m_contextMenu->addAction(actionCollection->action("copy_selection_to_new_layer")); m_contextMenu->addSeparator(); KisSelectionSP selection = canvas->viewManager()->selection(); if (selection && canvas->viewManager()->selectionEditable()) { m_contextMenu->addAction(actionCollection->action("edit_selection")); if (!selection->hasShapeSelection()) { m_contextMenu->addAction(actionCollection->action("convert_to_vector_selection")); } else { m_contextMenu->addAction(actionCollection->action("convert_to_raster_selection")); } QMenu *transformMenu = m_contextMenu->addMenu(i18n("Transform")); transformMenu->addAction(actionCollection->action("KisToolTransform")); transformMenu->addAction(actionCollection->action("selectionscale")); transformMenu->addAction(actionCollection->action("growselection")); transformMenu->addAction(actionCollection->action("shrinkselection")); transformMenu->addAction(actionCollection->action("borderselection")); transformMenu->addAction(actionCollection->action("smoothselection")); transformMenu->addAction(actionCollection->action("featherselection")); transformMenu->addAction(actionCollection->action("stroke_selection")); m_contextMenu->addSeparator(); } m_contextMenu->addAction(actionCollection->action("resizeimagetoselection")); m_contextMenu->addSeparator(); m_contextMenu->addAction(actionCollection->action("toggle_display_selection")); m_contextMenu->addAction(actionCollection->action("show-global-selection-mask")); return m_contextMenu; } SelectionMode KisSelectionToolHelper::tryOverrideSelectionMode(KisSelectionSP activeSelection, SelectionMode currentMode, SelectionAction currentAction) const { if (currentAction != SELECTION_DEFAULT && currentAction != SELECTION_REPLACE) { if (activeSelection) { currentMode = activeSelection->hasShapeSelection() ? SHAPE_PROTECTION : PIXEL_SELECTION; } } return currentMode; } diff --git a/libs/ui/tool/kis_tool_shape.cc b/libs/ui/tool/kis_tool_shape.cc index 5e071c196e..39a8c6c960 100644 --- a/libs/ui/tool/kis_tool_shape.cc +++ b/libs/ui/tool/kis_tool_shape.cc @@ -1,253 +1,295 @@ /* * 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_tool_shape.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_figure_painting_tool_helper.h" #include #include #include #include #include "kis_selection_mask.h" #include "kis_shape_selection.h" #include "kis_processing_applicator.h" KisToolShape::KisToolShape(KoCanvasBase * canvas, const QCursor & cursor) : KisToolPaint(canvas, cursor) { m_shapeOptionsWidget = 0; } KisToolShape::~KisToolShape() { // in case the widget hasn't been shown if (m_shapeOptionsWidget && !m_shapeOptionsWidget->parent()) { delete m_shapeOptionsWidget; } } void KisToolShape::activate(ToolActivation toolActivation, const QSet &shapes) { KisToolPaint::activate(toolActivation, shapes); m_configGroup = KSharedConfig::openConfig()->group(toolId()); } int KisToolShape::flags() const { return KisTool::FLAG_USES_CUSTOM_COMPOSITEOP|KisTool::FLAG_USES_CUSTOM_PRESET |KisTool::FLAG_USES_CUSTOM_SIZE; } QWidget * KisToolShape::createOptionWidget() { m_shapeOptionsWidget = new WdgGeometryOptions(0); m_shapeOptionsWidget->cmbOutline->setCurrentIndex(KisPainter::StrokeStyleBrush); + m_shapeOptionsWidget->sldRotation->setSuffix(QChar(Qt::Key_degree)); + m_shapeOptionsWidget->sldRotation->setRange(0.0, 360.0, 2); + m_shapeOptionsWidget->sldRotation->setSingleStep(1.0); + + m_shapeOptionsWidget->sldScale->setSuffix("%"); + m_shapeOptionsWidget->sldScale->setRange(0.0, 500.0, 2); + m_shapeOptionsWidget->sldScale->setSingleStep(1.0); + //connect two combo box event. Inherited classes can call the slots to make appropriate changes connect(m_shapeOptionsWidget->cmbOutline, SIGNAL(currentIndexChanged(int)), this, SLOT(outlineSettingChanged(int))); connect(m_shapeOptionsWidget->cmbFill, SIGNAL(currentIndexChanged(int)), this, SLOT(fillSettingChanged(int))); + connect(m_shapeOptionsWidget->sldRotation, SIGNAL(valueChanged(qreal)), this, SLOT(patternRotationSettingChanged(qreal))); + connect(m_shapeOptionsWidget->sldScale, SIGNAL(valueChanged(qreal)), this, SLOT(patternScaleSettingChanged(qreal))); m_shapeOptionsWidget->cmbOutline->setCurrentIndex(m_configGroup.readEntry("outlineType", 0)); m_shapeOptionsWidget->cmbFill->setCurrentIndex(m_configGroup.readEntry("fillType", 0)); + m_shapeOptionsWidget->sldScale->setValue(m_configGroup.readEntry("patternTransformScale", 100)); + m_shapeOptionsWidget->sldRotation->setValue(m_configGroup.readEntry("patternTransformRotation", 0)); //if both settings are empty, force the outline to brush so the tool will work when first activated if ( m_shapeOptionsWidget->cmbFill->currentIndex() == 0 && m_shapeOptionsWidget->cmbOutline->currentIndex() == 0) { m_shapeOptionsWidget->cmbOutline->setCurrentIndex(1); // brush } + bool enablePatternTransform = (m_shapeOptionsWidget->cmbFill->currentIndex() == int(KisToolShapeUtils::FillStylePattern)); + m_shapeOptionsWidget->gbPatternTransform->setEnabled(enablePatternTransform); + return m_shapeOptionsWidget; } void KisToolShape::outlineSettingChanged(int value) { m_configGroup.writeEntry("outlineType", value); } void KisToolShape::fillSettingChanged(int value) { m_configGroup.writeEntry("fillType", value); + bool enable = (value == int(KisToolShapeUtils::FillStylePattern)); + m_shapeOptionsWidget->gbPatternTransform->setEnabled(enable); +} + +void KisToolShape::patternRotationSettingChanged(qreal value) +{ + m_configGroup.writeEntry("patternTransformRotation", value); +} + +void KisToolShape::patternScaleSettingChanged(qreal value) +{ + m_configGroup.writeEntry("patternTransformScale", value); } KisToolShapeUtils::FillStyle KisToolShape::fillStyle() { if (m_shapeOptionsWidget) { return static_cast(m_shapeOptionsWidget->cmbFill->currentIndex()); } else { return KisToolShapeUtils::FillStyleNone; } } KisToolShapeUtils::StrokeStyle KisToolShape::strokeStyle() { if (m_shapeOptionsWidget) { return static_cast(m_shapeOptionsWidget->cmbOutline->currentIndex()); } else { return KisToolShapeUtils::StrokeStyleNone; } } +QTransform KisToolShape::fillTransform() +{ + QTransform transform; + + if (m_shapeOptionsWidget) { + transform.rotate(m_shapeOptionsWidget->sldRotation->value()); + qreal scale = m_shapeOptionsWidget->sldScale->value()*0.01; + transform.scale(scale, scale); + } + + return transform; +} + qreal KisToolShape::currentStrokeWidth() const { const qreal sizeInPx = canvas()->resourceManager()->resource(KisCanvasResourceProvider::Size).toReal(); return canvas()->unit().fromUserValue(sizeInPx); } KisToolShape::ShapeAddInfo KisToolShape::shouldAddShape(KisNodeSP currentNode) const { ShapeAddInfo info; if (currentNode->inherits("KisShapeLayer")) { info.shouldAddShape = true; } else if (KisSelectionMask *mask = dynamic_cast(currentNode.data())) { if (mask->selection()->hasShapeSelection()) { info.shouldAddShape = true; info.shouldAddSelectionShape = true; } } return info; } void KisToolShape::ShapeAddInfo::markAsSelectionShapeIfNeeded(KoShape *shape) const { if (this->shouldAddSelectionShape) { shape->setUserData(new KisShapeSelectionMarker()); } } void KisToolShape::addShape(KoShape* shape) { using namespace KisToolShapeUtils; KoImageCollection* imageCollection = canvas()->shapeController()->resourceManager()->imageCollection(); switch(fillStyle()) { case FillStyleForegroundColor: shape->setBackground(QSharedPointer(new KoColorBackground(currentFgColor().toQColor()))); break; case FillStyleBackgroundColor: shape->setBackground(QSharedPointer(new KoColorBackground(currentBgColor().toQColor()))); break; case FillStylePattern: if (imageCollection) { QSharedPointer fill(new KoPatternBackground(imageCollection)); if (currentPattern()) { fill->setPattern(currentPattern()->pattern()); + fill->setTransform(fillTransform()); shape->setBackground(fill); } } else { shape->setBackground(QSharedPointer(0)); } break; case FillStyleNone: shape->setBackground(QSharedPointer(0)); break; } switch (strokeStyle()) { case KisToolShapeUtils::StrokeStyleNone: shape->setStroke(KoShapeStrokeModelSP()); break; case KisToolShapeUtils::StrokeStyleForeground: case KisToolShapeUtils::StrokeStyleBackground: { KoShapeStrokeSP stroke(new KoShapeStroke()); stroke->setLineWidth(currentStrokeWidth()); const QColor color = strokeStyle() == KisToolShapeUtils::StrokeStyleForeground ? canvas()->resourceManager()->foregroundColor().toQColor() : canvas()->resourceManager()->backgroundColor().toQColor(); stroke->setColor(color); shape->setStroke(stroke); break; } } KUndo2Command *parentCommand = new KUndo2Command(); KoSelection *selection = canvas()->selectedShapesProxy()->selection(); const QList oldSelectedShapes = selection->selectedShapes(); // reset selection on the newly added shape :) // TODO: think about moving this into controller->addShape? new KoKeepShapesSelectedCommand(oldSelectedShapes, {shape}, canvas()->selectedShapesProxy(), false, parentCommand); KUndo2Command *cmd = canvas()->shapeController()->addShape(shape, 0, parentCommand); parentCommand->setText(cmd->text()); new KoKeepShapesSelectedCommand(oldSelectedShapes, {shape}, canvas()->selectedShapesProxy(), true, parentCommand); KisProcessingApplicator::runSingleCommandStroke(image(), cmd, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); } void KisToolShape::addPathShape(KoPathShape* pathShape, const KUndo2MagicString& name) { KisNodeSP node = currentNode(); if (!node) { return; } // Compute the outline KisImageSP image = this->image(); QTransform matrix; matrix.scale(image->xRes(), image->yRes()); matrix.translate(pathShape->position().x(), pathShape->position().y()); QPainterPath mapedOutline = matrix.map(pathShape->outline()); if (node->hasEditablePaintDevice()) { KisFigurePaintingToolHelper helper(name, image, node, canvas()->resourceManager(), strokeStyle(), - fillStyle()); + fillStyle(), + fillTransform()); helper.paintPainterPath(mapedOutline); } else if (node->inherits("KisShapeLayer")) { pathShape->normalize(); addShape(pathShape); } } diff --git a/libs/ui/tool/kis_tool_shape.h b/libs/ui/tool/kis_tool_shape.h index 3bae642b14..6d22d93ea5 100644 --- a/libs/ui/tool/kis_tool_shape.h +++ b/libs/ui/tool/kis_tool_shape.h @@ -1,95 +1,98 @@ /* * 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. */ #ifndef KIS_TOOL_SHAPE_H_ #define KIS_TOOL_SHAPE_H_ #include #include #include #include #include "kis_tool_paint.h" #include "KisSelectionToolFactoryBase.h" #include "KisToolShapeUtils.h" #include "ui_wdggeometryoptions.h" class KoCanvasBase; class KoPathShape; class WdgGeometryOptions : public QWidget, public Ui::WdgGeometryOptions { Q_OBJECT public: WdgGeometryOptions(QWidget *parent) : QWidget(parent) { setupUi(this); } }; /** * Base for tools specialized in drawing shapes */ class KRITAUI_EXPORT KisToolShape : public KisToolPaint { Q_OBJECT public: KisToolShape(KoCanvasBase * canvas, const QCursor & cursor); ~KisToolShape() override; int flags() const override; WdgGeometryOptions *m_shapeOptionsWidget; public Q_SLOTS: void activate(ToolActivation toolActivation, const QSet &shapes) override; virtual void outlineSettingChanged(int value); virtual void fillSettingChanged(int value); + virtual void patternRotationSettingChanged(qreal value); + virtual void patternScaleSettingChanged(qreal value); protected: QWidget* createOptionWidget() override; KisToolShapeUtils::FillStyle fillStyle(); KisToolShapeUtils::StrokeStyle strokeStyle(); + QTransform fillTransform(); qreal currentStrokeWidth() const; struct KRITAUI_EXPORT ShapeAddInfo { bool shouldAddShape = false; bool shouldAddSelectionShape = false; void markAsSelectionShapeIfNeeded(KoShape *shape) const; }; ShapeAddInfo shouldAddShape(KisNodeSP currentNode) const; void addShape(KoShape* shape); void addPathShape(KoPathShape* pathShape, const KUndo2MagicString& name); KConfigGroup m_configGroup; }; #endif // KIS_TOOL_SHAPE_H_ diff --git a/libs/ui/tool/strokes/move_stroke_strategy.cpp b/libs/ui/tool/strokes/move_stroke_strategy.cpp index 0f296fe13d..47a41bc30a 100644 --- a/libs/ui/tool/strokes/move_stroke_strategy.cpp +++ b/libs/ui/tool/strokes/move_stroke_strategy.cpp @@ -1,359 +1,360 @@ /* * Copyright (c) 2011 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "move_stroke_strategy.h" #include #include "kis_image_interfaces.h" #include "kis_node.h" #include "commands_new/kis_update_command.h" #include "commands_new/kis_node_move_command2.h" #include "kis_layer_utils.h" #include "krita_utils.h" #include "KisRunnableStrokeJobData.h" #include "KisRunnableStrokeJobUtils.h" #include "KisRunnableStrokeJobsInterface.h" +#include "kis_abstract_projection_plane.h" MoveStrokeStrategy::MoveStrokeStrategy(KisNodeSelectionRecipe nodeSelection, KisUpdatesFacade *updatesFacade, KisStrokeUndoFacade *undoFacade) : KisStrokeStrategyUndoCommandBased(kundo2_i18n("Move"), false, undoFacade), m_requestedNodeSelection(nodeSelection), m_updatesFacade(updatesFacade), m_updatesEnabled(true) { setSupportsWrapAroundMode(true); enableJob(KisSimpleStrokeStrategy::JOB_INIT, true, KisStrokeJobData::BARRIER); } MoveStrokeStrategy::MoveStrokeStrategy(KisNodeList nodes, KisUpdatesFacade *updatesFacade, KisStrokeUndoFacade *undoFacade) : MoveStrokeStrategy(KisNodeSelectionRecipe(nodes), updatesFacade, undoFacade) { } MoveStrokeStrategy::MoveStrokeStrategy(const MoveStrokeStrategy &rhs, int lod) : QObject(), KisStrokeStrategyUndoCommandBased(rhs), m_requestedNodeSelection(rhs.m_requestedNodeSelection, lod), m_nodes(rhs.m_nodes), m_blacklistedNodes(rhs.m_blacklistedNodes), m_updatesFacade(rhs.m_updatesFacade), m_finalOffset(rhs.m_finalOffset), m_dirtyRect(rhs.m_dirtyRect), m_dirtyRects(rhs.m_dirtyRects), m_updatesEnabled(rhs.m_updatesEnabled) { } void MoveStrokeStrategy::saveInitialNodeOffsets(KisNodeSP node) { if (!m_blacklistedNodes.contains(node)) { m_initialNodeOffsets.insert(node, QPoint(node->x(), node->y())); } KisNodeSP child = node->firstChild(); while(child) { saveInitialNodeOffsets(child); child = child->nextSibling(); } } void MoveStrokeStrategy::initStrokeCallback() { /** * Our LodN moght have already prepared the list of nodes for us, * so we should reuse it to avoid different nodes to be moved in * LodN and Lod0 modes. */ if (m_updatesEnabled) { m_nodes = m_requestedNodeSelection.selectNodesToProcess(); if (!m_nodes.isEmpty()) { m_nodes = KisLayerUtils::sortAndFilterMergableInternalNodes(m_nodes, true); } KritaUtils::filterContainer(m_nodes, [this](KisNodeSP node) { // TODO: check isolation return !KisLayerUtils::checkIsCloneOf(node, m_nodes) && node->isEditable(true); }); Q_FOREACH(KisNodeSP subtree, m_nodes) { KisLayerUtils::recursiveApplyNodes( subtree, [this](KisNodeSP node) { if (KisLayerUtils::checkIsCloneOf(node, m_nodes) || !node->isEditable(false)) { m_blacklistedNodes.insert(node); } }); } if (m_sharedNodes) { *m_sharedNodes = m_nodes; } } else { KIS_SAFE_ASSERT_RECOVER_RETURN(m_sharedNodes); m_nodes = *m_sharedNodes; } if (m_nodes.isEmpty()) { emit sigStrokeStartedEmpty(); return; } QVector jobs; KritaUtils::addJobBarrier(jobs, [this]() { Q_FOREACH(KisNodeSP node, m_nodes) { KisLayerUtils::forceAllHiddenOriginalsUpdate(node); } }); KritaUtils::addJobBarrier(jobs, [this]() { Q_FOREACH(KisNodeSP node, m_nodes) { KisLayerUtils::forceAllDelayedNodesUpdate(node); } }); KritaUtils::addJobBarrier(jobs, [this]() { QRect handlesRect; Q_FOREACH(KisNodeSP node, m_nodes) { saveInitialNodeOffsets(node); handlesRect |= KisLayerUtils::recursiveTightNodeVisibleBounds(node); } KisStrokeStrategyUndoCommandBased::initStrokeCallback(); if (m_updatesEnabled) { KisLodTransform t(m_nodes.first()->projection()); handlesRect = t.mapInverted(handlesRect); emit this->sigHandlesRectCalculated(handlesRect); } m_updateTimer.start(); }); runnableJobsInterface()->addRunnableJobs(jobs); } void MoveStrokeStrategy::finishStrokeCallback() { Q_FOREACH (KisNodeSP node, m_nodes) { KUndo2Command *updateCommand = new KisUpdateCommand(node, m_dirtyRects[node], m_updatesFacade, true); addMoveCommands(node, updateCommand); notifyCommandDone(KUndo2CommandSP(updateCommand), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); } if (!m_updatesEnabled) { Q_FOREACH (KisNodeSP node, m_nodes) { m_updatesFacade->refreshGraphAsync(node, m_dirtyRects[node]); } } KisStrokeStrategyUndoCommandBased::finishStrokeCallback(); } void MoveStrokeStrategy::cancelStrokeCallback() { if (!m_nodes.isEmpty()) { m_finalOffset = QPoint(); m_hasPostponedJob = true; tryPostUpdateJob(true); } KisStrokeStrategyUndoCommandBased::cancelStrokeCallback(); } void MoveStrokeStrategy::tryPostUpdateJob(bool forceUpdate) { if (!m_hasPostponedJob) return; if (forceUpdate || (m_updateTimer.elapsed() > m_updateInterval && !m_updatesFacade->hasUpdatesRunning())) { addMutatedJob(new BarrierUpdateData(forceUpdate)); } } void MoveStrokeStrategy::doStrokeCallback(KisStrokeJobData *data) { if (PickLayerData *pickData = dynamic_cast(data)) { KisNodeSelectionRecipe clone = m_requestedNodeSelection; clone.pickPoint = pickData->pos; emit sigLayersPicked(clone.selectNodesToProcess()); return; } Data *d = dynamic_cast(data); if (!m_nodes.isEmpty() && d) { /** * NOTE: we do not care about threading here, because * all our jobs are declared sequential */ m_finalOffset = d->offset; m_hasPostponedJob = true; tryPostUpdateJob(false); } else if (BarrierUpdateData *barrierData = dynamic_cast(data)) { doCanvasUpdate(barrierData->forceUpdate); } else if (KisAsyncronousStrokeUpdateHelper::UpdateData *updateData = dynamic_cast(data)) { tryPostUpdateJob(updateData->forceUpdate); } else { KisStrokeStrategyUndoCommandBased::doStrokeCallback(data); } } #include "kis_selection_mask.h" #include "kis_selection.h" void MoveStrokeStrategy::doCanvasUpdate(bool forceUpdate) { if (!forceUpdate && (m_updateTimer.elapsed() < m_updateInterval || m_updatesFacade->hasUpdatesRunning())) { return; } if (!m_hasPostponedJob) return; Q_FOREACH (KisNodeSP node, m_nodes) { QRect dirtyRect = moveNode(node, m_finalOffset); m_dirtyRects[node] |= dirtyRect; if (m_updatesEnabled) { m_updatesFacade->refreshGraphAsync(node, dirtyRect); } if (KisSelectionMask *mask = dynamic_cast(node.data())) { Q_UNUSED(mask); //mask->selection()->notifySelectionChanged(); } } m_hasPostponedJob = false; m_updateTimer.restart(); } QRect MoveStrokeStrategy::moveNode(KisNodeSP node, QPoint offset) { QRect dirtyRect; if (!m_blacklistedNodes.contains(node)) { - dirtyRect = node->extent(); + dirtyRect = node->projectionPlane()->tightUserVisibleBounds(); QPoint newOffset = m_initialNodeOffsets[node] + offset; /** * Some layers, e.g. clones need an update to change extent(), so * calculate the dirty rect manually */ QPoint currentOffset(node->x(), node->y()); dirtyRect |= dirtyRect.translated(newOffset - currentOffset); node->setX(newOffset.x()); node->setY(newOffset.y()); KisNodeMoveCommand2::tryNotifySelection(node); } KisNodeSP child = node->firstChild(); while(child) { dirtyRect |= moveNode(child, offset); child = child->nextSibling(); } return dirtyRect; } void MoveStrokeStrategy::addMoveCommands(KisNodeSP node, KUndo2Command *parent) { if (!m_blacklistedNodes.contains(node)) { QPoint nodeOffset(node->x(), node->y()); new KisNodeMoveCommand2(node, nodeOffset - m_finalOffset, nodeOffset, parent); } KisNodeSP child = node->firstChild(); while(child) { addMoveCommands(child, parent); child = child->nextSibling(); } } void MoveStrokeStrategy::setUpdatesEnabled(bool value) { m_updatesEnabled = value; } bool checkSupportsLodMoves(KisNodeSP subtree) { return !KisLayerUtils::recursiveFindNode( subtree, [](KisNodeSP node) -> bool { return !node->supportsLodMoves(); }); } KisStrokeStrategy* MoveStrokeStrategy::createLodClone(int levelOfDetail) { KisNodeList nodesToCheck; if (m_requestedNodeSelection.mode == KisNodeSelectionRecipe::SelectedLayer) { nodesToCheck = m_requestedNodeSelection.selectedNodes; } else if (!m_requestedNodeSelection.selectedNodes.isEmpty()){ /** * Since this function is executed in the GUI thread, we cannot properly * pick the layers. Therefore we should use pessimistic approach and * check if there are non-lodn-capable nodes in the entire image. */ nodesToCheck.append(KisLayerUtils::findRoot(m_requestedNodeSelection.selectedNodes.first())); } Q_FOREACH (KisNodeSP node, nodesToCheck) { if (!checkSupportsLodMoves(node)) return 0; } MoveStrokeStrategy *clone = new MoveStrokeStrategy(*this, levelOfDetail); connect(clone, SIGNAL(sigHandlesRectCalculated(QRect)), this, SIGNAL(sigHandlesRectCalculated(QRect))); connect(clone, SIGNAL(sigStrokeStartedEmpty()), this, SIGNAL(sigStrokeStartedEmpty())); connect(clone, SIGNAL(sigLayersPicked(const KisNodeList&)), this, SIGNAL(sigLayersPicked(const KisNodeList&))); this->setUpdatesEnabled(false); m_sharedNodes.reset(new KisNodeList()); clone->m_sharedNodes = m_sharedNodes; return clone; } diff --git a/libs/ui/widgets/kis_scratch_pad.cpp b/libs/ui/widgets/kis_scratch_pad.cpp index 7d4f622733..d6258e008b 100644 --- a/libs/ui/widgets/kis_scratch_pad.cpp +++ b/libs/ui/widgets/kis_scratch_pad.cpp @@ -1,536 +1,644 @@ /* This file is part of the KDE project * Copyright 2010 (C) Boudewijn Rempt * Copyright 2011 (C) Dmitry Kazakov * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_scratch_pad.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_config.h" #include "kis_image.h" #include "kis_undo_stores.h" #include "kis_update_scheduler.h" #include "kis_post_execution_undo_adapter.h" #include "kis_scratch_pad_event_filter.h" #include "kis_painting_information_builder.h" #include "kis_tool_freehand_helper.h" #include "kis_image_patch.h" #include "kis_canvas_widget_base.h" #include "kis_layer_projection_plane.h" #include "kis_node_graph_listener.h" #include "kis_transaction.h" class KisScratchPadNodeListener : public KisNodeGraphListener { public: KisScratchPadNodeListener(KisScratchPad *scratchPad) : m_scratchPad(scratchPad) { } void requestProjectionUpdate(KisNode *node, const QVector &rects, bool resetAnimationCache) override { KisNodeGraphListener::requestProjectionUpdate(node, rects, resetAnimationCache); QMutexLocker locker(&m_lock); Q_FOREACH (const QRect &rc, rects) { m_scratchPad->imageUpdated(rc); } } private: KisScratchPad *m_scratchPad; QMutex m_lock; }; class KisScratchPadDefaultBounds : public KisDefaultBounds { public: KisScratchPadDefaultBounds(KisScratchPad *scratchPad) : m_scratchPad(scratchPad) { } ~KisScratchPadDefaultBounds() override {} QRect bounds() const override { return m_scratchPad->imageBounds(); } void * sourceCookie() const override { return m_scratchPad; } private: Q_DISABLE_COPY(KisScratchPadDefaultBounds) KisScratchPad *m_scratchPad; }; KisScratchPad::KisScratchPad(QWidget *parent) : QWidget(parent) , m_toolMode(HOVERING) , m_paintLayer(0) , m_displayProfile(0) , m_resourceProvider(0) { setAutoFillBackground(false); setMouseTracking(true); m_cursor = KisCursor::load("tool_freehand_cursor.png", 5, 5); + m_colorPickerCursor = KisCursor::load("tool_color_picker_cursor.png", 5, 5); setCursor(m_cursor); + KisConfig cfg(true); QImage checkImage = KisCanvasWidgetBase::createCheckersImage(cfg.checkSize()); m_checkBrush = QBrush(checkImage); // We are not supposed to use updates here, // so just set the listener to null m_updateScheduler = new KisUpdateScheduler(0); m_undoStore = new KisSurrogateUndoStore(); m_undoAdapter = new KisPostExecutionUndoAdapter(m_undoStore, m_updateScheduler); m_nodeListener = new KisScratchPadNodeListener(this); connect(this, SIGNAL(sigUpdateCanvas(QRect)), SLOT(slotUpdateCanvas(QRect)), Qt::QueuedConnection); // filter will be deleted by the QObject hierarchy m_eventFilter = new KisScratchPadEventFilter(this); m_infoBuilder = new KisPaintingInformationBuilder(); m_scaleBorderWidth = 1; } KisScratchPad::~KisScratchPad() { delete m_infoBuilder; delete m_undoAdapter; delete m_undoStore; delete m_updateScheduler; delete m_nodeListener; } KisScratchPad::Mode KisScratchPad::modeFromButton(Qt::MouseButton button) const { return button == Qt::NoButton ? HOVERING : button == Qt::MidButton ? PANNING : button == Qt::RightButton ? PICKING : PAINTING; } void KisScratchPad::pointerPress(KoPointerEvent *event) { - if (m_toolMode != HOVERING) return; + if (isModeManuallySet == false) { - m_toolMode = modeFromButton(event->button()); + if (m_toolMode != HOVERING) return; + + m_toolMode = modeFromButton(event->button()); - if (m_toolMode == PAINTING) { - beginStroke(event); - event->accept(); } - else if (m_toolMode == PANNING) { - beginPan(event); - event->accept(); + + // see if we are pressing down with a button + if (event->button() == Qt::LeftButton || + event->button() == Qt::MidButton || + event->button() == Qt::RightButton) { + isMouseDown = true; + } else { + isMouseDown = false; } - else if (m_toolMode == PICKING) { - pick(event); - event->accept(); + + // if mouse is down, we are doing one of three things + if(isMouseDown) { + if (m_toolMode == PAINTING) { + beginStroke(event); + event->accept(); + } + else if (m_toolMode == PANNING) { + beginPan(event); + event->accept(); + } + else if (m_toolMode == PICKING) { + pick(event); + event->accept(); + } } + } void KisScratchPad::pointerRelease(KoPointerEvent *event) { - if (modeFromButton(event->button()) != m_toolMode) return; + isMouseDown = false; + + if (isModeManuallySet == false) { + if (modeFromButton(event->button()) != m_toolMode) return; + + if (m_toolMode == PAINTING) { + endStroke(event); + m_toolMode = HOVERING; + event->accept(); + } + else if (m_toolMode == PANNING) { + endPan(event); + m_toolMode = HOVERING; + event->accept(); + } + else if (m_toolMode == PICKING) { + event->accept(); + m_toolMode = HOVERING; + } + + } else { + if (m_toolMode == PAINTING) { + endStroke(event); + } + else if (m_toolMode == PANNING) { + endPan(event); + } - if (m_toolMode == PAINTING) { - endStroke(event); - m_toolMode = HOVERING; - event->accept(); - } - else if (m_toolMode == PANNING) { - endPan(event); - m_toolMode = HOVERING; - event->accept(); - } - else if (m_toolMode == PICKING) { event->accept(); - m_toolMode = HOVERING; } + + } void KisScratchPad::pointerMove(KoPointerEvent *event) { - m_helper->cursorMoved(documentToWidget().map(event->point)); - if (m_toolMode == PAINTING) { - doStroke(event); - event->accept(); - } - else if (m_toolMode == PANNING) { - doPan(event); - event->accept(); + if(event && event->point.isNull() == false) { + m_helper->cursorMoved(documentToWidget().map(event->point)); } - else if (m_toolMode == PICKING) { - pick(event); - event->accept(); + + + if (isMouseDown) { + if (m_toolMode == PAINTING) { + doStroke(event); + event->accept(); + } + else if (m_toolMode == PANNING) { + doPan(event); + event->accept(); + } + else if (m_toolMode == PICKING) { + pick(event); + event->accept(); + } } } void KisScratchPad::beginStroke(KoPointerEvent *event) { + m_helper->initPaint(event, documentToWidget().map(event->point), 0, 0, m_updateScheduler, m_paintLayer, m_paintLayer->paintDevice()->defaultBounds()); + + } void KisScratchPad::doStroke(KoPointerEvent *event) { m_helper->paintEvent(event); } void KisScratchPad::endStroke(KoPointerEvent *event) { Q_UNUSED(event); m_helper->endPaint(); } void KisScratchPad::beginPan(KoPointerEvent *event) { setCursor(QCursor(Qt::ClosedHandCursor)); m_panDocPoint = event->point; } void KisScratchPad::doPan(KoPointerEvent *event) { QPointF docOffset = event->point - m_panDocPoint; m_translateTransform.translate(-docOffset.x(), -docOffset.y()); updateTransformations(); update(); } void KisScratchPad::endPan(KoPointerEvent *event) { Q_UNUSED(event); - setCursor(m_cursor); + + // the normal brush editor scratchpad reverts back to paint mode when done + if(isModeManuallySet) { + setCursor(QCursor(Qt::OpenHandCursor)); + } else { + setCursor(m_cursor); + } + } void KisScratchPad::pick(KoPointerEvent *event) { KoColor color; if (KisToolUtils::pickColor(color, m_paintLayer->projection(), event->point.toPoint())) { emit colorSelected(color); } } void KisScratchPad::setOnScreenResolution(qreal scaleX, qreal scaleY) { m_scaleBorderWidth = BORDER_SIZE(qMax(scaleX, scaleY)); m_scaleTransform = QTransform::fromScale(scaleX, scaleY); updateTransformations(); update(); } QTransform KisScratchPad::documentToWidget() const { return m_translateTransform.inverted() * m_scaleTransform; } QTransform KisScratchPad::widgetToDocument() const { return m_scaleTransform.inverted() * m_translateTransform; } void KisScratchPad::updateTransformations() { m_eventFilter->setWidgetToDocumentTransform(widgetToDocument()); } QRect KisScratchPad::imageBounds() const { return widgetToDocument().mapRect(rect()); } void KisScratchPad::imageUpdated(const QRect &rect) { emit sigUpdateCanvas(documentToWidget().mapRect(QRectF(rect)).toAlignedRect()); } void KisScratchPad::slotUpdateCanvas(const QRect &rect) { update(rect); } void KisScratchPad::paintEvent ( QPaintEvent * event ) { if(!m_paintLayer) return; QRectF imageRect = widgetToDocument().mapRect(QRectF(event->rect())); QRect alignedImageRect = imageRect.adjusted(-m_scaleBorderWidth, -m_scaleBorderWidth, m_scaleBorderWidth, m_scaleBorderWidth).toAlignedRect(); QPointF offset = alignedImageRect.topLeft(); m_paintLayer->projectionPlane()->recalculate(alignedImageRect, m_paintLayer); KisPaintDeviceSP projection = m_paintLayer->projection(); QImage image = projection->convertToQImage(m_displayProfile, alignedImageRect.x(), alignedImageRect.y(), alignedImageRect.width(), alignedImageRect.height(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); QPainter gc(this); gc.fillRect(event->rect(), m_checkBrush); gc.setRenderHints(QPainter::SmoothPixmapTransform); gc.drawImage(QRectF(event->rect()), image, imageRect.translated(-offset)); QBrush brush(Qt::lightGray); QPen pen(brush, 1, Qt::DotLine); gc.setPen(pen); if (m_cutoutOverlay.isValid()) { gc.drawRect(m_cutoutOverlay); } if(!isEnabled()) { QColor color(Qt::lightGray); color.setAlphaF(0.5); QBrush disabledBrush(color); gc.fillRect(event->rect(), disabledBrush); } gc.end(); } void KisScratchPad::setupScratchPad(KisCanvasResourceProvider* resourceProvider, const QColor &defaultColor) { m_resourceProvider = resourceProvider; KisConfig cfg(true); setDisplayProfile(cfg.displayProfile(QApplication::desktop()->screenNumber(this))); connect(m_resourceProvider, SIGNAL(sigDisplayProfileChanged(const KoColorProfile*)), SLOT(setDisplayProfile(const KoColorProfile*))); connect(m_resourceProvider, SIGNAL(sigOnScreenResolutionChanged(qreal,qreal)), SLOT(setOnScreenResolution(qreal,qreal))); connect(this, SIGNAL(colorSelected(KoColor)), m_resourceProvider, SLOT(slotSetFGColor(KoColor))); m_helper.reset(new KisToolFreehandHelper(m_infoBuilder, m_resourceProvider->resourceManager())); - m_defaultColor = KoColor(defaultColor, KoColorSpaceRegistry::instance()->rgb8()); + setFillColor(defaultColor); KisPaintDeviceSP paintDevice = new KisPaintDevice(m_defaultColor.colorSpace(), "scratchpad"); m_paintLayer = new KisPaintLayer(0, "ScratchPad", OPACITY_OPAQUE_U8, paintDevice); m_paintLayer->setGraphListener(m_nodeListener); m_paintLayer->paintDevice()->setDefaultBounds(new KisScratchPadDefaultBounds(this)); fillDefault(); } void KisScratchPad::setCutoutOverlayRect(const QRect& rc) { m_cutoutOverlay = rc; } +void KisScratchPad::setModeManually(bool value) +{ + isModeManuallySet = value; +} + +void KisScratchPad::setModeType(QString mode) +{ + if (mode.toLower() == "painting") { + m_toolMode = PAINTING; + setCursor(m_cursor); + } + else if (mode.toLower() == "panning") { + m_toolMode = PANNING; + setCursor(Qt::OpenHandCursor); + } + else if (mode.toLower() == "colorpicking") { + m_toolMode = PICKING; + setCursor(m_colorPickerCursor); + } +} + QImage KisScratchPad::cutoutOverlay() const { if(!m_paintLayer) return QImage(); KisPaintDeviceSP paintDevice = m_paintLayer->paintDevice(); QRect rc = widgetToDocument().mapRect(m_cutoutOverlay); QImage rawImage = paintDevice->convertToQImage(0, rc.x(), rc.y(), rc.width(), rc.height(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); QImage scaledImage = rawImage.scaled(m_cutoutOverlay.size(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); return scaledImage; } void KisScratchPad::setPresetImage(const QImage& image) { m_presetImage = image; } void KisScratchPad::paintCustomImage(const QImage& loadedImage) { // this is 99% copied from the normal paintPresetImage() // we don't want to save over the preset image, so we don't // want to store it in the m_presetImage if(!m_paintLayer) return; KisPaintDeviceSP paintDevice = m_paintLayer->paintDevice(); QRect overlayRect = widgetToDocument().mapRect(m_cutoutOverlay); QRect imageRect(QPoint(), overlayRect.size()); QImage scaledImage = loadedImage.scaled(overlayRect.size(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); KisPaintDeviceSP device = new KisPaintDevice(paintDevice->colorSpace()); device->convertFromQImage(scaledImage, 0); KisPainter painter(paintDevice); painter.beginTransaction(); painter.bitBlt(overlayRect.topLeft(), device, imageRect); painter.deleteTransaction(); update(); } +void KisScratchPad::loadScratchpadImage(QImage image) +{ + if(!m_paintLayer) return; + + m_translateTransform.reset(); // image will be loaded at 0,0, so reset panning location + updateTransformations(); + + fillDefault(); // wipes out whatever was there before + + QRect imageSize = image.rect(); + KisPaintDeviceSP paintDevice = m_paintLayer->paintDevice(); + + KisPaintDeviceSP device = new KisPaintDevice(paintDevice->colorSpace()); + device->convertFromQImage(image, 0); + + KisPainter painter(paintDevice); + painter.beginTransaction(); + painter.bitBlt(imageSize.topLeft(), device, imageSize); + painter.deleteTransaction(); + update(); +} + +QImage KisScratchPad::copyScratchpadImageData() +{ + const QRect paintingBounds = m_paintLayer.data()->exactBounds(); + QImage imageData = m_paintLayer->paintDevice()->convertToQImage(0, paintingBounds.x(), paintingBounds.y(), paintingBounds.width(), paintingBounds.height(), + KoColorConversionTransformation::internalRenderingIntent(), + KoColorConversionTransformation::internalConversionFlags()); + return imageData; +} + void KisScratchPad::paintPresetImage() { if(!m_paintLayer) return; KisPaintDeviceSP paintDevice = m_paintLayer->paintDevice(); QRect overlayRect = widgetToDocument().mapRect(m_cutoutOverlay); QRect imageRect(QPoint(), overlayRect.size()); QImage scaledImage = m_presetImage.scaled(overlayRect.size(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); KisPaintDeviceSP device = new KisPaintDevice(paintDevice->colorSpace()); device->convertFromQImage(scaledImage, 0); KisPainter painter(paintDevice); painter.beginTransaction(); painter.bitBlt(overlayRect.topLeft(), device, imageRect); painter.deleteTransaction(); update(); } void KisScratchPad::setDisplayProfile(const KoColorProfile *colorProfile) { if (colorProfile) { m_displayProfile = colorProfile; QWidget::update(); } } void KisScratchPad::fillDefault() { if(!m_paintLayer) return; KisPaintDeviceSP paintDevice = m_paintLayer->paintDevice(); KisTransaction t(paintDevice); paintDevice->setDefaultPixel(m_defaultColor); paintDevice->clear(); t.end(); update(); } void KisScratchPad::fillTransparent() { if(!m_paintLayer) return; KisPaintDeviceSP paintDevice = m_paintLayer->paintDevice(); QColor transQColor(0,0,0,0); KoColor transparentColor(transQColor, KoColorSpaceRegistry::instance()->rgb8()); transparentColor.setOpacity(0.0); KisTransaction t(paintDevice); paintDevice->setDefaultPixel(transparentColor); paintDevice->clear(); t.end(); update(); } +void KisScratchPad::setFillColor(QColor newColor) +{ + m_defaultColor = KoColor(newColor, KoColorSpaceRegistry::instance()->rgb8()); +} + void KisScratchPad::fillGradient() { if(!m_paintLayer) return; KisPaintDeviceSP paintDevice = m_paintLayer->paintDevice(); KoAbstractGradientSP gradient = m_resourceProvider->currentGradient(); QRect gradientRect = widgetToDocument().mapRect(rect()); KisTransaction t(paintDevice); paintDevice->clear(); KisGradientPainter painter(paintDevice); painter.setGradient(gradient); painter.setGradientShape(KisGradientPainter::GradientShapeLinear); painter.paintGradient(gradientRect.topLeft(), gradientRect.bottomRight(), KisGradientPainter::GradientRepeatNone, 0.2, false, gradientRect.left(), gradientRect.top(), gradientRect.width(), gradientRect.height()); t.end(); update(); } void KisScratchPad::fillBackground() { if(!m_paintLayer) return; KisPaintDeviceSP paintDevice = m_paintLayer->paintDevice(); KisTransaction t(paintDevice); paintDevice->setDefaultPixel(m_resourceProvider->bgColor()); paintDevice->clear(); t.end(); update(); } void KisScratchPad::fillLayer() { if(!m_paintLayer) return; KisPaintDeviceSP paintDevice = m_paintLayer->paintDevice(); QRect sourceRect(0, 0, paintDevice->exactBounds().width(), paintDevice->exactBounds().height()); KisPainter painter(paintDevice); painter.beginTransaction(); painter.bitBlt(QPoint(0, 0), m_resourceProvider->currentImage()->projection(), sourceRect); painter.deleteTransaction(); update(); } diff --git a/libs/ui/widgets/kis_scratch_pad.h b/libs/ui/widgets/kis_scratch_pad.h index d920ec2327..34c1168919 100644 --- a/libs/ui/widgets/kis_scratch_pad.h +++ b/libs/ui/widgets/kis_scratch_pad.h @@ -1,181 +1,204 @@ /* This file is part of the KDE project * Copyright 2010 (C) Boudewijn Rempt * Copyright 2011 (C) Dmitry Kazakov * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KIS_SCRATCH_PAD_H #define KIS_SCRATCH_PAD_H #include #include #include #include #include #include #include class QColor; class KoColorProfile; class KoPointerEvent; class KisCanvasResourceProvider; class KisUpdateScheduler; class KisUndoStore; class KisPostExecutionUndoAdapter; class KisScratchPadEventFilter; class KisPaintingInformationBuilder; class KisToolFreehandHelper; class KisNodeGraphListener; /** * A scratchpad is a painting canvas with only one zoomlevel and based on * a paint layer, not on a KisImage. It can have a blank, tiled background or * a gradient background. */ class KRITAUI_EXPORT KisScratchPad : public QWidget { Q_OBJECT public: void setupScratchPad(KisCanvasResourceProvider* resourceProvider, const QColor &defaultColor); KisScratchPad(QWidget *parent = 0); ~KisScratchPad() override; /// set the specified rect as the area taken for @see cutoutOverlay void setCutoutOverlayRect(const QRect&rc); + /** + * keep track of if our scratchpad is in paint, pan, or color pick mode + * Set to true if there is a GUI controlling current mode + * If this is false, the modes are only changed with various mouse click shortcuts + */ + void setModeManually(bool value); + + /** + * @brief change the mode explicitly to paint, mix, or pan + * @param what mode to change it to + */ + void setModeType(QString modeName); + /// return the contents of the area under the cutoutOverlay rect QImage cutoutOverlay() const; // A callback for our own node graph listener void imageUpdated(const QRect &rect); // A callback for scratch pad default bounds QRect imageBounds() const; // Called by the event filter void pointerPress(KoPointerEvent *event); void pointerRelease(KoPointerEvent *event); void pointerMove(KoPointerEvent *event); public Q_SLOTS: void fillDefault(); void fillGradient(); void fillBackground(); void fillTransparent(); + void setFillColor(QColor newColor); + /// Fill the area with what is on your current canvas void fillLayer(); /** * Set the icon of the current preset */ void setPresetImage(const QImage& image); /** * Paint the icon of the current preset inside the * cutout overlay * * \see setPresetImage */ void paintPresetImage(); /** * Paint the icon of a custom image that is being loaded * */ - void paintCustomImage(const QImage& loadedImage); + void paintCustomImage(const QImage & loadedImage); + + + void loadScratchpadImage(QImage image); + + QImage copyScratchpadImageData(); private Q_SLOTS: void setOnScreenResolution(qreal scaleX, qreal scaleY); void setDisplayProfile(const KoColorProfile* colorProfile); void slotUpdateCanvas(const QRect &rect); Q_SIGNALS: void colorSelected(const KoColor& color); void sigUpdateCanvas(const QRect &rect); protected: void paintEvent ( QPaintEvent * event ) override; private: void beginStroke(KoPointerEvent *event); void doStroke(KoPointerEvent *event); void endStroke(KoPointerEvent *event); void beginPan(KoPointerEvent *event); void doPan(KoPointerEvent *event); void endPan(KoPointerEvent *event); void pick(KoPointerEvent *event); void updateTransformations(); QTransform documentToWidget() const; QTransform widgetToDocument() const; private: enum Mode { PAINTING, HOVERING, PANNING, PICKING }; Mode modeFromButton(Qt::MouseButton button) const; private: KoColor m_defaultColor; Mode m_toolMode; + bool isModeManuallySet = false; + bool isMouseDown = false; KisPaintLayerSP m_paintLayer; const KoColorProfile* m_displayProfile; QCursor m_cursor; + QCursor m_colorPickerCursor; QRect m_cutoutOverlay; QBrush m_checkBrush; KisCanvasResourceProvider* m_resourceProvider; KisUpdateScheduler *m_updateScheduler; KisUndoStore *m_undoStore; KisPostExecutionUndoAdapter *m_undoAdapter; KisNodeGraphListener *m_nodeListener; KisScratchPadEventFilter *m_eventFilter; QScopedPointer m_helper; KisPaintingInformationBuilder *m_infoBuilder; QTransform m_scaleTransform; QTransform m_translateTransform; QPointF m_panDocPoint; int m_scaleBorderWidth; QImage m_presetImage; }; #endif // KIS_SCRATCH_PAD_H diff --git a/packaging/windows/installer/ConfigureInstallerNsis.cmake b/packaging/windows/installer/ConfigureInstallerNsis.cmake index 45085c4069..9d4e18a32c 100644 --- a/packaging/windows/installer/ConfigureInstallerNsis.cmake +++ b/packaging/windows/installer/ConfigureInstallerNsis.cmake @@ -1,25 +1,33 @@ if("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") set(INSTALLER_NSIS_IS_32_BIT NO) else() set(INSTALLER_NSIS_IS_32_BIT YES) endif() configure_file( ${CMAKE_CURRENT_LIST_DIR}/MakeInstallerNsis.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/MakeinstallerNsis.cmake @ONLY ) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/MakeinstallerNsis.cmake DESTINATION ${CMAKE_INSTALL_PREFIX} ) install(FILES ${CMAKE_CURRENT_LIST_DIR}/installer_krita.nsi ${CMAKE_CURRENT_LIST_DIR}/license_gpl-3.0.rtf DESTINATION ${CMAKE_INSTALL_PREFIX}/installer ) install(FILES ${CMAKE_CURRENT_LIST_DIR}/include/FileExists2.nsh ${CMAKE_CURRENT_LIST_DIR}/include/IsFileInUse.nsh DESTINATION ${CMAKE_INSTALL_PREFIX}/installer/include ) + +install( + FILES + ${CMAKE_CURRENT_LIST_DIR}/translations/English.nsh + ${CMAKE_CURRENT_LIST_DIR}/translations/TradChinese.nsh + ${CMAKE_CURRENT_LIST_DIR}/translations/SimpChinese.nsh + DESTINATION ${CMAKE_INSTALL_PREFIX}/installer/translations +) diff --git a/packaging/windows/installer/MakeInstallerNsis.cmake.in b/packaging/windows/installer/MakeInstallerNsis.cmake.in index a4603609dc..bee7ddc325 100644 --- a/packaging/windows/installer/MakeInstallerNsis.cmake.in +++ b/packaging/windows/installer/MakeInstallerNsis.cmake.in @@ -1,166 +1,167 @@ cmake_minimum_required(VERSION 3.0 FATAL_ERROR) set(ARCH_IS_32_BIT @INSTALLER_NSIS_IS_32_BIT@) if(ARCH_IS_32_BIT) set(ARG_ARCH "/DKRITA_INSTALLER_32") set(FILENAME_ARCH "-x86") else() set(ARG_ARCH "/DKRITA_INSTALLER_64") set(FILENAME_ARCH "-x64") endif() if(NOT IS_DIRECTORY "${KRITA_PACKAGE_ROOT}") message(FATAL_ERROR "KRITA_PACKAGE_ROOT not set") endif() if(NOT DEFINED OUTPUT_FILEPATH) set(OUTPUT_FILEPATH "${CMAKE_CURRENT_BINARY_DIR}/krita-setup${FILENAME_ARCH}.exe") endif() if(NOT DEFINED DOWNLOAD_DIR) set(DOWNLOAD_DIR "${CMAKE_CURRENT_BINARY_DIR}") endif() # Download and find NSIS if(NOT DEFINED NO_DOWNLOAD_NSIS) - set(DOWNLOAD_NSIS_VERSION "3.02.1") + set(DOWNLOAD_NSIS_VERSION "3.05") message(STATUS "Downloading NSIS...") if(EXISTS "${CMAKE_CURRENT_BINARY_DIR}/nsis-${DOWNLOAD_NSIS_VERSION}") file(REMOVE_RECURSE "${CMAKE_CURRENT_BINARY_DIR}/nsis-${DOWNLOAD_NSIS_VERSION}") endif() file(DOWNLOAD "https://files.kde.org/krita/build/dependencies/nsis-${DOWNLOAD_NSIS_VERSION}.zip" "${DOWNLOAD_DIR}/nsis-${DOWNLOAD_NSIS_VERSION}.zip" - EXPECTED_HASH SHA1=06c791f9cf668d945564316c9c947ebb4cc469e9 + EXPECTED_HASH SHA1=68f9c7025110b95c20431bbce28f3bf2d0ddb1ff ) execute_process(COMMAND ${CMAKE_COMMAND} -E tar xfz "${DOWNLOAD_DIR}/nsis-${DOWNLOAD_NSIS_VERSION}.zip" WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" RESULT_VARIABLE result_extract ) if(NOT result_extract EQUAL 0) message(FATAL_ERROR "Failed to extract NSIS") endif() if(NOT IS_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/nsis-${DOWNLOAD_NSIS_VERSION}") message(FATAL_ERROR "Failed to find NSIS after extracting") endif() find_program(TOOL_MAKENSIS NAMES makensis.exe PATHS "${CMAKE_CURRENT_BINARY_DIR}/nsis-${DOWNLOAD_NSIS_VERSION}" NO_DEFAULT_PATH ) else() find_program(TOOL_MAKENSIS NAMES makensis.exe HINTS "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\NSIS]" "[HKEY_LOCAL_MACHINE\\SOFTWARE\\NSIS]" "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\NSIS\\Unicode]" "[HKEY_LOCAL_MACHINE\\SOFTWARE\\NSIS\\Unicode]" ) endif() if(NOT TOOL_MAKENSIS) message(FATAL_ERROR "Failed to find makensis.exe") endif() execute_process(COMMAND "${TOOL_MAKENSIS}" "/version" OUTPUT_VARIABLE MAKENSIS_VERSION ) # Check version is v3.* if(NOT MAKENSIS_VERSION MATCHES "^v3\\.") message(FATAL_ERROR "Expected NSIS version v3.*, got ${MAKENSIS_VERSION}") endif() message(STATUS "NSIS version ${MAKENSIS_VERSION}") # Check if package contains debug symbols file(TO_CMAKE_PATH "${KRITA_PACKAGE_ROOT}" KRITA_PACKAGE_ROOT_PATCHED) file(GLOB_RECURSE globForDebugFiles "${KRITA_PACKAGE_ROOT_PATCHED}/*.debug" ) if(globForDebugFiles) if(REMOVE_DEBUG) message(STATUS "Removing debug symbols") foreach(debugFileItem ${globForDebugFiles}) get_filename_component(debugFileDir "${debugFileItem}" DIRECTORY) get_filename_component(debugDirName "${debugFileDir}" NAME) if(debugDirName STREQUAL ".debug") if(EXISTS "${debugFileDir}") message(STATUS "Deleting ${debugFileDir}") file(REMOVE_RECURSE "${debugFileDir}") endif() else() message(STATUS "Deleting ${debugFileItem}") file(REMOVE "${debugFileItem}") endif() endforeach() else() message(FATAL_ERROR "${KRITA_PACKAGE_ROOT} seems to contain debug symbols. Set REMOVE_DEBUG to true if you want to remove them.") endif() endif() # Download installer script package message(STATUS "Downloading NSIS script package...") if(EXISTS "${CMAKE_CURRENT_BINARY_DIR}/krita-nsis") file(REMOVE_RECURSE "${CMAKE_CURRENT_BINARY_DIR}/krita-nsis") endif() file(DOWNLOAD "https://github.com/alvinhochun/KritaShellExtension/releases/download/v1.2.4b/krita-nsis-v1.2.4b.zip" "${DOWNLOAD_DIR}/krita-nsis.zip" EXPECTED_HASH SHA1=43c5bade13fb885e8546dac4486b8e2df62dc692 ) execute_process(COMMAND ${CMAKE_COMMAND} -E tar xfz "${DOWNLOAD_DIR}/krita-nsis.zip" WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" RESULT_VARIABLE result_extract ) if(NOT result_extract EQUAL 0) message(FATAL_ERROR "Failed to extract krita-nsis package") endif() if(NOT IS_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/krita-nsis") message(FATAL_ERROR "Failed to find krita-nsis after extracting") endif() # Place Krita installer files in the right place file(COPY "${CMAKE_CURRENT_LIST_DIR}/installer/" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/krita-nsis/" FILES_MATCHING PATTERN "*" ) # Detect FFmpeg (currently optional) if(EXISTS "${KRITA_PACKAGE_ROOT}/bin/ffmpeg.exe" AND EXISTS "${KRITA_PACKAGE_ROOT}/bin/ffmpeg_LICENSE.txt" AND EXISTS "${KRITA_PACKAGE_ROOT}/bin/ffmpeg_README.txt" ) set(ARG_HAS_FFMPEG "/DHAS_FFMPEG") else() set(ARG_HAS_FFMPEG "/DNO_HAS_FFMPEG") endif() # Build installer message(STATUS "Building installer...") set(KRITA_VERSION_NUMBER "@KRITA_STABLE_VERSION_MAJOR@.@KRITA_STABLE_VERSION_MINOR@.@KRITA_VERSION_RELEASE@.@KRITA_VERSION_REVISION@") set(KRITA_VERSION_STRING "@KRITA_VERSION_STRING@") set(KRITA_GIT_SHA1_STRING "@KRITA_GIT_SHA1_STRING@") if(KRITA_GIT_SHA1_STRING) set(KRITA_VERSION_STRING "${KRITA_VERSION_STRING} (git ${KRITA_GIT_SHA1_STRING})") endif() execute_process(COMMAND "${TOOL_MAKENSIS}" "${ARG_ARCH}" "/DKRITA_VERSION=${KRITA_VERSION_NUMBER}" "/DKRITA_VERSION_DISPLAY=${KRITA_VERSION_STRING}" "/DKRITA_INSTALLER_OUTPUT_DIR=" "/DKRITA_INSTALLER_OUTPUT_NAME=${OUTPUT_FILEPATH}" "/DKRITA_PACKAGE_ROOT=${KRITA_PACKAGE_ROOT}" "${ARG_HAS_FFMPEG}" "/XSetCompressor /SOLID lzma" "/V3" + "/INPUTCHARSET" "UTF8" "${CMAKE_CURRENT_BINARY_DIR}/krita-nsis/installer_krita.nsi" RESULT_VARIABLE result_makensis ) if(NOT result_makensis EQUAL 0) message(FATAL_ERROR "Failed to build installer") endif() message(STATUS "Built installer") diff --git a/packaging/windows/installer/installer_krita.nsi b/packaging/windows/installer/installer_krita.nsi index 20819075e6..27d8aad31d 100644 --- a/packaging/windows/installer/installer_krita.nsi +++ b/packaging/windows/installer/installer_krita.nsi @@ -1,662 +1,646 @@ !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 +# Enabling DPI awareness creates awful CJK text in some sizes, so don't enable it. +ManifestDPIAware false # 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)" +!define MUI_PAGE_HEADER_TEXT "$(ShellExLicensePageHeader)" !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 +!define MUI_PAGE_CUSTOMFUNCTION_PRE un.func_UnintallFirstpage_Init !insertmacro MUI_UNPAGE_CONFIRM !insertmacro MUI_UNPAGE_INSTFILES +# Languages !insertmacro MUI_LANGUAGE "English" +!insertmacro MUI_LANGUAGE "TradChinese" +!insertmacro MUI_LANGUAGE "SimpChinese" !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..." + DetailPrint "$(RemovingShellEx)" 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." + MessageBox MB_OK|MB_ICONSTOP "$(RemoveShellExFailed)" ${EndIf} SetDetailsPrint both - DetailPrint "Failed to remove Krita Shell Integration." + DetailPrint "$(RemoveShellExFailed)" Abort ${EndIf} Delete "$PrevShellExInstallLocation\uninstall.exe" RMDir /REBOOTOK "$PrevShellExInstallLocation" SetRebootFlag false SetDetailsPrint lastused - DetailPrint "Krita Shell Integration removed." + DetailPrint "$(RemoveShellExDone)" pop $R0 ${EndIf} SectionEnd -Section "Remove Old Version" SEC_remove_old_version +Section "$(SectionRemoveOldVer)" SEC_remove_old_version ${If} $KritaNsisInstallLocation != "" ${AndIf} ${FileExists} "$KritaNsisInstallLocation\uninstall.exe" push $R0 - DetailPrint "Removing previous version..." + DetailPrint "$(RemovingOldVer)" 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." + MessageBox MB_OK|MB_ICONSTOP "$(RemoveOldVerFailed)" ${EndIf} SetDetailsPrint both - DetailPrint "Failed to remove previous version of Krita." + DetailPrint "$(RemoveOldVerFailed)" Abort ${EndIf} Delete "$KritaNsisInstallLocation\uninstall.exe" RMDir /REBOOTOK "$KritaNsisInstallLocation" SetRebootFlag false SetDetailsPrint lastused - DetailPrint "Previous version removed." + DetailPrint "$(RemoveOldVerDone)" 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 + # InstallerLanguage: + # Language used by the installer (to be re-used for the uninstaller) + WriteRegStr HKLM "Software\Krita" \ + "InstallerLanguage" "$LANGUAGE" # 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 +Section "$(SectionShellEx)" 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 +Section "$(SectionBundledFfmpeg)" 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}" + #!insertmacro MUI_DESCRIPTION_TEXT ${SEC_remove_shellex} "Remove previously installed Krita Shell Integration." + !insertmacro MUI_DESCRIPTION_TEXT ${SEC_remove_old_version} "$(SectionRemoveOldVerDesc)" + !insertmacro MUI_DESCRIPTION_TEXT ${SEC_product_main} "$(SectionMainDesc)" + !insertmacro MUI_DESCRIPTION_TEXT ${SEC_shellex} "$(SectionShellExDesc)" !ifdef HAS_FFMPEG - !insertmacro MUI_DESCRIPTION_TEXT ${SEC_ffmpeg} "Install a bundled version of FFmpeg for exporting animations." + !insertmacro MUI_DESCRIPTION_TEXT ${SEC_ffmpeg} "$(SectionBundledFfmpegDesc)" !endif !insertmacro MUI_FUNCTION_DESCRIPTION_END -Section "un.Shell Integration" +Section "un.$(SectionShellEx)" ${If} $UninstallShellExStandalone == 1 push $R0 - DetailPrint "Removing Krita Shell Integration..." + DetailPrint "$(RemovingShellEx)" 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!" + MessageBox MB_OK|MB_ICONSTOP "$(RemoveShellExFailed)" ${EndIf} SetDetailsPrint lastused SetDetailsPrint both - DetailPrint "Failed to remove Krita Shell Integration." + DetailPrint "$(RemoveShellExFailed)" ${EndIf} Delete "$INSTDIR\shellex\uninstall.exe" RMDir /REBOOTOK "$INSTDIR\shellex" SetDetailsPrint lastused - DetailPrint "Krita Shell Integration removed." + DetailPrint "$(RemoveShellExDone)" 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." + MessageBox MB_OK|MB_ICONSTOP "$(MsgRequireWin7)" ${EndIf} Abort ${EndIf} + + ${IfNot} ${Silent} + # Language selection, seems that the order is predefined. + Push "" # This value is for languages auto count + Push ${LANG_ENGLISH} + Push English + Push ${LANG_TRADCHINESE} + Push "繁體中文" + Push ${LANG_SIMPCHINESE} + Push "简体中文" + Push A # = auto count languages + LangDLL::LangDialog "$(^SetupCaption)" "$(SetupLangPrompt)" + Pop $LANGUAGE + ${If} $LANGUAGE == "cancel" + Abort + ${Endif} + ${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/" + MessageBox MB_OK|MB_ICONSTOP "$(Msg64bitOn32bit)" ${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?" \ + MessageBox MB_YESNO|MB_ICONEXCLAMATION "$(Msg32bitOn64bit)" \ /SD IDYES \ IDYES lbl_allow32on64 Abort ${EndIf} lbl_allow32on64: ${Endif} !endif - # Detect other Krita versions + + # Detect ancient 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: + ${EndIf} + ${If} $KritaMsiProductX86 != "" + ${OrIf} $KritaMsiProductX64 != "" + ${IfNot} ${Silent} + MessageBox MB_YESNO|MB_ICONQUESTION|MB_DEFBUTTON1 "$(MsgAncientVerMustBeRemoved)" \ + /SD IDYES \ + IDYES lbl_removeAncientVer + Abort + ${EndIf} + lbl_removeAncientVer: + ${If} $KritaMsiProductX64 != "" push $R0 ${MsiUninstall} $KritaMsiProductX64 $R0 ${If} $R0 != 0 ${IfNot} ${Silent} - MessageBox MB_OK|MB_ICONSTOP "Failed to remove Krita 3.0 Alpha 1." + ${IfKritaMsi3Alpha} $KritaMsiProductX64 + MessageBox MB_OK|MB_ICONSTOP "$(MsgKrita3alpha1RemoveFailed)" + ${Else} + MessageBox MB_OK|MB_ICONSTOP "$(MsgKrita2msi64bitRemoveFailed)" + ${EndIf} ${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} + ${EndIf} + ${If} $KritaMsiProductX86 != "" + push $R0 + ${MsiUninstall} $KritaMsiProductX86 $R0 + ${If} $R0 != 0 ${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 + MessageBox MB_OK|MB_ICONSTOP "$(MsgKrita2msi32bitRemoveFailed)" ${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)." + Abort ${EndIf} - Abort + pop $R0 + StrCpy $KritaMsiProductX86 "" ${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." + MessageBox MB_OK|MB_ICONINFORMATION "$(MsgKritaSameVerReinstall)" ${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." + MessageBox MB_OK|MB_ICONINFORMATION "$(MsgKrita3264bitSwap)" !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." + MessageBox MB_OK|MB_ICONEXCLAMATION "$(MsgKrita3264bitSwap)" !endif ${EndIf} ${EndIf} ${ElseIf} $R0 == 1 # Upgrade ${If} $KritaNsisBitness == ${KRITA_INSTALLER_BITNESS} # 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}." + MessageBox MB_OK|MB_ICONINFORMATION "$(MsgKrita3264bitSwap)" !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}." + MessageBox MB_OK|MB_ICONEXCLAMATION "$(MsgKrita3264bitSwap)" !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." + MessageBox MB_OK|MB_ICONSTOP "$(MsgKritaNewerAlreadyInstalled)" ${EndIf} Abort ${Else} ${IfNot} ${Silent} - MessageBox MB_OK|MB_ICONSTOP "Unexpected state" + MessageBox MB_OK|MB_ICONSTOP "Error: 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." + MessageBox MB_OK|MB_ICONEXCLAMATION "$(MsgKritaRunning)" ${EndIf} + SetErrorLevel 10 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 + + # Get and use installer language: + Push $0 + ReadRegStr $0 HKLM "Software\Krita" "InstallerLanguage" + ${If} $0 != "" + StrCpy $LANGUAGE $0 + ${EndIf} + Pop $0 + ReadRegDWORD $UninstallShellExStandalone HKLM "Software\Krita\ShellExtension" "Standalone" + ${If} ${Silent} + # Only check here if running in silent mode. It's otherwise checked in + # un.func_UnintallFirstpage_Init in order to display a prompt in the + # correct language. + ${If} ${IsFileinUse} "$INSTDIR\bin\krita.exe" + SetErrorLevel 10 + Abort + ${EndIf} + ${EndIf} +FunctionEnd + +Function un.func_UnintallFirstpage_Init ${If} ${IsFileinUse} "$INSTDIR\bin\krita.exe" ${IfNot} ${Silent} - MessageBox MB_OK|MB_ICONEXCLAMATION "Krita appears to be running. Please close Krita before uninstalling." + MessageBox MB_OK|MB_ICONEXCLAMATION "$(MsgUninstallKritaRunning)" ${EndIf} - Abort + SetErrorLevel 10 + Quit ${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}." + !insertmacro MUI_HEADER_TEXT "$(ConfirmInstallPageHeader)" "$(ConfirmInstallPageDesc)" + + ${NSD_CreateLabel} 0u 0u 300u 20u "$(DesktopIconPageDesc2)" + pop $R0 + + ${NSD_CreateCheckbox} 0u 20u 300u 10u "$(DesktopIconPageCheckbox)" + pop $hwndChkDesktopIcon + ${If} $CreateDesktopIcon == 1 + ${NSD_Check} $hwndChkDesktopIcon + ${Else} + ${NSD_Uncheck} $hwndChkDesktopIcon + ${EndIf} + ${NSD_OnClick} $hwndChkDesktopIcon func_DesktopShortcutPage_CheckChange - ${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" + ${NSD_CreateLabel} 0u 40u 300u 140u "$(ConfirmInstallPageDesc2)" pop $R0 # TODO: Add install option summary for review? nsDialogs::Show pop $R0 FunctionEnd + + +# Strings +!include "translations\English.nsh" +!include "translations\TradChinese.nsh" +!include "translations\SimpChinese.nsh" diff --git a/packaging/windows/installer/translations/English.nsh b/packaging/windows/installer/translations/English.nsh new file mode 100644 index 0000000000..47a0922b31 --- /dev/null +++ b/packaging/windows/installer/translations/English.nsh @@ -0,0 +1,46 @@ +!define CURRENT_LANG ${LANG_ENGLISH} + +# Strings to show in the installation log: +LangString RemovingShellEx ${CURRENT_LANG} "Removing Krita Shell Integration..." +LangString RemoveShellExFailed ${CURRENT_LANG} "Failed to remove Krita Shell Integration." +LangString RemoveShellExDone ${CURRENT_LANG} "Krita Shell Integration removed." +LangString RemovingOldVer ${CURRENT_LANG} "Removing previous version..." +LangString RemoveOldVerFailed ${CURRENT_LANG} "Failed to remove previous version of Krita." +LangString RemoveOldVerDone ${CURRENT_LANG} "Previous version removed." + +# Strings for the component selection dialog: +LangString SectionRemoveOldVer ${CURRENT_LANG} "Remove Old Version" +LangString SectionRemoveOldVerDesc ${CURRENT_LANG} "Remove previously installed Krita $KritaNsisVersion ($KritaNsisBitness-bit)." +LangString SectionShellEx ${CURRENT_LANG} "Shell Integration" +LangString SectionShellExDesc ${CURRENT_LANG} "Shell Extension component to provide thumbnails and file properties display for Krita files.$\r$\n$\r$\nVersion: ${KRITASHELLEX_VERSION}" +LangString SectionMainDesc ${CURRENT_LANG} "${KRITA_PRODUCTNAME} ${KRITA_VERSION_DISPLAY}$\r$\n$\r$\nVersion: ${KRITA_VERSION}" +# We don't actually bundle FFmpeg so these are not shown. +LangString SectionBundledFfmpeg ${CURRENT_LANG} "Bundled FFmpeg" +LangString SectionBundledFfmpegDesc ${CURRENT_LANG} "Install a bundled version of FFmpeg for exporting animations." + +# Main dialog strings: +LangString SetupLangPrompt ${CURRENT_LANG} "Choose the language to be used for the setup process:" +LangString ShellExLicensePageHeader ${CURRENT_LANG} "License Agreement (Krita Shell Extension)" +LangString ConfirmInstallPageHeader ${CURRENT_LANG} "Confirm Installation" +LangString ConfirmInstallPageDesc ${CURRENT_LANG} "Confirm installation of ${KRITA_PRODUCTNAME} ${KRITA_VERSION_DISPLAY}." +LangString DesktopIconPageDesc2 ${CURRENT_LANG} "You can choose whether to create a shortcut icon on the desktop for launching Krita:" +LangString DesktopIconPageCheckbox ${CURRENT_LANG} "Create a desktop icon" +LangString ConfirmInstallPageDesc2 ${CURRENT_LANG} "Setup is ready to install ${KRITA_PRODUCTNAME} ${KRITA_VERSION_DISPLAY}. You may go back to review the install options before you continue.$\r$\n$\r$\n$_CLICK" + +# Misc. message prompts: +LangString MsgRequireWin7 ${CURRENT_LANG} "${KRITA_PRODUCTNAME} ${KRITA_VERSION_DISPLAY} requires Windows 7 or above." +LangString Msg64bitOn32bit ${CURRENT_LANG} "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/" +LangString Msg32bitOn64bit ${CURRENT_LANG} "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.$\nYou can download the 64-bit version on https://krita.org/$\n$\nDo you still wish to install the 32-bit version of Krita?" +# These prompts are used for when Krita 2.9 or earlier, or the 3.0 alpha 1 MSI version is installed. +LangString MsgAncientVerMustBeRemoved ${CURRENT_LANG} "An ancient version of Krita is detected. This program will now attempt to remove any old versions of Krita.$\nDo you wish to continue?" +LangString MsgKrita3alpha1RemoveFailed ${CURRENT_LANG} "Failed to remove Krita 3.0 Alpha 1." +LangString MsgKrita2msi32bitRemoveFailed ${CURRENT_LANG} "Failed to remove old Krita (32-bit)." +LangString MsgKrita2msi64bitRemoveFailed ${CURRENT_LANG} "Failed to remove old Krita (64-bit)." +# +LangString MsgKritaSameVerReinstall ${CURRENT_LANG} "It appears that ${KRITA_PRODUCTNAME} ${KRITA_VERSION_DISPLAY} is already installed.$\nThis setup will reinstall it." +LangString MsgKrita3264bitSwap ${CURRENT_LANG} "It appears that Krita $KritaNsisBitness-bit ($KritaNsisVersion) is currently installed. This setup will replace it with the ${KRITA_INSTALLER_BITNESS}-bit version of Krita ${KRITA_VERSION_DISPLAY}." +LangString MsgKritaNewerAlreadyInstalled ${CURRENT_LANG} "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." +LangString MsgKritaRunning ${CURRENT_LANG} "Krita appears to be running. Please close Krita before running this installer." +LangString MsgUninstallKritaRunning ${CURRENT_LANG} "Krita appears to be running. Please close Krita before uninstalling." + +!undef CURRENT_LANG diff --git a/packaging/windows/installer/translations/SimpChinese.nsh b/packaging/windows/installer/translations/SimpChinese.nsh new file mode 100644 index 0000000000..f65ed909ae --- /dev/null +++ b/packaging/windows/installer/translations/SimpChinese.nsh @@ -0,0 +1,46 @@ +!define CURRENT_LANG ${LANG_SIMPCHINESE} + +# Strings to show in the installation log: +LangString RemovingShellEx ${CURRENT_LANG} "正在删除 Krita 文件资源管理器插件..." +LangString RemoveShellExFailed ${CURRENT_LANG} "无法删除 Krita 文件资源管理器插件。" +LangString RemoveShellExDone ${CURRENT_LANG} "成功删除 Krita 文件资源管理器插件。" +LangString RemovingOldVer ${CURRENT_LANG} "正在卸载旧版软件..." +LangString RemoveOldVerFailed ${CURRENT_LANG} "无法卸载旧版 Krita 软件。" +LangString RemoveOldVerDone ${CURRENT_LANG} "成功卸载旧版 Krita 软件。" + +# Strings for the component selection dialog: +LangString SectionRemoveOldVer ${CURRENT_LANG} "卸载旧版软件" +LangString SectionRemoveOldVerDesc ${CURRENT_LANG} "卸载之前安装的 Krita $KritaNsisVersion ($KritaNsisBitness-bit)." +LangString SectionShellEx ${CURRENT_LANG} "文件资源管理器插件" +LangString SectionShellExDesc ${CURRENT_LANG} "安装此插件后,Windows 文件资源管理器即可显示 Krita 文件的缩略图和属性信息。$\r$\n$\r$\n版本: ${KRITASHELLEX_VERSION}" +LangString SectionMainDesc ${CURRENT_LANG} "${KRITA_PRODUCTNAME} ${KRITA_VERSION_DISPLAY}$\r$\n$\r$\n版本: ${KRITA_VERSION}" +# We don't actually bundle FFmpeg so these are not shown. +LangString SectionBundledFfmpeg ${CURRENT_LANG} "FFmpeg 软件包" +LangString SectionBundledFfmpegDesc ${CURRENT_LANG} "安装本安装程序自带的 FFmpeg 软件包,用于导出动画。" + +# Main dialog strings: +LangString SetupLangPrompt ${CURRENT_LANG} "请选择安装程序显示的语言:" +LangString ShellExLicensePageHeader ${CURRENT_LANG} "许可证协议 (Krita 文件资源管理器插件)" +LangString ConfirmInstallPageHeader ${CURRENT_LANG} "确认安装" +LangString ConfirmInstallPageDesc ${CURRENT_LANG} "确认安装 ${KRITA_PRODUCTNAME} ${KRITA_VERSION_DISPLAY}。" +LangString DesktopIconPageDesc2 ${CURRENT_LANG} "选择是否在桌面上创建启动 Krita 的图标快捷方式:" +LangString DesktopIconPageCheckbox ${CURRENT_LANG} "创建桌面图标快捷方式" +LangString ConfirmInstallPageDesc2 ${CURRENT_LANG} "安装程序已经准备就绪,即将安装 ${KRITA_PRODUCTNAME} ${KRITA_VERSION_DISPLAY}。在继续前可返回检查安装选项,确保一切无误。$\r$\n$\r$\n$_CLICK" + +# Misc. message prompts: +LangString MsgRequireWin7 ${CURRENT_LANG} "${KRITA_PRODUCTNAME} ${KRITA_VERSION_DISPLAY} 只支持 Windows 7 以及更高版本。" +LangString Msg64bitOn32bit ${CURRENT_LANG} "本机正在运行 32 位版本的 Windows,但本安装程序提供的是 64 位版本的 Krita,它只能在 64 位版本的 Windows 下运行。请前往 https://krita.org/ 网站,下载 32 位版本的 Krita 安装程序。" +LangString Msg32bitOn64bit ${CURRENT_LANG} "你正在 64 位版本的 Windows 下面安装 32 位版本的 Krita,这将降低程序性能。我们强烈建议你改而安装 64 位版本的 Krita。$\n请前往 https://krita.org/ 网站,下载 64 位版本的 Krita。$\n$\n仍要继续安装 32 位版本的 Krita 吗?" +# These prompts are used for when Krita 2.9 or earlier, or the 3.0 alpha 1 MSI version is installed. +LangString MsgAncientVerMustBeRemoved ${CURRENT_LANG} "检测到本机已经安装了旧版本的 Krita。本安装程序将在安装前卸载旧版软件。$\n确定要继续吗?" +LangString MsgKrita3alpha1RemoveFailed ${CURRENT_LANG} "无法卸载 Krita 3.0 Alpha 1。" +LangString MsgKrita2msi32bitRemoveFailed ${CURRENT_LANG} "无法卸载旧版 Krita (32 位)。" +LangString MsgKrita2msi64bitRemoveFailed ${CURRENT_LANG} "无法卸载旧版 Krita (64 位)。" +# +LangString MsgKritaSameVerReinstall ${CURRENT_LANG} "检测到本机已经安装了 ${KRITA_PRODUCTNAME} ${KRITA_VERSION_DISPLAY}。$\n本安装程序将重新安装此版本的 Krita。" +LangString MsgKrita3264bitSwap ${CURRENT_LANG} "检测到本机已经安装了$KritaNsisBitness位版本的 Krita ($KritaNsisVersion)。本安装程序将用${KRITA_INSTALLER_BITNESS}位版本的 Krita ${KRITA_VERSION_DISPLAY} 将其覆盖。" +LangString MsgKritaNewerAlreadyInstalled ${CURRENT_LANG} "检测到本机已经安装了$KritaNsisBitness位版本的 Krita ($KritaNsisVersion)。如需安装旧版的 Krita (${KRITA_VERSION_DISPLAY}),请先手动卸载已有版本,然后在此运行此安装程序。" +LangString MsgKritaRunning ${CURRENT_LANG} "检测到 Krita 正在运行。请在关闭 Krita 后再次运行此安装程序。" +LangString MsgUninstallKritaRunning ${CURRENT_LANG} "检测到 Krita 正在运行。请在关闭 Krita 后再次运行此卸载程序。" + +!undef CURRENT_LANG diff --git a/packaging/windows/installer/translations/TradChinese.nsh b/packaging/windows/installer/translations/TradChinese.nsh new file mode 100644 index 0000000000..478e660396 --- /dev/null +++ b/packaging/windows/installer/translations/TradChinese.nsh @@ -0,0 +1,46 @@ +!define CURRENT_LANG ${LANG_TRADCHINESE} + +# Strings to show in the installation log: +LangString RemovingShellEx ${CURRENT_LANG} "正在移除 Krita 系統介面整合元件..." +LangString RemoveShellExFailed ${CURRENT_LANG} "無法成功移除 Krita 系統介面整合元件。" +LangString RemoveShellExDone ${CURRENT_LANG} "已移除 Krita 系統介面整合元件。" +LangString RemovingOldVer ${CURRENT_LANG} "正在解除安裝先前版本..." +LangString RemoveOldVerFailed ${CURRENT_LANG} "無法成功解除安裝先前版本的 Krita。" +LangString RemoveOldVerDone ${CURRENT_LANG} "已解除安裝先前版本。" + +# Strings for the component selection dialog: +LangString SectionRemoveOldVer ${CURRENT_LANG} "移除先前版本" +LangString SectionRemoveOldVerDesc ${CURRENT_LANG} "解除安裝先前曾安裝的 Krita $KritaNsisVersion ($KritaNsisBitness-bit)." +LangString SectionShellEx ${CURRENT_LANG} "系統介面整合" +LangString SectionShellExDesc ${CURRENT_LANG} "安裝整合元件 (Shell Extension) 以在 Window 檔案總管中顯示 Krita 檔案的縮圖和屬性資訊。$\r$\n$\r$\n版本: ${KRITASHELLEX_VERSION}" +LangString SectionMainDesc ${CURRENT_LANG} "${KRITA_PRODUCTNAME} ${KRITA_VERSION_DISPLAY}$\r$\n$\r$\n版本: ${KRITA_VERSION}" +# We don't actually bundle FFmpeg so these are not shown. +LangString SectionBundledFfmpeg ${CURRENT_LANG} "內置 FFmpeg" +LangString SectionBundledFfmpegDesc ${CURRENT_LANG} "安裝包含在本安裝程式中的 FFmpeg 以用作匯出動畫檔案。" + +# Main dialog strings: +LangString SetupLangPrompt ${CURRENT_LANG} "請選擇安裝過程使用的語言:" +LangString ShellExLicensePageHeader ${CURRENT_LANG} "授權協議 (Krita 系統介面整合元件)" +LangString ConfirmInstallPageHeader ${CURRENT_LANG} "確認安裝" +LangString ConfirmInstallPageDesc ${CURRENT_LANG} "確認安裝 ${KRITA_PRODUCTNAME} ${KRITA_VERSION_DISPLAY}。" +LangString DesktopIconPageDesc2 ${CURRENT_LANG} "你可以選擇要否在桌面上建立開啟 Krita 的捷徑:" +LangString DesktopIconPageCheckbox ${CURRENT_LANG} "在桌面建立捷徑" +LangString ConfirmInstallPageDesc2 ${CURRENT_LANG} "本安裝程式已準備好在電腦安裝 ${KRITA_PRODUCTNAME} ${KRITA_VERSION_DISPLAY}。如有需要,你可以返回先前的頁面檢查安裝選項。$\r$\n$\r$\n$_CLICK" + +# Misc. message prompts: +LangString MsgRequireWin7 ${CURRENT_LANG} "${KRITA_PRODUCTNAME} ${KRITA_VERSION_DISPLAY} 只支援 Windows 7 或以上版本。" +LangString Msg64bitOn32bit ${CURRENT_LANG} "此電腦正在執行32位元版本的 Windows,但本安裝程式提供64位元版本的 Krita 只能在64位元版本的 Windows 上使用。請到 https://krita.org/ 下載32位元版本的 Krita。" +LangString Msg32bitOn64bit ${CURRENT_LANG} "你正嘗試在64位元版本的 Windows 上安裝32位元版本的 Krita。本安裝程式建議你安裝效能更高的64位元版本的 Krita。$\n請到 https://krita.org/ 下載64位元版本的 Krita。$\n$\n你仍要安裝32位元版本的 Krita嗎?" +# These prompts are used for when Krita 2.9 or earlier, or the 3.0 alpha 1 MSI version is installed. +LangString MsgAncientVerMustBeRemoved ${CURRENT_LANG} "發現電腦上安裝有舊版本的 Krita。本安裝程式會先嘗試將其解除安裝。$\n你確定要繼續嗎?" +LangString MsgKrita3alpha1RemoveFailed ${CURRENT_LANG} "無法成功解除安裝 Krita 3.0 Alpha 1。" +LangString MsgKrita2msi32bitRemoveFailed ${CURRENT_LANG} "無法成功解除安裝舊版 Krita (32-bit)。" +LangString MsgKrita2msi64bitRemoveFailed ${CURRENT_LANG} "無法成功解除安裝舊版 Krita (64-bit)。" +# +LangString MsgKritaSameVerReinstall ${CURRENT_LANG} "發現電腦上已安裝有 ${KRITA_PRODUCTNAME} ${KRITA_VERSION_DISPLAY}。$\n本安裝程式會重新安裝此版本的 Krita。" +LangString MsgKrita3264bitSwap ${CURRENT_LANG} "發現電腦上已安裝有$KritaNsisBitness位元版本的 Krita ($KritaNsisVersion)。本安裝程式會以${KRITA_INSTALLER_BITNESS}位元版本的 Krita ${KRITA_VERSION_DISPLAY} 將其取代。" +LangString MsgKritaNewerAlreadyInstalled ${CURRENT_LANG} "發現電腦上已安裝有$KritaNsisBitness位元版本的 Krita ($KritaNsisVersion)。如需安裝較舊版本的 Krita (${KRITA_VERSION_DISPLAY}),請先自行解除安裝現有版本的 Krita。" +LangString MsgKritaRunning ${CURRENT_LANG} "發現 Krita 正在執行中。使用本安裝程式前請先退出 Krita。" +LangString MsgUninstallKritaRunning ${CURRENT_LANG} "發現 Krita 正在執行中。解除安裝前請先退出 Krita。" + +!undef CURRENT_LANG diff --git a/packaging/windows/msix/.gitignore b/packaging/windows/msix/.gitignore new file mode 100644 index 0000000000..89f9ac04aa --- /dev/null +++ b/packaging/windows/msix/.gitignore @@ -0,0 +1 @@ +out/ diff --git a/packaging/windows/msix/01_gen_resources_pri.cmd b/packaging/windows/msix/01_gen_resources_pri.cmd new file mode 100644 index 0000000000..84698a058d --- /dev/null +++ b/packaging/windows/msix/01_gen_resources_pri.cmd @@ -0,0 +1,32 @@ +@echo off + +setlocal enableextensions enabledelayedexpansion + +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%" + for /f "delims=" %%a in ('dir /a:d /b "bin\10.*"') do ( + if exist "bin\%%a\x64\makeappx.exe" ( + set "MAKEPRI=%WindowsSdkDir%\bin\%%a\x64\makepri.exe" + ) + ) + if "%MAKEPRI%" == "" if exist "bin\x64\makepri.exe" ( + set "MAKEPRI=%WindowsSdkDir%\bin\x64\makepri.exe" + ) + popd +) +if "%MAKEPRI%" == "" ( + echo ERROR: makepri not found 1>&2 + exit /b 1 +) + +mkdir out + +"%MAKEPRI%" new /pr "%~dp0pkg" /mn "%~dp0manifest.xml" /cf "%~dp0priconfig.xml" /o /of "%~dp0out\resources.pri" +if errorlevel 1 ( + echo ERROR running makepri 1>&2 + exit /B 1 +) + +echo Done. diff --git a/packaging/windows/msix/02_gen_mapping.cmd b/packaging/windows/msix/02_gen_mapping.cmd new file mode 100644 index 0000000000..d17e9a8ef7 --- /dev/null +++ b/packaging/windows/msix/02_gen_mapping.cmd @@ -0,0 +1,98 @@ +@echo off + +:: Please edit this to point to the extracted installer files. +set "KRITA_DIR=D:\dev\krita\msix\unpacked\krita-x64-4.3.0-beta2-setup" + + + +set "ASSETS_DIR=%~dp0pkg\Assets" +set "MAPPING_OUT=%~dp0out\mapping.txt" + +setlocal enabledelayedexpansion +goto begin + + +:: Subroutines + +:get_rel_path out_variable file_path base_path +setlocal enableextensions +set FULL_PATH=%~f2 +set FULL_BASE=%~f3 +for /L %%n in (1 1 512) do if "!FULL_BASE:~%%n,1!" neq "" set /a "FULL_BASE_len=%%n+1" +set /a "FULL_BASE_len_plus_one=%FULL_BASE_len%+1" +if not exist "%FULL_PATH%" ( + set REL_PATH= +) else ( + if not exist "%FULL_BASE%" ( + set REL_PATH= + ) else ( + if "!FULL_PATH:~0,%FULL_BASE_len%!" == "%FULL_BASE%" ( + set REL_PATH=!FULL_PATH:~%FULL_BASE_len_plus_one%! + ) else ( + set REl_PATH= + ) + ) +) +endlocal & set "%1=%REL_PATH%" +goto :EOF + + +:get_temp_file out_variable +setlocal enableextensions +set "uniqueFileName=%tmp%\bat~%RANDOM%.tmp" +if exist "%uniqueFileName%" call :get_temp_file uniqueFileName +endlocal & set "%1=%uniqueFileName%" +goto :EOF + + +:: ---------------------------- +:begin + + +rem Sanity checks: +if not exist "%KRITA_DIR%\bin\krita.exe" ( + echo ERROR: KRITA_DIR is set to "%KRITA_DIR%" but "%KRITA_DIR%\bin\krita.exe" does not exist! 1>&2 + exit /B 1 +) +if not exist "%KRITA_DIR%\shellex\kritashellex64.dll" ( + echo ERROR: "%KRITA_DIR%\shellex\kritashellex64.dll" does not exist! 1>&2 + exit /B 1 +) +if exist "%KRITA_DIR%\$PLUGINSDIR" ( + echo ERROR: You did not remove "$PLUGINSDIR". + exit /B 1 +) +if exist "%KRITA_DIR%\uninstall.exe.nsis" ( + echo ERROR: You did not remove "uninstall.exe.nsis". + exit /B 1 +) + + +call :get_temp_file OUT_TEMP +echo Writing list to temporary file "%OUT_TEMP%" + +echo [Files] > %OUT_TEMP% +echo "%~dp0manifest.xml" "AppxManifest.xml" >> %OUT_TEMP% +echo "%~dp0out\resources.pri" "Resources.pri" >> %OUT_TEMP% + +rem Krita application files: +pushd "%KRITA_DIR%" +for /r %%a in (*) do ( + call :get_rel_path rel "%%a" "%CD%" + echo "%%a" "krita\!rel!" >> %OUT_TEMP% +) +popd + +rem Assets: +pushd "%ASSETS_DIR%" +for /r %%a in (*) do ( + call :get_rel_path rel "%%a" "%CD%" + echo "%%a" "Assets\!rel!" >> %OUT_TEMP% +) +popd + +copy "%OUT_TEMP%" "%MAPPING_OUT%" +del "%OUT_TEMP%" + +echo Written mapping file to "%MAPPING_OUT%" +echo Done. diff --git a/packaging/windows/msix/03_build_msix.cmd b/packaging/windows/msix/03_build_msix.cmd new file mode 100644 index 0000000000..b6796965cc --- /dev/null +++ b/packaging/windows/msix/03_build_msix.cmd @@ -0,0 +1,72 @@ +@echo off + +setlocal enableextensions enabledelayedexpansion +goto begin + +(this is a comment block...) + +For reference, the MSIX Packaging tool uses the following command arguments: + pack /v /o /l /nv /nfv /f "%UserProfile%\AppData\Local\Packages\Microsoft.MsixPackagingTool_8wekyb3d8bbwe\LocalState\DiagOutputDir\Logs\wox1ifkc.h0i.txt" /p "D:\dev\krita\msix\Krita-testing_4.3.0.0_x64__svcxxs8w6n55m.msix" + +The arguments stands for: + pack: Creates a package. + /v: Enable verbose logging output to the console. + /o: Overwrites the output file if it exists. If you don't specify this option or the /no option, the user is asked whether they want to overwrite the file. + /l: Used for localized packages. The default validation trips on localized packages. This options disables only that specific validation, without requiring that all validation be disabled. + /nv: Skips semantic validation. If you don't specify this option, the tool performs a full validation of the package. + /nfv: ??? + /f : Specifies the mapping file. + /p : Specifies the app package or bundle. + + +:begin + + +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%" + for /f "delims=" %%a in ('dir /a:d /b "bin\10.*"') do ( + if exist "bin\%%a\x64\makeappx.exe" ( + set "MAKEAPPX=%WindowsSdkDir%\bin\%%a\x64\makeappx.exe" + ) + if exist "bin\%%a\x64\signtool.exe" ( + set "SIGNTOOL=%WindowsSdkDir%\bin\%%a\x64\signtool.exe" + ) + ) + if "%MAKEAPPX%" == "" if exist "bin\x64\makeappx.exe" ( + set "MAKEAPPX=%WindowsSdkDir%\bin\x64\makeappx.exe" + ) + if "%SIGNTOOL%" == "" if exist "bin\x64\signtool.exe" ( + set "SIGNTOOL=%WindowsSdkDir%\bin\x64\signtool.exe" + ) + popd +) +if "%MAKEAPPX%" == "" ( + echo ERROR: makeappx not found 1>&2 + exit /b 1 +) +if "%SIGNTOOL%" == "" ( + echo ERROR: signtool not found 1>&2 + exit /b 1 +) + + +"%MAKEAPPX%" pack /v /f "%~dp0out\mapping.txt" /p "%~dp0out\out.msix" /o +if errorlevel 1 ( + echo ERROR running makeappx 1>&2 + exit /B 1 +) + +echo. +echo MSIX generated. Signing... + +"%SIGNTOOL%" sign %SIGNTOOL_SIGN_FLAGS% /fd sha256 "%~dp0out\out.msix" +if errorlevel 1 ( + echo ERROR running signtool 1>&2 + echo If you need to specify a PFX keyfile and its password, run: + echo set SIGNTOOL_SIGN_FLAGS=/f "absolute_path_to_keyfile.pfx" /p password + exit /B 1 +) + +echo Done. diff --git a/packaging/windows/msix/README.md b/packaging/windows/msix/README.md new file mode 100644 index 0000000000..5af9de84a8 --- /dev/null +++ b/packaging/windows/msix/README.md @@ -0,0 +1,81 @@ +(Originally written by Alvin on 2020-06-02 ~ 2020-06-03) + +Documentation sources: + +- https://docs.microsoft.com/en-us/windows/msix/desktop/desktop-to-uwp-manual-conversion +- https://docs.microsoft.com/en-us/uwp/schemas/appxpackage/appx-package-manifest +- https://docs.microsoft.com/en-us/previous-versions/windows/dn934795%28v%3dwin.10%29 +- https://docs.microsoft.com/en-us/windows/uwp/design/style/app-icons-and-logos + + +Assets +--- + +Most of the assets starts out extracted from the package that the MSIX +Packaging Tool generated, with `fileicon.png` being the exception. Since the +assets are target-based, we need to use `makepri` to generate a `pri` file. + +The assets are placed under the `Assets` dir when inside the package, so this +dir structure needs to be replicated. Also, in order to not mess up `makepri`, +the `Assets` dir needs to be placed in its own dir with no other files in it. +I chose to name it `pkg`. + +I generated a `priconfig.xml` file using this command: + +``` +makepri.exe createconfig /cf priconfig.xml /dq en-us +``` + +... however I also commented out the `packaging` section. + +Read more about the app icons on https://docs.microsoft.com/en-us/windows/uwp/design/style/app-icons-and-logos + +TODO: On editing the assets... + + +Manifest +--- + +The manifest is constructed by referencing the official documentation and the +two manifests generated by DAC (as in Krita 4.2.8 store version) and MSIX +Packaging Tool. A huge portion is manually rewritten though. + +There are several issues that couldn't be resolved: + +- The property handler just doesn't work so it has been commented out. +- Explorer doesn't add the drop shadow to the thumbnail. In Win32 there is a + registry value to change it but there is no such option in the manifest. +- Explorer doesn't load the preview image on the preview pane. In Win32 the + image provided by the thumbnail handler is automatically used, but not for + Appx for some reason? + + +Building the MSIX +--- + +Needs: + +- Latest Windows SDK (though I've tested with 10.0.17763.0 and10.0.18362.0) +- Krita x64 NSIS installer EXE file +- 7-Zip +- The cert to sign the package with + +Steps: + +1. Extract the EXE installer to somewhere **outside of the source tree** using + 7-Zip (don't run the installer!) +2. Remove `$PLUGINSDIR` and `uninstall.exe.nsis` inside +3. Edit `02_gen_mapping.cmd` and point `KRITA_DIR` to it +4. Make necessary amendments to `manifest.xml` +5. Start a command prompt, then run `01_gen_resources_pri`, `02_gen_mapping` + and `03_build_msix` in order (don't run them from Explorer or you may miss + any error messages) + - If you need to specify the keyfile and password used to sign the MSIX + file, execute the following command before running the final script: + ``` + set SIGNTOOL_SIGN_FLAGS=/f "absolute_path_to_keyfile.pfx" /p password + ``` +6. Install and test `out\out.msix` + +TODO: Technically we can build the MSIX on the binary factory together with the +rest of the build (excluding the MSIX signing step)... diff --git a/packaging/windows/msix/manifest.xml b/packaging/windows/msix/manifest.xml new file mode 100644 index 0000000000..26fd4c586b --- /dev/null +++ b/packaging/windows/msix/manifest.xml @@ -0,0 +1,112 @@ + + + + + + Krita + Krita + Digital Painting, Creative Freedom! + Assets\StoreLogo.png + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Krita Image Document + Assets\fileicon.png + + .kra + + + + + + + + OpenRaster Image Document + Assets\fileicon.png + + .ora + + + + + + + + Krita Brush Preset File + Assets\fileicon.png + + .kpp + + + + + + + + Image File + Assets\fileicon.png + + .bmp + .exr + .gif + .jpeg + .jpg + .png + .psd + .tif + .tiff + .xcf + + + + + + + diff --git a/packaging/windows/msix/pkg/Assets/Square150x150Logo.scale-100.png b/packaging/windows/msix/pkg/Assets/Square150x150Logo.scale-100.png new file mode 100644 index 0000000000..0f60c0ce4f Binary files /dev/null and b/packaging/windows/msix/pkg/Assets/Square150x150Logo.scale-100.png differ diff --git a/packaging/windows/msix/pkg/Assets/Square150x150Logo.scale-125.png b/packaging/windows/msix/pkg/Assets/Square150x150Logo.scale-125.png new file mode 100644 index 0000000000..f34c3d1fdd Binary files /dev/null and b/packaging/windows/msix/pkg/Assets/Square150x150Logo.scale-125.png differ diff --git a/packaging/windows/msix/pkg/Assets/Square150x150Logo.scale-150.png b/packaging/windows/msix/pkg/Assets/Square150x150Logo.scale-150.png new file mode 100644 index 0000000000..9f39b5be5f Binary files /dev/null and b/packaging/windows/msix/pkg/Assets/Square150x150Logo.scale-150.png differ diff --git a/packaging/windows/msix/pkg/Assets/Square150x150Logo.scale-200.png b/packaging/windows/msix/pkg/Assets/Square150x150Logo.scale-200.png new file mode 100644 index 0000000000..a79bd60a08 Binary files /dev/null and b/packaging/windows/msix/pkg/Assets/Square150x150Logo.scale-200.png differ diff --git a/packaging/windows/msix/pkg/Assets/Square150x150Logo.scale-400.png b/packaging/windows/msix/pkg/Assets/Square150x150Logo.scale-400.png new file mode 100644 index 0000000000..1c63069584 Binary files /dev/null and b/packaging/windows/msix/pkg/Assets/Square150x150Logo.scale-400.png differ diff --git a/packaging/windows/msix/pkg/Assets/Square310x310Logo.scale-100.png b/packaging/windows/msix/pkg/Assets/Square310x310Logo.scale-100.png new file mode 100644 index 0000000000..29516ed60a Binary files /dev/null and b/packaging/windows/msix/pkg/Assets/Square310x310Logo.scale-100.png differ diff --git a/packaging/windows/msix/pkg/Assets/Square310x310Logo.scale-125.png b/packaging/windows/msix/pkg/Assets/Square310x310Logo.scale-125.png new file mode 100644 index 0000000000..5259c21694 Binary files /dev/null and b/packaging/windows/msix/pkg/Assets/Square310x310Logo.scale-125.png differ diff --git a/packaging/windows/msix/pkg/Assets/Square310x310Logo.scale-150.png b/packaging/windows/msix/pkg/Assets/Square310x310Logo.scale-150.png new file mode 100644 index 0000000000..848a19a492 Binary files /dev/null and b/packaging/windows/msix/pkg/Assets/Square310x310Logo.scale-150.png differ diff --git a/packaging/windows/msix/pkg/Assets/Square310x310Logo.scale-200.png b/packaging/windows/msix/pkg/Assets/Square310x310Logo.scale-200.png new file mode 100644 index 0000000000..48d6590167 Binary files /dev/null and b/packaging/windows/msix/pkg/Assets/Square310x310Logo.scale-200.png differ diff --git a/packaging/windows/msix/pkg/Assets/Square310x310Logo.scale-400.png b/packaging/windows/msix/pkg/Assets/Square310x310Logo.scale-400.png new file mode 100644 index 0000000000..1a1087aa32 Binary files /dev/null and b/packaging/windows/msix/pkg/Assets/Square310x310Logo.scale-400.png differ diff --git a/packaging/windows/msix/pkg/Assets/Square44x44Logo.scale-100.png b/packaging/windows/msix/pkg/Assets/Square44x44Logo.scale-100.png new file mode 100644 index 0000000000..5de3fa53d8 Binary files /dev/null and b/packaging/windows/msix/pkg/Assets/Square44x44Logo.scale-100.png differ diff --git a/packaging/windows/msix/pkg/Assets/Square44x44Logo.scale-125.png b/packaging/windows/msix/pkg/Assets/Square44x44Logo.scale-125.png new file mode 100644 index 0000000000..0ef80529ff Binary files /dev/null and b/packaging/windows/msix/pkg/Assets/Square44x44Logo.scale-125.png differ diff --git a/packaging/windows/msix/pkg/Assets/Square44x44Logo.scale-150.png b/packaging/windows/msix/pkg/Assets/Square44x44Logo.scale-150.png new file mode 100644 index 0000000000..54be7edb0d Binary files /dev/null and b/packaging/windows/msix/pkg/Assets/Square44x44Logo.scale-150.png differ diff --git a/packaging/windows/msix/pkg/Assets/Square44x44Logo.scale-200.png b/packaging/windows/msix/pkg/Assets/Square44x44Logo.scale-200.png new file mode 100644 index 0000000000..3f55989d43 Binary files /dev/null and b/packaging/windows/msix/pkg/Assets/Square44x44Logo.scale-200.png differ diff --git a/packaging/windows/msix/pkg/Assets/Square44x44Logo.scale-400.png b/packaging/windows/msix/pkg/Assets/Square44x44Logo.scale-400.png new file mode 100644 index 0000000000..c825e44638 Binary files /dev/null and b/packaging/windows/msix/pkg/Assets/Square44x44Logo.scale-400.png differ diff --git a/packaging/windows/msix/pkg/Assets/Square44x44Logo.targetsize-16.png b/packaging/windows/msix/pkg/Assets/Square44x44Logo.targetsize-16.png new file mode 100644 index 0000000000..3c5d5a8b72 Binary files /dev/null and b/packaging/windows/msix/pkg/Assets/Square44x44Logo.targetsize-16.png differ diff --git a/packaging/windows/msix/pkg/Assets/Square44x44Logo.targetsize-16_altform-unplated.png b/packaging/windows/msix/pkg/Assets/Square44x44Logo.targetsize-16_altform-unplated.png new file mode 100644 index 0000000000..e536f776de Binary files /dev/null and b/packaging/windows/msix/pkg/Assets/Square44x44Logo.targetsize-16_altform-unplated.png differ diff --git a/packaging/windows/msix/pkg/Assets/Square44x44Logo.targetsize-24.png b/packaging/windows/msix/pkg/Assets/Square44x44Logo.targetsize-24.png new file mode 100644 index 0000000000..1bb4ddbab6 Binary files /dev/null and b/packaging/windows/msix/pkg/Assets/Square44x44Logo.targetsize-24.png differ diff --git a/packaging/windows/msix/pkg/Assets/Square44x44Logo.targetsize-24_altform-unplated.png b/packaging/windows/msix/pkg/Assets/Square44x44Logo.targetsize-24_altform-unplated.png new file mode 100644 index 0000000000..3eaa4d5b66 Binary files /dev/null and b/packaging/windows/msix/pkg/Assets/Square44x44Logo.targetsize-24_altform-unplated.png differ diff --git a/packaging/windows/msix/pkg/Assets/Square44x44Logo.targetsize-256.png b/packaging/windows/msix/pkg/Assets/Square44x44Logo.targetsize-256.png new file mode 100644 index 0000000000..f98cebc8fc Binary files /dev/null and b/packaging/windows/msix/pkg/Assets/Square44x44Logo.targetsize-256.png differ diff --git a/packaging/windows/msix/pkg/Assets/Square44x44Logo.targetsize-256_altform-unplated.png b/packaging/windows/msix/pkg/Assets/Square44x44Logo.targetsize-256_altform-unplated.png new file mode 100644 index 0000000000..700573ff46 Binary files /dev/null and b/packaging/windows/msix/pkg/Assets/Square44x44Logo.targetsize-256_altform-unplated.png differ diff --git a/packaging/windows/msix/pkg/Assets/Square44x44Logo.targetsize-32.png b/packaging/windows/msix/pkg/Assets/Square44x44Logo.targetsize-32.png new file mode 100644 index 0000000000..724b5ac521 Binary files /dev/null and b/packaging/windows/msix/pkg/Assets/Square44x44Logo.targetsize-32.png differ diff --git a/packaging/windows/msix/pkg/Assets/Square44x44Logo.targetsize-32_altform-unplated.png b/packaging/windows/msix/pkg/Assets/Square44x44Logo.targetsize-32_altform-unplated.png new file mode 100644 index 0000000000..17bbbc1412 Binary files /dev/null and b/packaging/windows/msix/pkg/Assets/Square44x44Logo.targetsize-32_altform-unplated.png differ diff --git a/packaging/windows/msix/pkg/Assets/Square44x44Logo.targetsize-48.png b/packaging/windows/msix/pkg/Assets/Square44x44Logo.targetsize-48.png new file mode 100644 index 0000000000..69533bc71c Binary files /dev/null and b/packaging/windows/msix/pkg/Assets/Square44x44Logo.targetsize-48.png differ diff --git a/packaging/windows/msix/pkg/Assets/Square44x44Logo.targetsize-48_altform-unplated.png b/packaging/windows/msix/pkg/Assets/Square44x44Logo.targetsize-48_altform-unplated.png new file mode 100644 index 0000000000..c5ee239b92 Binary files /dev/null and b/packaging/windows/msix/pkg/Assets/Square44x44Logo.targetsize-48_altform-unplated.png differ diff --git a/packaging/windows/msix/pkg/Assets/Square71x71Logo.scale-100.png b/packaging/windows/msix/pkg/Assets/Square71x71Logo.scale-100.png new file mode 100644 index 0000000000..d6ae596285 Binary files /dev/null and b/packaging/windows/msix/pkg/Assets/Square71x71Logo.scale-100.png differ diff --git a/packaging/windows/msix/pkg/Assets/Square71x71Logo.scale-125.png b/packaging/windows/msix/pkg/Assets/Square71x71Logo.scale-125.png new file mode 100644 index 0000000000..37e74419a9 Binary files /dev/null and b/packaging/windows/msix/pkg/Assets/Square71x71Logo.scale-125.png differ diff --git a/packaging/windows/msix/pkg/Assets/Square71x71Logo.scale-150.png b/packaging/windows/msix/pkg/Assets/Square71x71Logo.scale-150.png new file mode 100644 index 0000000000..9536c2e958 Binary files /dev/null and b/packaging/windows/msix/pkg/Assets/Square71x71Logo.scale-150.png differ diff --git a/packaging/windows/msix/pkg/Assets/Square71x71Logo.scale-200.png b/packaging/windows/msix/pkg/Assets/Square71x71Logo.scale-200.png new file mode 100644 index 0000000000..81e0b733be Binary files /dev/null and b/packaging/windows/msix/pkg/Assets/Square71x71Logo.scale-200.png differ diff --git a/packaging/windows/msix/pkg/Assets/Square71x71Logo.scale-400.png b/packaging/windows/msix/pkg/Assets/Square71x71Logo.scale-400.png new file mode 100644 index 0000000000..cef1b08ca6 Binary files /dev/null and b/packaging/windows/msix/pkg/Assets/Square71x71Logo.scale-400.png differ diff --git a/packaging/windows/msix/pkg/Assets/StoreLogo.scale-100.png b/packaging/windows/msix/pkg/Assets/StoreLogo.scale-100.png new file mode 100644 index 0000000000..0ca4d66672 Binary files /dev/null and b/packaging/windows/msix/pkg/Assets/StoreLogo.scale-100.png differ diff --git a/packaging/windows/msix/pkg/Assets/StoreLogo.scale-125.png b/packaging/windows/msix/pkg/Assets/StoreLogo.scale-125.png new file mode 100644 index 0000000000..2129fbd8a2 Binary files /dev/null and b/packaging/windows/msix/pkg/Assets/StoreLogo.scale-125.png differ diff --git a/packaging/windows/msix/pkg/Assets/StoreLogo.scale-150.png b/packaging/windows/msix/pkg/Assets/StoreLogo.scale-150.png new file mode 100644 index 0000000000..72478309b7 Binary files /dev/null and b/packaging/windows/msix/pkg/Assets/StoreLogo.scale-150.png differ diff --git a/packaging/windows/msix/pkg/Assets/StoreLogo.scale-200.png b/packaging/windows/msix/pkg/Assets/StoreLogo.scale-200.png new file mode 100644 index 0000000000..3c0c5116ab Binary files /dev/null and b/packaging/windows/msix/pkg/Assets/StoreLogo.scale-200.png differ diff --git a/packaging/windows/msix/pkg/Assets/StoreLogo.scale-400.png b/packaging/windows/msix/pkg/Assets/StoreLogo.scale-400.png new file mode 100644 index 0000000000..d9b2babec4 Binary files /dev/null and b/packaging/windows/msix/pkg/Assets/StoreLogo.scale-400.png differ diff --git a/packaging/windows/msix/pkg/Assets/Wide310x150Logo.scale-100.png b/packaging/windows/msix/pkg/Assets/Wide310x150Logo.scale-100.png new file mode 100644 index 0000000000..88976fc158 Binary files /dev/null and b/packaging/windows/msix/pkg/Assets/Wide310x150Logo.scale-100.png differ diff --git a/packaging/windows/msix/pkg/Assets/Wide310x150Logo.scale-125.png b/packaging/windows/msix/pkg/Assets/Wide310x150Logo.scale-125.png new file mode 100644 index 0000000000..ba9f0c0680 Binary files /dev/null and b/packaging/windows/msix/pkg/Assets/Wide310x150Logo.scale-125.png differ diff --git a/packaging/windows/msix/pkg/Assets/Wide310x150Logo.scale-150.png b/packaging/windows/msix/pkg/Assets/Wide310x150Logo.scale-150.png new file mode 100644 index 0000000000..00f1c271ce Binary files /dev/null and b/packaging/windows/msix/pkg/Assets/Wide310x150Logo.scale-150.png differ diff --git a/packaging/windows/msix/pkg/Assets/Wide310x150Logo.scale-200.png b/packaging/windows/msix/pkg/Assets/Wide310x150Logo.scale-200.png new file mode 100644 index 0000000000..9ae2787341 Binary files /dev/null and b/packaging/windows/msix/pkg/Assets/Wide310x150Logo.scale-200.png differ diff --git a/packaging/windows/msix/pkg/Assets/Wide310x150Logo.scale-400.png b/packaging/windows/msix/pkg/Assets/Wide310x150Logo.scale-400.png new file mode 100644 index 0000000000..e74281f653 Binary files /dev/null and b/packaging/windows/msix/pkg/Assets/Wide310x150Logo.scale-400.png differ diff --git a/packaging/windows/msix/pkg/Assets/fileicon.targetsize-16.png b/packaging/windows/msix/pkg/Assets/fileicon.targetsize-16.png new file mode 100644 index 0000000000..b67fe67374 Binary files /dev/null and b/packaging/windows/msix/pkg/Assets/fileicon.targetsize-16.png differ diff --git a/packaging/windows/msix/pkg/Assets/fileicon.targetsize-24.png b/packaging/windows/msix/pkg/Assets/fileicon.targetsize-24.png new file mode 100644 index 0000000000..56e966eeba Binary files /dev/null and b/packaging/windows/msix/pkg/Assets/fileicon.targetsize-24.png differ diff --git a/packaging/windows/msix/pkg/Assets/fileicon.targetsize-256.png b/packaging/windows/msix/pkg/Assets/fileicon.targetsize-256.png new file mode 100644 index 0000000000..4da535c930 Binary files /dev/null and b/packaging/windows/msix/pkg/Assets/fileicon.targetsize-256.png differ diff --git a/packaging/windows/msix/pkg/Assets/fileicon.targetsize-32.png b/packaging/windows/msix/pkg/Assets/fileicon.targetsize-32.png new file mode 100644 index 0000000000..f3a158e903 Binary files /dev/null and b/packaging/windows/msix/pkg/Assets/fileicon.targetsize-32.png differ diff --git a/packaging/windows/msix/pkg/Assets/fileicon.targetsize-48.png b/packaging/windows/msix/pkg/Assets/fileicon.targetsize-48.png new file mode 100644 index 0000000000..36cde3244c Binary files /dev/null and b/packaging/windows/msix/pkg/Assets/fileicon.targetsize-48.png differ diff --git a/packaging/windows/msix/pkg/Assets/fileicon.targetsize-64.png b/packaging/windows/msix/pkg/Assets/fileicon.targetsize-64.png new file mode 100644 index 0000000000..1b025699d5 Binary files /dev/null and b/packaging/windows/msix/pkg/Assets/fileicon.targetsize-64.png differ diff --git a/packaging/windows/msix/priconfig.xml b/packaging/windows/msix/priconfig.xml new file mode 100644 index 0000000000..d1ec769fe6 --- /dev/null +++ b/packaging/windows/msix/priconfig.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/dockers/animation/timeline_frames_model.cpp b/plugins/dockers/animation/timeline_frames_model.cpp index d672ae59a7..480e4e6b1a 100644 --- a/plugins/dockers/animation/timeline_frames_model.cpp +++ b/plugins/dockers/animation/timeline_frames_model.cpp @@ -1,1027 +1,1021 @@ /* * 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 "timeline_frames_model.h" #include #include #include #include #include #include #include "kis_layer.h" #include "kis_config.h" #include "kis_global.h" #include "kis_debug.h" #include "kis_image.h" #include "kis_image_animation_interface.h" #include "kis_undo_adapter.h" #include "kis_node_dummies_graph.h" #include "kis_dummies_facade_base.h" #include "KisNodeDisplayModeAdapter.h" #include "kis_signal_compressor.h" #include "kis_signal_compressor_with_param.h" #include "kis_keyframe_channel.h" #include "kundo2command.h" #include "kis_post_execution_undo_adapter.h" #include #include #include "kis_animation_utils.h" #include "timeline_color_scheme.h" #include "kis_node_model.h" #include "kis_projection_leaf.h" #include "kis_time_range.h" #include "kis_node_view_color_scheme.h" #include "krita_utils.h" #include "KisPart.h" #include #include "KisDocument.h" #include "KisViewManager.h" #include "kis_processing_applicator.h" #include #include "kis_node_uuid_info.h" struct TimelineFramesModel::Private { Private() : activeLayerIndex(0), dummiesFacade(0), needFinishInsertRows(false), needFinishRemoveRows(false), updateTimer(200, KisSignalCompressor::FIRST_INACTIVE), parentOfRemovedNode(0) {} int activeLayerIndex; QPointer dummiesFacade; KisImageWSP image; bool needFinishInsertRows; bool needFinishRemoveRows; QList updateQueue; KisSignalCompressor updateTimer; KisNodeDummy* parentOfRemovedNode; QScopedPointer converter; QScopedPointer nodeInterface; QPersistentModelIndex lastClickedIndex; QVariant layerName(int row) const { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return QVariant(); return dummy->node()->name(); } bool layerEditable(int row) const { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return true; return dummy->node()->visible() && !dummy->node()->userLocked(); } bool frameExists(int row, int column) const { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return false; KisKeyframeChannel *primaryChannel = dummy->node()->getKeyframeChannel(KisKeyframeChannel::Content.id()); return (primaryChannel && primaryChannel->keyframeAt(column)); } bool frameHasContent(int row, int column) { KisNodeDummy *dummy = converter->dummyFromRow(row); KisKeyframeChannel *primaryChannel = dummy->node()->getKeyframeChannel(KisKeyframeChannel::Content.id()); if (!primaryChannel) return false; // first check if we are a key frame KisKeyframeSP frame = primaryChannel->activeKeyframeAt(column); if (!frame) return false; return frame->hasContent(); } bool specialKeyframeExists(int row, int column) { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return false; Q_FOREACH(KisKeyframeChannel *channel, dummy->node()->keyframeChannels()) { if (channel->id() != KisKeyframeChannel::Content.id() && channel->keyframeAt(column)) { return true; } } return false; } int frameColorLabel(int row, int column) { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return -1; KisKeyframeChannel *primaryChannel = dummy->node()->getKeyframeChannel(KisKeyframeChannel::Content.id()); if (!primaryChannel) return -1; KisKeyframeSP frame = primaryChannel->activeKeyframeAt(column); if (!frame) return -1; return frame->colorLabel(); } void setFrameColorLabel(int row, int column, int color) { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return; KisKeyframeChannel *primaryChannel = dummy->node()->getKeyframeChannel(KisKeyframeChannel::Content.id()); if (!primaryChannel) return; KisKeyframeSP frame = primaryChannel->keyframeAt(column); if (!frame) return; frame->setColorLabel(color); } int layerColorLabel(int row) const { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return -1; return dummy->node()->colorLabelIndex(); } QVariant layerProperties(int row) const { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return QVariant(); PropertyList props = dummy->node()->sectionModelProperties(); return QVariant::fromValue(props); } bool setLayerProperties(int row, PropertyList props) { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return false; nodeInterface->setNodeProperties(dummy->node(), image, props); return true; } bool addKeyframe(int row, int column, bool copy) { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return false; KisNodeSP node = dummy->node(); if (!KisAnimationUtils::supportsContentFrames(node)) return false; KisAnimationUtils::createKeyframeLazy(image, node, KisKeyframeChannel::Content.id(), column, copy); return true; } bool addNewLayer(int row) { Q_UNUSED(row); if (nodeInterface) { KisLayerSP layer = nodeInterface->addPaintLayer(); layer->setPinnedToTimeline(true); } return true; } bool removeLayer(int row) { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return false; if (nodeInterface) { nodeInterface->removeNode(dummy->node()); } return true; } }; TimelineFramesModel::TimelineFramesModel(QObject *parent) : ModelWithExternalNotifications(parent), m_d(new Private) { connect(&m_d->updateTimer, SIGNAL(timeout()), SLOT(processUpdateQueue())); } TimelineFramesModel::~TimelineFramesModel() { } bool TimelineFramesModel::hasConnectionToCanvas() const { return m_d->dummiesFacade; } void TimelineFramesModel::setNodeManipulationInterface(NodeManipulationInterface *iface) { m_d->nodeInterface.reset(iface); } KisNodeSP TimelineFramesModel::nodeAt(QModelIndex index) const { /** * The dummy might not exist because the user could (quickly) change * active layer and the list of the nodes in m_d->converter will change. */ KisNodeDummy *dummy = m_d->converter->dummyFromRow(index.row()); return dummy ? dummy->node() : 0; } QMap TimelineFramesModel::channelsAt(QModelIndex index) const { KisNodeDummy *srcDummy = m_d->converter->dummyFromRow(index.row()); return srcDummy->node()->keyframeChannels(); } void TimelineFramesModel::setDummiesFacade(KisDummiesFacadeBase *dummiesFacade, KisImageSP image, KisNodeDisplayModeAdapter *displayModeAdapter) { KisDummiesFacadeBase *oldDummiesFacade = m_d->dummiesFacade; if (m_d->dummiesFacade && m_d->image) { m_d->image->animationInterface()->disconnect(this); m_d->image->disconnect(this); m_d->dummiesFacade->disconnect(this); } m_d->image = image; KisTimeBasedItemModel::setImage(image); m_d->dummiesFacade = dummiesFacade; m_d->converter.reset(); if (m_d->dummiesFacade) { m_d->converter.reset(new TimelineNodeListKeeper(this, m_d->dummiesFacade, displayModeAdapter)); connect(m_d->dummiesFacade, SIGNAL(sigDummyChanged(KisNodeDummy*)), SLOT(slotDummyChanged(KisNodeDummy*))); connect(m_d->image->animationInterface(), SIGNAL(sigFullClipRangeChanged()), SIGNAL(sigInfiniteTimelineUpdateNeeded())); connect(m_d->image->animationInterface(), SIGNAL(sigAudioChannelChanged()), SIGNAL(sigAudioChannelChanged())); connect(m_d->image->animationInterface(), SIGNAL(sigAudioVolumeChanged()), SIGNAL(sigAudioChannelChanged())); connect(m_d->image, SIGNAL(sigImageModified()), SLOT(slotImageContentChanged())); } if (m_d->dummiesFacade != oldDummiesFacade) { beginResetModel(); endResetModel(); } if (m_d->dummiesFacade) { emit sigInfiniteTimelineUpdateNeeded(); emit sigAudioChannelChanged(); slotCurrentTimeChanged(m_d->image->animationInterface()->currentUITime()); } } void TimelineFramesModel::slotDummyChanged(KisNodeDummy *dummy) { if (!m_d->updateQueue.contains(dummy)) { m_d->updateQueue.append(dummy); } m_d->updateTimer.start(); } void TimelineFramesModel::slotImageContentChanged() { if (m_d->activeLayerIndex < 0) return; KisNodeDummy *dummy = m_d->converter->dummyFromRow(m_d->activeLayerIndex); if (!dummy) return; slotDummyChanged(dummy); } void TimelineFramesModel::processUpdateQueue() { if (!m_d->converter) return; Q_FOREACH (KisNodeDummy *dummy, m_d->updateQueue) { int row = m_d->converter->rowForDummy(dummy); if (row >= 0) { emit headerDataChanged (Qt::Vertical, row, row); emit dataChanged(this->index(row, 0), this->index(row, columnCount() - 1)); } } m_d->updateQueue.clear(); } void TimelineFramesModel::slotCurrentNodeChanged(KisNodeSP node) { if (!node) { m_d->activeLayerIndex = -1; return; } KisNodeDummy *dummy = m_d->dummiesFacade->dummyForNode(node); if (!dummy) { // It's perfectly normal that dummyForNode returns 0; that happens // when views get activated while Krita is closing down. return; } m_d->converter->updateActiveDummy(dummy); const int row = m_d->converter->rowForDummy(dummy); if (row < 0) { qWarning() << "WARNING: TimelineFramesModel::slotCurrentNodeChanged: node not found!"; } if (row >= 0 && m_d->activeLayerIndex != row) { setData(index(row, 0), true, ActiveLayerRole); } } int TimelineFramesModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); if(!m_d->dummiesFacade) return 0; return m_d->converter->rowCount(); } QVariant TimelineFramesModel::data(const QModelIndex &index, int role) const { if(!m_d->dummiesFacade) return QVariant(); switch (role) { case ActiveLayerRole: { return index.row() == m_d->activeLayerIndex; } case FrameEditableRole: { return m_d->layerEditable(index.row()); } case FrameHasContent: { return m_d->frameHasContent(index.row(), index.column()); } case FrameExistsRole: { return m_d->frameExists(index.row(), index.column()); } case SpecialKeyframeExists: { return m_d->specialKeyframeExists(index.row(), index.column()); } case FrameColorLabelIndexRole: { int label = m_d->frameColorLabel(index.row(), index.column()); return label > 0 ? label : QVariant(); } case Qt::DisplayRole: { return m_d->layerName(index.row()); } case Qt::TextAlignmentRole: { return QVariant(Qt::AlignHCenter | Qt::AlignVCenter); } case Qt::UserRole + KisResourceModel::LargeThumbnail: { KisNodeDummy *dummy = m_d->converter->dummyFromRow(index.row()); if (!dummy) { return QVariant(); } const int maxSize = 200; - QSize size = dummy->node()->extent().size(); - size.scale(maxSize, maxSize, Qt::KeepAspectRatio); - if (size.width() == 0 || size.height() == 0) { - // No thumbnail can be shown if there isn't width or height... - return QVariant(); - } - QImage image(dummy->node()->createThumbnailForFrame(size.width(), size.height(), index.column())); + QImage image(dummy->node()->createThumbnailForFrame(maxSize, maxSize, index.column(), Qt::KeepAspectRatio)); return image; } } return ModelWithExternalNotifications::data(index, role); } bool TimelineFramesModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (!index.isValid() || !m_d->dummiesFacade) return false; switch (role) { case ActiveLayerRole: { if (value.toBool() && index.row() != m_d->activeLayerIndex) { int prevLayer = m_d->activeLayerIndex; m_d->activeLayerIndex = index.row(); emit dataChanged(this->index(prevLayer, 0), this->index(prevLayer, columnCount() - 1)); emit dataChanged(this->index(m_d->activeLayerIndex, 0), this->index(m_d->activeLayerIndex, columnCount() - 1)); emit headerDataChanged(Qt::Vertical, prevLayer, prevLayer); emit headerDataChanged(Qt::Vertical, m_d->activeLayerIndex, m_d->activeLayerIndex); KisNodeDummy *dummy = m_d->converter->dummyFromRow(m_d->activeLayerIndex); KIS_ASSERT_RECOVER(dummy) { return true; } emit requestCurrentNodeChanged(dummy->node()); emit sigEnsureRowVisible(m_d->activeLayerIndex); } break; } case FrameColorLabelIndexRole: { m_d->setFrameColorLabel(index.row(), index.column(), value.toInt()); } break; } return ModelWithExternalNotifications::setData(index, value, role); } QVariant TimelineFramesModel::headerData(int section, Qt::Orientation orientation, int role) const { if(!m_d->dummiesFacade) return QVariant(); if (orientation == Qt::Vertical) { switch (role) { case ActiveLayerRole: return section == m_d->activeLayerIndex; case Qt::DisplayRole: { QVariant value = headerData(section, orientation, Qt::ToolTipRole); if (!value.isValid()) return value; QString name = value.toString(); const int maxNameSize = 13; if (name.size() > maxNameSize) { name = QString("%1...").arg(name.left(maxNameSize)); } return name; } case Qt::TextColorRole: { // WARNING: this role doesn't work for header views! Use // bold font to show isolated mode instead! return QVariant(); } case Qt::FontRole: { KisNodeDummy *dummy = m_d->converter->dummyFromRow(section); if (!dummy) return QVariant(); KisNodeSP node = dummy->node(); QFont baseFont; if (node->projectionLeaf()->isDroppedNode()) { baseFont.setStrikeOut(true); } else if (m_d->image && m_d->image->isolatedModeRoot() && KisNodeModel::belongsToIsolatedGroup(m_d->image, node, m_d->dummiesFacade)) { baseFont.setBold(true); } return baseFont; } case Qt::ToolTipRole: { return m_d->layerName(section); } case TimelinePropertiesRole: { return QVariant::fromValue(m_d->layerProperties(section)); } case OtherLayersRole: { TimelineNodeListKeeper::OtherLayersList list = m_d->converter->otherLayersList(); return QVariant::fromValue(list); } case LayerUsedInTimelineRole: { KisNodeDummy *dummy = m_d->converter->dummyFromRow(section); if (!dummy) return QVariant(); return dummy->node()->isPinnedToTimeline(); } case Qt::BackgroundRole: { int label = m_d->layerColorLabel(section); if (label > 0) { KisNodeViewColorScheme scm; QColor color = scm.colorLabel(label); QPalette pal = qApp->palette(); color = KritaUtils::blendColors(color, pal.color(QPalette::Button), 0.3); return QBrush(color); } else { return QVariant(); } } } } return ModelWithExternalNotifications::headerData(section, orientation, role); } bool TimelineFramesModel::setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role) { if (!m_d->dummiesFacade) return false; if (orientation == Qt::Vertical) { switch (role) { case ActiveLayerRole: { setData(index(section, 0), value, role); break; } case TimelinePropertiesRole: { TimelineFramesModel::PropertyList props = value.value(); int result = m_d->setLayerProperties(section, props); emit headerDataChanged (Qt::Vertical, section, section); return result; } case LayerUsedInTimelineRole: { KisNodeDummy *dummy = m_d->converter->dummyFromRow(section); if (!dummy) return false; dummy->node()->setPinnedToTimeline(value.toBool()); return true; } } } return ModelWithExternalNotifications::setHeaderData(section, orientation, value, role); } Qt::DropActions TimelineFramesModel::supportedDragActions() const { return Qt::MoveAction | Qt::CopyAction; } Qt::DropActions TimelineFramesModel::supportedDropActions() const { return Qt::MoveAction | Qt::CopyAction; } QStringList TimelineFramesModel::mimeTypes() const { QStringList types; types << QLatin1String("application/x-krita-frame"); return types; } void TimelineFramesModel::setLastClickedIndex(const QModelIndex &index) { m_d->lastClickedIndex = index; } QMimeData* TimelineFramesModel::mimeData(const QModelIndexList &indexes) const { return mimeDataExtended(indexes, m_d->lastClickedIndex, UndefinedPolicy); } QMimeData *TimelineFramesModel::mimeDataExtended(const QModelIndexList &indexes, const QModelIndex &baseIndex, TimelineFramesModel::MimeCopyPolicy copyPolicy) const { QMimeData *data = new QMimeData(); QByteArray encoded; QDataStream stream(&encoded, QIODevice::WriteOnly); const int baseRow = baseIndex.row(); const int baseColumn = baseIndex.column(); const QByteArray uuidDataRoot = m_d->image->root()->uuid().toRfc4122(); stream << int(uuidDataRoot.size()); stream.writeRawData(uuidDataRoot.data(), uuidDataRoot.size()); stream << indexes.size(); stream << baseRow << baseColumn; Q_FOREACH (const QModelIndex &index, indexes) { KisNodeSP node = nodeAt(index); KIS_SAFE_ASSERT_RECOVER(node) { continue; } stream << index.row() - baseRow << index.column() - baseColumn; const QByteArray uuidData = node->uuid().toRfc4122(); stream << int(uuidData.size()); stream.writeRawData(uuidData.data(), uuidData.size()); } stream << int(copyPolicy); data->setData("application/x-krita-frame", encoded); return data; } inline void decodeBaseIndex(QByteArray *encoded, int *row, int *col) { int size_UNUSED = 0; QDataStream stream(encoded, QIODevice::ReadOnly); stream >> size_UNUSED >> *row >> *col; } bool TimelineFramesModel::canDropFrameData(const QMimeData */*data*/, const QModelIndex &index) { if (!index.isValid()) return false; /** * Now we support D&D around any layer, so just return 'true' all * the time. */ return true; } bool TimelineFramesModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) { Q_UNUSED(row); Q_UNUSED(column); return dropMimeDataExtended(data, action, parent); } bool TimelineFramesModel::dropMimeDataExtended(const QMimeData *data, Qt::DropAction action, const QModelIndex &parent, bool *dataMoved) { bool result = false; if ((action != Qt::MoveAction && action != Qt::CopyAction) || !parent.isValid()) return result; QByteArray encoded = data->data("application/x-krita-frame"); QDataStream stream(&encoded, QIODevice::ReadOnly); int uuidLenRoot = 0; stream >> uuidLenRoot; QByteArray uuidDataRoot(uuidLenRoot, '\0'); stream.readRawData(uuidDataRoot.data(), uuidLenRoot); QUuid nodeUuidRoot = QUuid::fromRfc4122(uuidDataRoot); KisPart *partInstance = KisPart::instance(); QList> documents = partInstance->documents(); KisImageSP srcImage = 0; Q_FOREACH(KisDocument *doc, documents) { KisImageSP tmpSrcImage = doc->image(); if (tmpSrcImage->root()->uuid() == nodeUuidRoot) { srcImage = tmpSrcImage; break; } } if (!srcImage) { KisPart *kisPartInstance = KisPart::instance(); kisPartInstance->currentMainwindow()->viewManager()->showFloatingMessage( i18n("Dropped frames are not available in this Krita instance") , QIcon()); return false; } int size, baseRow, baseColumn; stream >> size >> baseRow >> baseColumn; const QPoint offset(parent.column() - baseColumn, parent.row() - baseRow); KisAnimationUtils::FrameMovePairList frameMoves; for (int i = 0; i < size; i++) { int relRow, relColumn; stream >> relRow >> relColumn; const int srcRow = baseRow + relRow; const int srcColumn = baseColumn + relColumn; int uuidLen = 0; stream >> uuidLen; QByteArray uuidData(uuidLen, '\0'); stream.readRawData(uuidData.data(), uuidLen); QUuid nodeUuid = QUuid::fromRfc4122(uuidData); KisNodeSP srcNode; if (!nodeUuid.isNull()) { KisNodeUuidInfo nodeInfo(nodeUuid); srcNode = nodeInfo.findNode(srcImage->root()); } else { QModelIndex index = this->index(srcRow, srcColumn); srcNode = nodeAt(index); } KIS_SAFE_ASSERT_RECOVER(srcNode) { continue; } const QModelIndex dstRowIndex = this->index(srcRow + offset.y(), 0); if (!dstRowIndex.isValid()) continue; KisNodeSP dstNode = nodeAt(dstRowIndex); KIS_SAFE_ASSERT_RECOVER(dstNode) { continue; } Q_FOREACH (KisKeyframeChannel *channel, srcNode->keyframeChannels().values()) { KisAnimationUtils::FrameItem srcItem(srcNode, channel->id(), srcColumn); KisAnimationUtils::FrameItem dstItem(dstNode, channel->id(), srcColumn + offset.x()); frameMoves << std::make_pair(srcItem, dstItem); } } MimeCopyPolicy copyPolicy = UndefinedPolicy; if (!stream.atEnd()) { int value = 0; stream >> value; copyPolicy = MimeCopyPolicy(value); } const bool copyFrames = copyPolicy == UndefinedPolicy ? action == Qt::CopyAction : copyPolicy == CopyFramesPolicy; if (dataMoved) { *dataMoved = !copyFrames; } KUndo2Command *cmd = 0; if (!frameMoves.isEmpty()) { KisImageBarrierLockerWithFeedback locker(m_d->image); cmd = KisAnimationUtils::createMoveKeyframesCommand(frameMoves, copyFrames, false, 0); } if (cmd) { KisProcessingApplicator::runSingleCommandStroke(m_d->image, cmd, KisStrokeJobData::BARRIER, KisStrokeJobData::EXCLUSIVE); } return cmd; } Qt::ItemFlags TimelineFramesModel::flags(const QModelIndex &index) const { Qt::ItemFlags flags = ModelWithExternalNotifications::flags(index); if (!index.isValid()) return flags; if (m_d->frameExists(index.row(), index.column()) || m_d->specialKeyframeExists(index.row(), index.column())) { if (data(index, FrameEditableRole).toBool()) { flags |= Qt::ItemIsDragEnabled; } } /** * Basically we should forbid overrides only if we D&D a single frame * and allow it when we D&D multiple frames. But we cannot distinguish * it here... So allow all the time. */ flags |= Qt::ItemIsDropEnabled; return flags; } bool TimelineFramesModel::insertRows(int row, int count, const QModelIndex &parent) { Q_UNUSED(parent); KIS_ASSERT_RECOVER(count == 1) { return false; } if (row < 0 || row > rowCount()) return false; bool result = m_d->addNewLayer(row); return result; } bool TimelineFramesModel::removeRows(int row, int count, const QModelIndex &parent) { Q_UNUSED(parent); KIS_ASSERT_RECOVER(count == 1) { return false; } if (row < 0 || row >= rowCount()) return false; bool result = m_d->removeLayer(row); return result; } bool TimelineFramesModel::insertOtherLayer(int index, int dstRow) { Q_UNUSED(dstRow); TimelineNodeListKeeper::OtherLayersList list = m_d->converter->otherLayersList(); if (index < 0 || index >= list.size()) return false; list[index].dummy->node()->setPinnedToTimeline(true); dstRow = m_d->converter->rowForDummy(list[index].dummy); setData(this->index(dstRow, 0), true, ActiveLayerRole); return true; } int TimelineFramesModel::activeLayerRow() const { return m_d->activeLayerIndex; } bool TimelineFramesModel::createFrame(const QModelIndex &dstIndex) { if (!dstIndex.isValid()) return false; return m_d->addKeyframe(dstIndex.row(), dstIndex.column(), false); } bool TimelineFramesModel::copyFrame(const QModelIndex &dstIndex) { if (!dstIndex.isValid()) return false; return m_d->addKeyframe(dstIndex.row(), dstIndex.column(), true); } bool TimelineFramesModel::insertFrames(int dstColumn, const QList &dstRows, int count, int timing) { if (dstRows.isEmpty() || count <= 0) return true; timing = qMax(timing, 1); KUndo2Command *parentCommand = new KUndo2Command(kundo2_i18np("Insert frame", "Insert %1 frames", count)); { KisImageBarrierLockerWithFeedback locker(m_d->image); QModelIndexList indexes; Q_FOREACH (int row, dstRows) { for (int column = dstColumn; column < columnCount(); column++) { indexes << index(row, column); } } setLastVisibleFrame(columnCount() + (count * timing) - 1); createOffsetFramesCommand(indexes, QPoint((count * timing), 0), false, false, parentCommand); Q_FOREACH (int row, dstRows) { KisNodeDummy *dummy = m_d->converter->dummyFromRow(row); if (!dummy) continue; KisNodeSP node = dummy->node(); if (!KisAnimationUtils::supportsContentFrames(node)) continue; for (int column = dstColumn; column < dstColumn + (count * timing); column += timing) { KisAnimationUtils::createKeyframeCommand(m_d->image, node, KisKeyframeChannel::Content.id(), column, false, parentCommand); } } const int oldTime = m_d->image->animationInterface()->currentUITime(); const int newTime = dstColumn > oldTime ? dstColumn : dstColumn + (count * timing) - 1; new KisSwitchCurrentTimeCommand(m_d->image->animationInterface(), oldTime, newTime, parentCommand); } KisProcessingApplicator::runSingleCommandStroke(m_d->image, parentCommand, KisStrokeJobData::BARRIER, KisStrokeJobData::EXCLUSIVE); return true; } bool TimelineFramesModel::insertHoldFrames(QModelIndexList selectedIndexes, int count) { if (selectedIndexes.isEmpty() || count == 0) return true; QScopedPointer parentCommand(new KUndo2Command(kundo2_i18np("Insert frame", "Insert %1 frames", count))); { KisImageBarrierLockerWithFeedback locker(m_d->image); QSet uniqueKeyframesInSelection; int minSelectedTime = std::numeric_limits::max(); Q_FOREACH (const QModelIndex &index, selectedIndexes) { KisNodeSP node = nodeAt(index); KIS_SAFE_ASSERT_RECOVER(node) { continue; } KisKeyframeChannel *channel = node->getKeyframeChannel(KisKeyframeChannel::Content.id()); if (!channel) continue; minSelectedTime = qMin(minSelectedTime, index.column()); KisKeyframeSP keyFrame = channel->activeKeyframeAt(index.column()); if (keyFrame) { uniqueKeyframesInSelection.insert(keyFrame); } } QList keyframesToMove; for (auto it = uniqueKeyframesInSelection.begin(); it != uniqueKeyframesInSelection.end(); ++it) { KisKeyframeSP keyframe = *it; KisKeyframeChannel *channel = keyframe->channel(); KisKeyframeSP nextKeyframe = channel->nextKeyframe(keyframe); if (nextKeyframe) { keyframesToMove << nextKeyframe; } } std::sort(keyframesToMove.begin(), keyframesToMove.end(), [] (KisKeyframeSP lhs, KisKeyframeSP rhs) { return lhs->time() > rhs->time(); }); if (keyframesToMove.isEmpty()) return true; const int maxColumn = columnCount(); if (count > 0) { setLastVisibleFrame(columnCount() + count); } Q_FOREACH (KisKeyframeSP keyframe, keyframesToMove) { int plannedFrameMove = count; if (count < 0) { KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(keyframe->time() > 0, false); KisKeyframeSP prevFrame = keyframe->channel()->previousKeyframe(keyframe); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(prevFrame, false); plannedFrameMove = qMax(count, prevFrame->time() - keyframe->time() + 1); minSelectedTime = qMin(minSelectedTime, prevFrame->time()); } KisNodeDummy *dummy = m_d->dummiesFacade->dummyForNode(keyframe->channel()->node()); KIS_SAFE_ASSERT_RECOVER(dummy) { continue; } const int row = m_d->converter->rowForDummy(dummy); KIS_SAFE_ASSERT_RECOVER(row >= 0) { continue; } QModelIndexList indexes; for (int column = keyframe->time(); column < maxColumn; column++) { indexes << index(row, column); } createOffsetFramesCommand(indexes, QPoint(plannedFrameMove, 0), false, true, parentCommand.data()); } const int oldTime = m_d->image->animationInterface()->currentUITime(); const int newTime = minSelectedTime; new KisSwitchCurrentTimeCommand(m_d->image->animationInterface(), oldTime, newTime, parentCommand.data()); } KisProcessingApplicator::runSingleCommandStroke(m_d->image, parentCommand.take(), KisStrokeJobData::BARRIER, KisStrokeJobData::EXCLUSIVE); return true; } QString TimelineFramesModel::audioChannelFileName() const { return m_d->image ? m_d->image->animationInterface()->audioChannelFileName() : QString(); } void TimelineFramesModel::setAudioChannelFileName(const QString &fileName) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->image); m_d->image->animationInterface()->setAudioChannelFileName(fileName); } bool TimelineFramesModel::isAudioMuted() const { return m_d->image ? m_d->image->animationInterface()->isAudioMuted() : false; } void TimelineFramesModel::setAudioMuted(bool value) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->image); m_d->image->animationInterface()->setAudioMuted(value); } qreal TimelineFramesModel::audioVolume() const { return m_d->image ? m_d->image->animationInterface()->audioVolume() : 0.5; } void TimelineFramesModel::setAudioVolume(qreal value) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->image); m_d->image->animationInterface()->setAudioVolume(value); } void TimelineFramesModel::setFullClipRangeStart(int column) { m_d->image->animationInterface()->setFullClipRangeStartTime(column); } void TimelineFramesModel::setFullClipRangeEnd(int column) { m_d->image->animationInterface()->setFullClipRangeEndTime(column); } diff --git a/plugins/dockers/layerdocker/LayerBox.cpp b/plugins/dockers/layerdocker/LayerBox.cpp index d8eadd94a9..4cebfad4fb 100644 --- a/plugins/dockers/layerdocker/LayerBox.cpp +++ b/plugins/dockers/layerdocker/LayerBox.cpp @@ -1,1109 +1,1130 @@ /* * LayerBox.cc - part of Krita aka Krayon aka KimageShop * * Copyright (c) 2002 Patrick Julien * Copyright (C) 2006 Gábor Lehel * Copyright (C) 2007 Thomas Zander * Copyright (C) 2007 Boudewijn Rempt * Copyright (c) 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 "LayerBox.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 "kis_action_manager.h" #include "widgets/kis_cmb_composite.h" #include "widgets/kis_slider_spin_box.h" #include "KisViewManager.h" #include "kis_node_manager.h" #include "kis_node_model.h" #include "canvas/kis_canvas2.h" #include "kis_dummies_facade_base.h" #include "kis_shape_controller.h" #include "kis_selection_mask.h" #include "kis_config.h" #include "KisView.h" #include "krita_utils.h" #include "kis_color_label_selector_widget.h" #include "kis_signals_blocker.h" #include "kis_color_filter_combo.h" #include "kis_node_filter_proxy_model.h" #include "kis_selection.h" #include "kis_processing_applicator.h" #include "commands/kis_set_global_selection_command.h" #include "KisSelectionActionsAdapter.h" #include "kis_layer_utils.h" #include "ui_WdgLayerBox.h" #include "NodeView.h" #include "SyncButtonAndAction.h" class LayerBoxStyle : public QProxyStyle { public: LayerBoxStyle(QStyle *baseStyle = 0) : QProxyStyle(baseStyle) {} void drawPrimitive(PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const { if (element == QStyle::PE_IndicatorItemViewItemDrop) { QColor color(widget->palette().color(QPalette::Highlight).lighter()); if (option->rect.height() == 0) { QBrush brush(color); QRect r(option->rect); r.setTop(r.top() - 2); r.setBottom(r.bottom() + 2); painter->fillRect(r, brush); } else { color.setAlpha(200); QBrush brush(color); painter->fillRect(option->rect, brush); } } else { QProxyStyle::drawPrimitive(element, option, painter, widget); } } }; inline void LayerBox::connectActionToButton(KisViewManager* viewManager, QAbstractButton *button, const QString &id) { if (!viewManager || !button) return; KisAction *action = viewManager->actionManager()->actionByName(id); if (!action) return; connect(button, SIGNAL(clicked()), action, SLOT(trigger())); connect(action, SIGNAL(sigEnableSlaves(bool)), button, SLOT(setEnabled(bool))); connect(viewManager->mainWindow(), SIGNAL(themeChanged()), this, SLOT(slotUpdateIcons())); } inline void LayerBox::addActionToMenu(QMenu *menu, const QString &id) { if (m_canvas) { menu->addAction(m_canvas->viewManager()->actionManager()->actionByName(id)); } } LayerBox::LayerBox() : QDockWidget(i18n("Layers")) , m_canvas(0) , m_wdgLayerBox(new Ui_WdgLayerBox) , m_thumbnailCompressor(500, KisSignalCompressor::FIRST_INACTIVE) , m_colorLabelCompressor(900, KisSignalCompressor::FIRST_INACTIVE) , m_thumbnailSizeCompressor(100, KisSignalCompressor::FIRST_INACTIVE) { KisConfig cfg(false); QWidget* mainWidget = new QWidget(this); setWidget(mainWidget); m_opacityDelayTimer.setSingleShot(true); m_wdgLayerBox->setupUi(mainWidget); m_wdgLayerBox->listLayers->setStyle(new LayerBoxStyle(m_wdgLayerBox->listLayers->style())); connect(m_wdgLayerBox->listLayers, SIGNAL(contextMenuRequested(QPoint,QModelIndex)), this, SLOT(slotContextMenuRequested(QPoint,QModelIndex))); connect(m_wdgLayerBox->listLayers, SIGNAL(collapsed(QModelIndex)), SLOT(slotCollapsed(QModelIndex))); connect(m_wdgLayerBox->listLayers, SIGNAL(expanded(QModelIndex)), SLOT(slotExpanded(QModelIndex))); connect(m_wdgLayerBox->listLayers, SIGNAL(selectionChanged(QModelIndexList)), SLOT(selectionChanged(QModelIndexList))); slotUpdateIcons(); m_wdgLayerBox->bnDelete->setIconSize(QSize(22, 22)); m_wdgLayerBox->bnRaise->setIconSize(QSize(22, 22)); m_wdgLayerBox->bnLower->setIconSize(QSize(22, 22)); m_wdgLayerBox->bnProperties->setIconSize(QSize(22, 22)); m_wdgLayerBox->bnDuplicate->setIconSize(QSize(22, 22)); m_wdgLayerBox->bnLower->setEnabled(false); m_wdgLayerBox->bnRaise->setEnabled(false); if (cfg.sliderLabels()) { m_wdgLayerBox->opacityLabel->hide(); m_wdgLayerBox->doubleOpacity->setPrefix(QString("%1: ").arg(i18n("Opacity"))); } m_wdgLayerBox->doubleOpacity->setRange(0, 100, 0); m_wdgLayerBox->doubleOpacity->setSuffix(i18n("%")); connect(m_wdgLayerBox->doubleOpacity, SIGNAL(valueChanged(qreal)), SLOT(slotOpacitySliderMoved(qreal))); connect(&m_opacityDelayTimer, SIGNAL(timeout()), SLOT(slotOpacityChanged())); connect(m_wdgLayerBox->cmbComposite, SIGNAL(activated(int)), SLOT(slotCompositeOpChanged(int))); m_newLayerMenu = new QMenu(this); m_wdgLayerBox->bnAdd->setMenu(m_newLayerMenu); m_wdgLayerBox->bnAdd->setPopupMode(QToolButton::MenuButtonPopup); m_nodeModel = new KisNodeModel(this); m_filteringModel = new KisNodeFilterProxyModel(this); m_filteringModel->setNodeModel(m_nodeModel); /** * Connect model updateUI() to enable/disable controls. * Note: nodeActivated() is connected separately in setImage(), because * it needs particular order of calls: first the connection to the * node manager should be called, then updateUI() */ connect(m_nodeModel, SIGNAL(rowsInserted(QModelIndex,int,int)), SLOT(updateUI())); connect(m_nodeModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), SLOT(updateUI())); connect(m_nodeModel, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), SLOT(updateUI())); connect(m_nodeModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), SLOT(updateUI())); connect(m_nodeModel, SIGNAL(modelReset()), SLOT(slotModelReset())); + connect(m_nodeModel, SIGNAL(rowsInserted(QModelIndex,int,int)), SLOT(slotForgetAboutSavedNodeBeforeEditSelectionMode())); + connect(m_nodeModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), SLOT(slotForgetAboutSavedNodeBeforeEditSelectionMode())); + connect(m_nodeModel, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), SLOT(slotForgetAboutSavedNodeBeforeEditSelectionMode())); + connect(m_nodeModel, SIGNAL(modelReset()), SLOT(slotForgetAboutSavedNodeBeforeEditSelectionMode())); + + KisAction *showGlobalSelectionMask = new KisAction(i18n("&Show Global Selection Mask"), this); showGlobalSelectionMask->setObjectName("show-global-selection-mask"); showGlobalSelectionMask->setActivationFlags(KisAction::ACTIVE_IMAGE); showGlobalSelectionMask->setToolTip(i18nc("@info:tooltip", "Shows global selection as a usual selection mask in Layers docker")); showGlobalSelectionMask->setCheckable(true); connect(showGlobalSelectionMask, SIGNAL(triggered(bool)), SLOT(slotEditGlobalSelection(bool))); m_actions.append(showGlobalSelectionMask); showGlobalSelectionMask->setChecked(cfg.showGlobalSelection()); m_colorSelector = new KisColorLabelSelectorWidget(this); connect(m_colorSelector, SIGNAL(currentIndexChanged(int)), SLOT(slotColorLabelChanged(int))); m_colorSelectorAction = new QWidgetAction(this); m_colorSelectorAction->setDefaultWidget(m_colorSelector); connect(m_nodeModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), &m_colorLabelCompressor, SLOT(start())); m_wdgLayerBox->listLayers->setModel(m_filteringModel); // this connection should be done *after* the setModel() call to // happen later than the internal selection model connect(m_filteringModel.data(), &KisNodeFilterProxyModel::rowsAboutToBeRemoved, this, &LayerBox::slotAboutToRemoveRows); connect(m_wdgLayerBox->cmbFilter, SIGNAL(selectedColorsChanged()), SLOT(updateLayerFiltering())); setEnabled(false); connect(&m_thumbnailCompressor, SIGNAL(timeout()), SLOT(updateThumbnail())); connect(&m_colorLabelCompressor, SIGNAL(timeout()), SLOT(updateAvailableLabels())); // set up the configure menu for changing thumbnail size QMenu* configureMenu = new QMenu(this); configureMenu->setStyleSheet("margin: 6px"); configureMenu->addSection(i18n("Thumbnail Size")); m_wdgLayerBox->configureLayerDockerToolbar->setMenu(configureMenu); m_wdgLayerBox->configureLayerDockerToolbar->setIcon(KisIconUtils::loadIcon("configure")); m_wdgLayerBox->configureLayerDockerToolbar->setPopupMode(QToolButton::InstantPopup); // add horizontal slider thumbnailSizeSlider = new QSlider(this); thumbnailSizeSlider->setOrientation(Qt::Horizontal); thumbnailSizeSlider->setRange(20, 80); thumbnailSizeSlider->setValue(cfg.layerThumbnailSize(false)); // grab this from the kritarc thumbnailSizeSlider->setMinimumHeight(20); thumbnailSizeSlider->setMinimumWidth(40); thumbnailSizeSlider->setTickInterval(5); QWidgetAction *sliderAction= new QWidgetAction(this); sliderAction->setDefaultWidget(thumbnailSizeSlider); configureMenu->addAction(sliderAction); connect(thumbnailSizeSlider, SIGNAL(sliderMoved(int)), &m_thumbnailSizeCompressor, SLOT(start())); connect(&m_thumbnailSizeCompressor, SIGNAL(timeout()), SLOT(slotUpdateThumbnailIconSize())); } LayerBox::~LayerBox() { delete m_wdgLayerBox; } void expandNodesRecursively(KisNodeSP root, QPointer filteringModel, NodeView *nodeView) { if (!root) return; if (filteringModel.isNull()) return; if (!nodeView) return; nodeView->blockSignals(true); KisNodeSP node = root->firstChild(); while (node) { QModelIndex idx = filteringModel->indexFromNode(node); if (idx.isValid()) { nodeView->setExpanded(idx, !node->collapsed()); } if (!node->collapsed() && node->childCount() > 0) { expandNodesRecursively(node, filteringModel, nodeView); } node = node->nextSibling(); } nodeView->blockSignals(false); } void LayerBox::slotAddLayerBnClicked() { if (m_canvas) { KisNodeList nodes = m_nodeManager->selectedNodes(); if (nodes.size() == 1) { KisAction *action = m_canvas->viewManager()->actionManager()->actionByName("add_new_paint_layer"); action->trigger(); } else { KisAction *action = m_canvas->viewManager()->actionManager()->actionByName("create_quick_group"); action->trigger(); } } } void LayerBox::setViewManager(KisViewManager* kisview) { m_nodeManager = kisview->nodeManager(); + if (m_nodeManager) { + connect(m_nodeManager, SIGNAL(sigNodeActivated(KisNodeSP)), SLOT(slotForgetAboutSavedNodeBeforeEditSelectionMode())); + } + Q_FOREACH (KisAction *action, m_actions) { kisview->actionManager()-> addAction(action->objectName(), action); } connect(m_wdgLayerBox->bnAdd, SIGNAL(clicked()), this, SLOT(slotAddLayerBnClicked())); connectActionToButton(kisview, m_wdgLayerBox->bnDuplicate, "duplicatelayer"); KisActionManager *actionManager = kisview->actionManager(); KisAction *action = actionManager->createAction("RenameCurrentLayer"); Q_ASSERT(action); connect(action, SIGNAL(triggered()), this, SLOT(slotRenameCurrentNode())); m_propertiesAction = actionManager->createAction("layer_properties"); Q_ASSERT(m_propertiesAction); new SyncButtonAndAction(m_propertiesAction, m_wdgLayerBox->bnProperties, this); connect(m_propertiesAction, SIGNAL(triggered()), this, SLOT(slotPropertiesClicked())); m_removeAction = actionManager->createAction("remove_layer"); Q_ASSERT(m_removeAction); new SyncButtonAndAction(m_removeAction, m_wdgLayerBox->bnDelete, this); connect(m_removeAction, SIGNAL(triggered()), this, SLOT(slotRmClicked())); action = actionManager->createAction("move_layer_up"); Q_ASSERT(action); new SyncButtonAndAction(action, m_wdgLayerBox->bnRaise, this); connect(action, SIGNAL(triggered()), this, SLOT(slotRaiseClicked())); action = actionManager->createAction("move_layer_down"); Q_ASSERT(action); new SyncButtonAndAction(action, m_wdgLayerBox->bnLower, this); connect(action, SIGNAL(triggered()), this, SLOT(slotLowerClicked())); m_changeCloneSourceAction = actionManager->createAction("set-copy-from"); Q_ASSERT(m_changeCloneSourceAction); connect(m_changeCloneSourceAction, &KisAction::triggered, this, &LayerBox::slotChangeCloneSourceClicked); } void LayerBox::setCanvas(KoCanvasBase *canvas) { if (m_canvas == canvas) return; setEnabled(canvas != 0); if (m_canvas) { m_canvas->disconnectCanvasObserver(this); m_nodeModel->setDummiesFacade(0, 0, 0, 0, 0); m_selectionActionsAdapter.reset(); if (m_image) { KisImageAnimationInterface *animation = m_image->animationInterface(); animation->disconnect(this); } disconnect(m_image, 0, this, 0); disconnect(m_nodeManager, 0, this, 0); disconnect(m_nodeModel, 0, m_nodeManager, 0); m_nodeManager->slotSetSelectedNodes(KisNodeList()); } m_canvas = dynamic_cast(canvas); if (m_canvas) { m_image = m_canvas->image(); connect(m_image, SIGNAL(sigImageUpdated(QRect)), &m_thumbnailCompressor, SLOT(start())); KisDocument* doc = static_cast(m_canvas->imageView()->document()); KisShapeController *kritaShapeController = dynamic_cast(doc->shapeController()); KisDummiesFacadeBase *kritaDummiesFacade = static_cast(kritaShapeController); m_selectionActionsAdapter.reset(new KisSelectionActionsAdapter(m_canvas->viewManager()->selectionManager())); m_nodeModel->setDummiesFacade(kritaDummiesFacade, m_image, kritaShapeController, m_selectionActionsAdapter.data(), m_nodeManager); connect(m_image, SIGNAL(sigAboutToBeDeleted()), SLOT(notifyImageDeleted())); connect(m_image, SIGNAL(sigNodeCollapsedChanged()), SLOT(slotNodeCollapsedChanged())); // cold start if (m_nodeManager) { setCurrentNode(m_nodeManager->activeNode()); // Connection KisNodeManager -> LayerBox connect(m_nodeManager, SIGNAL(sigUiNeedChangeActiveNode(KisNodeSP)), this, SLOT(setCurrentNode(KisNodeSP))); connect(m_nodeManager, SIGNAL(sigUiNeedChangeSelectedNodes(QList)), SLOT(slotNodeManagerChangedSelection(QList))); } else { setCurrentNode(m_canvas->imageView()->currentNode()); } // Connection LayerBox -> KisNodeManager (isolate layer) connect(m_nodeModel, SIGNAL(toggleIsolateActiveNode()), m_nodeManager, SLOT(toggleIsolateActiveNode())); KisImageAnimationInterface *animation = m_image->animationInterface(); connect(animation, &KisImageAnimationInterface::sigUiTimeChanged, this, &LayerBox::slotImageTimeChanged); expandNodesRecursively(m_image->rootLayer(), m_filteringModel, m_wdgLayerBox->listLayers); m_wdgLayerBox->listLayers->scrollTo(m_wdgLayerBox->listLayers->currentIndex()); updateAvailableLabels(); addActionToMenu(m_newLayerMenu, "add_new_paint_layer"); addActionToMenu(m_newLayerMenu, "add_new_group_layer"); addActionToMenu(m_newLayerMenu, "add_new_clone_layer"); addActionToMenu(m_newLayerMenu, "add_new_shape_layer"); addActionToMenu(m_newLayerMenu, "add_new_adjustment_layer"); addActionToMenu(m_newLayerMenu, "add_new_fill_layer"); addActionToMenu(m_newLayerMenu, "add_new_file_layer"); m_newLayerMenu->addSeparator(); addActionToMenu(m_newLayerMenu, "add_new_transparency_mask"); addActionToMenu(m_newLayerMenu, "add_new_filter_mask"); addActionToMenu(m_newLayerMenu, "add_new_colorize_mask"); addActionToMenu(m_newLayerMenu, "add_new_transform_mask"); addActionToMenu(m_newLayerMenu, "add_new_selection_mask"); } } void LayerBox::unsetCanvas() { setEnabled(false); if (m_canvas) { m_newLayerMenu->clear(); } m_filteringModel->unsetDummiesFacade(); disconnect(m_image, 0, this, 0); disconnect(m_nodeManager, 0, this, 0); disconnect(m_nodeModel, 0, m_nodeManager, 0); m_nodeManager->slotSetSelectedNodes(KisNodeList()); m_canvas = 0; } void LayerBox::notifyImageDeleted() { setCanvas(0); } void LayerBox::updateUI() { if (!m_canvas) return; if (!m_nodeManager) return; KisNodeSP activeNode = m_nodeManager->activeNode(); if (activeNode != m_activeNode) { if( !m_activeNode.isNull() ) m_activeNode->disconnect(this); m_activeNode = activeNode; if (activeNode) { KisKeyframeChannel *opacityChannel = activeNode->getKeyframeChannel(KisKeyframeChannel::Opacity.id(), false); if (opacityChannel) { watchOpacityChannel(opacityChannel); } else { watchOpacityChannel(0); connect(activeNode.data(), &KisNode::keyframeChannelAdded, this, &LayerBox::slotKeyframeChannelAdded); } } } m_wdgLayerBox->bnRaise->setEnabled(activeNode && activeNode->isEditable(false) && (activeNode->nextSibling() || (activeNode->parent() && activeNode->parent() != m_image->root()))); m_wdgLayerBox->bnLower->setEnabled(activeNode && activeNode->isEditable(false) && (activeNode->prevSibling() || (activeNode->parent() && activeNode->parent() != m_image->root()))); m_wdgLayerBox->doubleOpacity->setEnabled(activeNode && activeNode->isEditable(false)); m_wdgLayerBox->cmbComposite->setEnabled(activeNode && activeNode->isEditable(false)); m_wdgLayerBox->cmbComposite->validate(m_image->colorSpace()); if (activeNode) { if (activeNode->inherits("KisColorizeMask") || activeNode->inherits("KisLayer")) { m_wdgLayerBox->doubleOpacity->setEnabled(true); if (!m_wdgLayerBox->doubleOpacity->isDragging()) { slotSetOpacity(activeNode->opacity() * 100.0 / 255); } const KoCompositeOp* compositeOp = activeNode->compositeOp(); if (compositeOp) { slotSetCompositeOp(compositeOp); } else { m_wdgLayerBox->cmbComposite->setEnabled(false); } const KisGroupLayer *group = qobject_cast(activeNode.data()); bool compositeSelectionActive = !(group && group->passThroughMode()); m_wdgLayerBox->cmbComposite->setEnabled(compositeSelectionActive); } else if (activeNode->inherits("KisMask")) { m_wdgLayerBox->cmbComposite->setEnabled(false); m_wdgLayerBox->doubleOpacity->setEnabled(false); } } } /** * This method is called *only* when non-GUI code requested the * change of the current node */ void LayerBox::setCurrentNode(KisNodeSP node) { m_filteringModel->setActiveNode(node); QModelIndex index = node ? m_filteringModel->indexFromNode(node) : QModelIndex(); m_filteringModel->setData(index, true, KisNodeModel::ActiveRole); updateUI(); } void LayerBox::slotModelReset() { if(m_nodeModel->hasDummiesFacade()) { QItemSelection selection; Q_FOREACH (const KisNodeSP node, m_nodeManager->selectedNodes()) { const QModelIndex &idx = m_filteringModel->indexFromNode(node); if(idx.isValid()){ QItemSelectionRange selectionRange(idx); selection << selectionRange; } } m_wdgLayerBox->listLayers->selectionModel()->select(selection, QItemSelectionModel::ClearAndSelect); } updateUI(); } void LayerBox::slotSetCompositeOp(const KoCompositeOp* compositeOp) { KoID opId = KoCompositeOpRegistry::instance().getKoID(compositeOp->id()); m_wdgLayerBox->cmbComposite->blockSignals(true); m_wdgLayerBox->cmbComposite->selectCompositeOp(opId); m_wdgLayerBox->cmbComposite->blockSignals(false); } // range: 0-100 void LayerBox::slotSetOpacity(double opacity) { Q_ASSERT(opacity >= 0 && opacity <= 100); m_wdgLayerBox->doubleOpacity->blockSignals(true); m_wdgLayerBox->doubleOpacity->setValue(opacity); m_wdgLayerBox->doubleOpacity->blockSignals(false); } void LayerBox::slotContextMenuRequested(const QPoint &pos, const QModelIndex &index) { KisNodeList nodes = m_nodeManager->selectedNodes(); KisNodeSP activeNode = m_nodeManager->activeNode(); if (nodes.isEmpty() || !activeNode) return; if (m_canvas) { QMenu menu; const bool singleLayer = nodes.size() == 1; if (index.isValid()) { menu.addAction(m_propertiesAction); if (singleLayer) { addActionToMenu(&menu, "layer_style"); } Q_FOREACH(KisNodeSP node, nodes) { if (node && node->inherits("KisCloneLayer")) { menu.addAction(m_changeCloneSourceAction); break; } } { KisSignalsBlocker b(m_colorSelector); m_colorSelector->setCurrentIndex(singleLayer ? activeNode->colorLabelIndex() : -1); } menu.addAction(m_colorSelectorAction); menu.addSeparator(); addActionToMenu(&menu, "cut_layer_clipboard"); addActionToMenu(&menu, "copy_layer_clipboard"); addActionToMenu(&menu, "paste_layer_from_clipboard"); menu.addAction(m_removeAction); addActionToMenu(&menu, "duplicatelayer"); addActionToMenu(&menu, "merge_layer"); addActionToMenu(&menu, "new_from_visible"); if (singleLayer) { addActionToMenu(&menu, "flatten_image"); addActionToMenu(&menu, "flatten_layer"); } menu.addSeparator(); QMenu *selectMenu = menu.addMenu(i18n("&Select")); addActionToMenu(selectMenu, "select_all_layers"); addActionToMenu(selectMenu, "select_visible_layers"); addActionToMenu(selectMenu, "select_invisible_layers"); addActionToMenu(selectMenu, "select_locked_layers"); addActionToMenu(selectMenu, "select_unlocked_layers"); QMenu *groupMenu = menu.addMenu(i18n("&Group")); addActionToMenu(groupMenu, "create_quick_group"); addActionToMenu(groupMenu, "create_quick_clipping_group"); addActionToMenu(groupMenu, "quick_ungroup"); QMenu *locksMenu = menu.addMenu(i18n("&Toggle Locks && Visibility")); addActionToMenu(locksMenu, "toggle_layer_visibility"); addActionToMenu(locksMenu, "toggle_layer_lock"); addActionToMenu(locksMenu, "toggle_layer_inherit_alpha"); addActionToMenu(locksMenu, "toggle_layer_alpha_lock"); if (singleLayer) { QMenu *addLayerMenu = menu.addMenu(i18n("&Add")); addActionToMenu(addLayerMenu, "add_new_transparency_mask"); addActionToMenu(addLayerMenu, "add_new_filter_mask"); addActionToMenu(addLayerMenu, "add_new_colorize_mask"); addActionToMenu(addLayerMenu, "add_new_transform_mask"); addActionToMenu(addLayerMenu, "add_new_selection_mask"); addLayerMenu->addSeparator(); addActionToMenu(addLayerMenu, "add_new_clone_layer"); QMenu *convertToMenu = menu.addMenu(i18n("&Convert")); addActionToMenu(convertToMenu, "convert_to_paint_layer"); addActionToMenu(convertToMenu, "convert_to_transparency_mask"); addActionToMenu(convertToMenu, "convert_to_filter_mask"); addActionToMenu(convertToMenu, "convert_to_selection_mask"); addActionToMenu(convertToMenu, "convert_to_file_layer"); QMenu *splitAlphaMenu = menu.addMenu(i18n("S&plit Alpha")); addActionToMenu(splitAlphaMenu, "split_alpha_into_mask"); addActionToMenu(splitAlphaMenu, "split_alpha_write"); addActionToMenu(splitAlphaMenu, "split_alpha_save_merged"); } else { QMenu *addLayerMenu = menu.addMenu(i18n("&Add")); addActionToMenu(addLayerMenu, "add_new_clone_layer"); } menu.addSeparator(); addActionToMenu(&menu, "pin_to_timeline"); if (singleLayer) { KisNodeSP node = m_filteringModel->nodeFromIndex(index); if (node && !node->inherits("KisTransformMask")) { addActionToMenu(&menu, "isolate_active_layer"); } addActionToMenu(&menu, "selectopaque"); } } menu.exec(pos); } } void LayerBox::slotMinimalView() { m_wdgLayerBox->listLayers->setDisplayMode(NodeView::MinimalMode); } void LayerBox::slotDetailedView() { m_wdgLayerBox->listLayers->setDisplayMode(NodeView::DetailedMode); } void LayerBox::slotThumbnailView() { m_wdgLayerBox->listLayers->setDisplayMode(NodeView::ThumbnailMode); } void LayerBox::slotRmClicked() { if (!m_canvas) return; m_nodeManager->removeNode(); } void LayerBox::slotRaiseClicked() { if (!m_canvas) return; m_nodeManager->raiseNode(); } void LayerBox::slotLowerClicked() { if (!m_canvas) return; m_nodeManager->lowerNode(); } void LayerBox::slotPropertiesClicked() { if (!m_canvas) return; if (KisNodeSP active = m_nodeManager->activeNode()) { m_nodeManager->nodeProperties(active); } } void LayerBox::slotChangeCloneSourceClicked() { if (!m_canvas) return; m_nodeManager->changeCloneSource(); } void LayerBox::slotCompositeOpChanged(int index) { Q_UNUSED(index); if (!m_canvas) return; QString compositeOp = m_wdgLayerBox->cmbComposite->selectedCompositeOp().id(); m_nodeManager->nodeCompositeOpChanged(m_nodeManager->activeColorSpace()->compositeOp(compositeOp)); } void LayerBox::slotOpacityChanged() { if (!m_canvas) return; m_blockOpacityUpdate = true; m_nodeManager->nodeOpacityChanged(m_newOpacity); m_blockOpacityUpdate = false; } void LayerBox::slotOpacitySliderMoved(qreal opacity) { m_newOpacity = opacity; m_opacityDelayTimer.start(200); } void LayerBox::slotCollapsed(const QModelIndex &index) { KisNodeSP node = m_filteringModel->nodeFromIndex(index); if (node) { node->setCollapsed(true); } } void LayerBox::slotExpanded(const QModelIndex &index) { KisNodeSP node = m_filteringModel->nodeFromIndex(index); if (node) { node->setCollapsed(false); } } void LayerBox::slotSelectOpaque() { if (!m_canvas) return; QAction *action = m_canvas->viewManager()->actionManager()->actionByName("selectopaque"); if (action) { action->trigger(); } } void LayerBox::slotNodeCollapsedChanged() { expandNodesRecursively(m_image->rootLayer(), m_filteringModel, m_wdgLayerBox->listLayers); } inline bool isSelectionMask(KisNodeSP node) { return dynamic_cast(node.data()); } KisNodeSP LayerBox::findNonHidableNode(KisNodeSP startNode) { if (KisNodeManager::isNodeHidden(startNode, true) && startNode->parent() && !startNode->parent()->parent()) { KisNodeSP node = startNode->prevSibling(); while (node && KisNodeManager::isNodeHidden(node, true)) { node = node->prevSibling(); } if (!node) { node = startNode->nextSibling(); while (node && KisNodeManager::isNodeHidden(node, true)) { node = node->nextSibling(); } } if (!node) { node = m_image->root()->lastChild(); while (node && KisNodeManager::isNodeHidden(node, true)) { node = node->prevSibling(); } } KIS_ASSERT_RECOVER_NOOP(node && "cannot activate any node!"); startNode = node; } return startNode; } void LayerBox::slotEditGlobalSelection(bool showSelections) { KisNodeSP lastActiveNode = m_nodeManager->activeNode(); KisNodeSP activateNode = lastActiveNode; KisSelectionMaskSP globalSelectionMask; if (!showSelections) { - activateNode = findNonHidableNode(activateNode); + activateNode = + m_savedNodeBeforeEditSelectionMode ? + KisNodeSP(m_savedNodeBeforeEditSelectionMode) : + findNonHidableNode(activateNode); } m_nodeModel->setShowGlobalSelection(showSelections); globalSelectionMask = m_image->rootLayer()->selectionMask(); // try to find deactivated, but visible masks if (!globalSelectionMask) { KoProperties properties; properties.setProperty("visible", true); QList masks = m_image->rootLayer()->childNodes(QStringList("KisSelectionMask"), properties); if (!masks.isEmpty()) { globalSelectionMask = dynamic_cast(masks.first().data()); } } // try to find at least any selection mask if (!globalSelectionMask) { KoProperties properties; QList masks = m_image->rootLayer()->childNodes(QStringList("KisSelectionMask"), properties); if (!masks.isEmpty()) { globalSelectionMask = dynamic_cast(masks.first().data()); } } if (globalSelectionMask) { if (showSelections) { activateNode = globalSelectionMask; } } if (activateNode != lastActiveNode) { m_nodeManager->slotNonUiActivatedNode(activateNode); } else if (lastActiveNode) { setCurrentNode(lastActiveNode); } if (showSelections && !globalSelectionMask) { KisProcessingApplicator applicator(m_image, 0, KisProcessingApplicator::NONE, KisImageSignalVector() << ModifiedSignal, kundo2_i18n("Quick Selection Mask")); applicator.applyCommand( new KisLayerUtils::KeepNodesSelectedCommand( m_nodeManager->selectedNodes(), KisNodeList(), lastActiveNode, 0, m_image, false), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); applicator.applyCommand(new KisSetEmptyGlobalSelectionCommand(m_image), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); applicator.applyCommand(new KisLayerUtils::SelectGlobalSelectionMask(m_image), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); applicator.end(); } else if (!showSelections && globalSelectionMask && globalSelectionMask->selection()->selectedRect().isEmpty()) { KisProcessingApplicator applicator(m_image, 0, KisProcessingApplicator::NONE, KisImageSignalVector() << ModifiedSignal, kundo2_i18n("Cancel Quick Selection Mask")); applicator.applyCommand(new KisSetGlobalSelectionCommand(m_image, 0), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); applicator.end(); } + if (showSelections) { + m_savedNodeBeforeEditSelectionMode = lastActiveNode; + } } void LayerBox::selectionChanged(const QModelIndexList selection) { if (!m_nodeManager) return; /** * When the user clears the extended selection by clicking on the * empty area of the docker, the selection should be reset on to * the active layer, which might be even unselected(!). */ if (selection.isEmpty() && m_nodeManager->activeNode()) { QModelIndex selectedIndex = m_filteringModel->indexFromNode(m_nodeManager->activeNode()); m_wdgLayerBox->listLayers->selectionModel()-> setCurrentIndex(selectedIndex, QItemSelectionModel::ClearAndSelect); return; } QList selectedNodes; Q_FOREACH (const QModelIndex &idx, selection) { selectedNodes << m_filteringModel->nodeFromIndex(idx); } m_nodeManager->slotSetSelectedNodes(selectedNodes); updateUI(); } void LayerBox::slotAboutToRemoveRows(const QModelIndex &parent, int start, int end) { /** * Qt has changed its behavior when deleting an item. Previously * the selection priority was on the next item in the list, and * now it has shanged to the previous item. Here we just adjust * the selected item after the node removal. Please take care that * this method overrides what was done by the corresponding method * of QItemSelectionModel, which *has already done* its work. That * is why we use (start - 1) and (end + 1) in the activation * condition. * * See bug: https://bugs.kde.org/show_bug.cgi?id=345601 */ QModelIndex currentIndex = m_wdgLayerBox->listLayers->currentIndex(); QAbstractItemModel *model = m_filteringModel; if (currentIndex.isValid() && parent == currentIndex.parent() && currentIndex.row() >= start - 1 && currentIndex.row() <= end + 1) { QModelIndex old = currentIndex; if (model && end < model->rowCount(parent) - 1) // there are rows left below the change currentIndex = model->index(end + 1, old.column(), parent); else if (model && start > 0) // there are rows left above the change currentIndex = model->index(start - 1, old.column(), parent); else // there are no rows left in the table currentIndex = QModelIndex(); if (currentIndex.isValid() && currentIndex != old) { m_wdgLayerBox->listLayers->setCurrentIndex(currentIndex); } } } void LayerBox::slotNodeManagerChangedSelection(const KisNodeList &nodes) { if (!m_nodeManager) return; QModelIndexList newSelection; Q_FOREACH(KisNodeSP node, nodes) { newSelection << m_filteringModel->indexFromNode(node); } QItemSelectionModel *model = m_wdgLayerBox->listLayers->selectionModel(); if (KritaUtils::compareListsUnordered(newSelection, model->selectedIndexes())) { return; } QItemSelection selection; Q_FOREACH(const QModelIndex &idx, newSelection) { selection.select(idx, idx); } model->select(selection, QItemSelectionModel::ClearAndSelect); } void LayerBox::updateThumbnail() { m_wdgLayerBox->listLayers->updateNode(m_wdgLayerBox->listLayers->currentIndex()); } void LayerBox::slotRenameCurrentNode() { m_wdgLayerBox->listLayers->edit(m_wdgLayerBox->listLayers->currentIndex()); } void LayerBox::slotColorLabelChanged(int label) { KisNodeList nodes = m_nodeManager->selectedNodes(); Q_FOREACH(KisNodeSP node, nodes) { auto applyLabelFunc = [label](KisNodeSP node) { node->setColorLabelIndex(label); }; KisLayerUtils::recursiveApplyNodes(node, applyLabelFunc); } } void LayerBox::updateAvailableLabels() { if (!m_image) return; m_wdgLayerBox->cmbFilter->updateAvailableLabels(m_image->root()); } void LayerBox::updateLayerFiltering() { m_filteringModel->setAcceptedLabels(m_wdgLayerBox->cmbFilter->selectedColors()); } void LayerBox::slotKeyframeChannelAdded(KisKeyframeChannel *channel) { if (channel->id() == KisKeyframeChannel::Opacity.id()) { watchOpacityChannel(channel); } } void LayerBox::watchOpacityChannel(KisKeyframeChannel *channel) { if (m_opacityChannel) { m_opacityChannel->disconnect(this); } m_opacityChannel = channel; if (m_opacityChannel) { connect(m_opacityChannel, SIGNAL(sigKeyframeAdded(KisKeyframeSP)), this, SLOT(slotOpacityKeyframeChanged(KisKeyframeSP))); connect(m_opacityChannel, SIGNAL(sigKeyframeRemoved(KisKeyframeSP)), this, SLOT(slotOpacityKeyframeChanged(KisKeyframeSP))); connect(m_opacityChannel, SIGNAL(sigKeyframeMoved(KisKeyframeSP)), this, SLOT(slotOpacityKeyframeMoved(KisKeyframeSP))); connect(m_opacityChannel, SIGNAL(sigKeyframeChanged(KisKeyframeSP)), this, SLOT(slotOpacityKeyframeChanged(KisKeyframeSP))); } } void LayerBox::slotOpacityKeyframeChanged(KisKeyframeSP keyframe) { Q_UNUSED(keyframe); if (m_blockOpacityUpdate) return; updateUI(); } void LayerBox::slotOpacityKeyframeMoved(KisKeyframeSP keyframe, int fromTime) { Q_UNUSED(fromTime); slotOpacityKeyframeChanged(keyframe); } void LayerBox::slotImageTimeChanged(int time) { Q_UNUSED(time); updateUI(); } +void LayerBox::slotForgetAboutSavedNodeBeforeEditSelectionMode() +{ + m_savedNodeBeforeEditSelectionMode = 0; +} + void LayerBox::slotUpdateIcons() { m_wdgLayerBox->bnAdd->setIcon(KisIconUtils::loadIcon("addlayer")); m_wdgLayerBox->bnRaise->setIcon(KisIconUtils::loadIcon("arrowupblr")); m_wdgLayerBox->bnDelete->setIcon(KisIconUtils::loadIcon("deletelayer")); m_wdgLayerBox->bnLower->setIcon(KisIconUtils::loadIcon("arrowdown")); m_wdgLayerBox->bnProperties->setIcon(KisIconUtils::loadIcon("properties")); m_wdgLayerBox->bnDuplicate->setIcon(KisIconUtils::loadIcon("duplicatelayer")); // call child function about needing to update icons m_wdgLayerBox->listLayers->slotUpdateIcons(); } void LayerBox::slotUpdateThumbnailIconSize() { KisConfig cfg(false); cfg.setLayerThumbnailSize(thumbnailSizeSlider->value()); // this is a hack to force the layers list to update its display and // re-layout all the layers with the new thumbnail size resize(this->width()+1, this->height()+1); resize(this->width()-1, this->height()-1); } #include "moc_LayerBox.cpp" diff --git a/plugins/dockers/layerdocker/LayerBox.h b/plugins/dockers/layerdocker/LayerBox.h index 61e6d6ea75..799b8ea8a3 100644 --- a/plugins/dockers/layerdocker/LayerBox.h +++ b/plugins/dockers/layerdocker/LayerBox.h @@ -1,202 +1,205 @@ /* * LayerBox.h - part of Krita aka Krayon aka KimageShop * * Copyright (c) 2002 Patrick Julien * Copyright (C) 2006 Gábor Lehel * Copyright (C) 2007 Thomas Zander * Copyright (C) 2007-2009 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_LAYERBOX_H #define KIS_LAYERBOX_H #include #include #include #include #include #include #include #include #include #include "kis_action.h" #include "KisViewManager.h" #include "kis_mainwindow_observer.h" #include "kis_signal_compressor.h" #include class QModelIndex; typedef QList QModelIndexList; class QMenu; class QAbstractButton; class KoCompositeOp; class KisCanvas2; class KisNodeModel; class KisNodeFilterProxyModel; class Ui_WdgLayerBox; class KisNodeJugglerCompressed; class KisColorLabelSelectorWidget; class QWidgetAction; class KisKeyframeChannel; class KisSelectionActionsAdapter; /** * A widget that shows a visualization of the layer structure. * * The center of the layer box is KisNodeModel, which shows the actual layers. * This widget adds docking functionality and command buttons. * */ class LayerBox : public QDockWidget, public KisMainwindowObserver { Q_OBJECT public: LayerBox(); ~LayerBox() override; QString observerName() override { return "LayerBox"; } /// reimplemented from KisMainwindowObserver void setViewManager(KisViewManager* kisview) override; void setCanvas(KoCanvasBase *canvas) override; void unsetCanvas() override; private Q_SLOTS: void notifyImageDeleted(); void slotContextMenuRequested(const QPoint &pos, const QModelIndex &index); void slotMinimalView(); void slotDetailedView(); void slotThumbnailView(); // From the node manager to the layerbox void slotSetCompositeOp(const KoCompositeOp* compositeOp); void slotSetOpacity(double opacity); void updateUI(); void setCurrentNode(KisNodeSP node); void slotModelReset(); // from the layerbox to the node manager void slotRmClicked(); void slotRaiseClicked(); void slotLowerClicked(); void slotPropertiesClicked(); void slotChangeCloneSourceClicked(); void slotCompositeOpChanged(int index); void slotOpacityChanged(); void slotOpacitySliderMoved(qreal opacity); void slotCollapsed(const QModelIndex &index); void slotExpanded(const QModelIndex &index); void slotSelectOpaque(); void slotNodeCollapsedChanged(); void slotEditGlobalSelection(bool showSelections); void slotRenameCurrentNode(); void slotAboutToRemoveRows(const QModelIndex &parent, int first, int last); void selectionChanged(const QModelIndexList selection); void slotNodeManagerChangedSelection(const QList &nodes); void slotColorLabelChanged(int index); void slotUpdateIcons(); void slotAddLayerBnClicked(); void updateThumbnail(); void updateAvailableLabels(); void updateLayerFiltering(); void slotUpdateThumbnailIconSize(); // Opacity keyframing void slotKeyframeChannelAdded(KisKeyframeChannel *channel); void slotOpacityKeyframeChanged(KisKeyframeSP keyframe); void slotOpacityKeyframeMoved(KisKeyframeSP keyframe, int fromTime); void slotImageTimeChanged(int time); + void slotForgetAboutSavedNodeBeforeEditSelectionMode(); + private: inline void connectActionToButton(KisViewManager* view, QAbstractButton *button, const QString &id); inline void addActionToMenu(QMenu *menu, const QString &id); void watchOpacityChannel(KisKeyframeChannel *channel); KisNodeSP findNonHidableNode(KisNodeSP startNode); private: QPointer m_canvas; QScopedPointer m_selectionActionsAdapter; QMenu *m_newLayerMenu; KisImageWSP m_image; QPointer m_nodeModel; QPointer m_filteringModel; QPointer m_nodeManager; QPointer m_colorSelector; QPointer m_colorSelectorAction; Ui_WdgLayerBox* m_wdgLayerBox; QTimer m_opacityDelayTimer; int m_newOpacity; QVector m_actions; KisAction* m_removeAction; KisAction* m_propertiesAction; KisAction* m_changeCloneSourceAction; KisSignalCompressor m_thumbnailCompressor; KisSignalCompressor m_colorLabelCompressor; KisSignalCompressor m_thumbnailSizeCompressor; QSlider* thumbnailSizeSlider; KisNodeSP m_activeNode; + KisNodeWSP m_savedNodeBeforeEditSelectionMode; QPointer m_opacityChannel; bool m_blockOpacityUpdate {false}; }; class LayerBoxFactory : public KoDockFactoryBase { public: LayerBoxFactory() { } QString id() const override { return QString("KisLayerBox"); } QDockWidget* createDockWidget() override { LayerBox * dockWidget = new LayerBox(); dockWidget->setObjectName(id()); return dockWidget; } DockPosition defaultDockPosition() const override { return DockRight; } }; #endif // KIS_LAYERBOX_H diff --git a/plugins/extensions/pykrita/plugin/krita/__init__.py b/plugins/extensions/pykrita/plugin/krita/__init__.py index 573628f949..72f022a43a 100644 --- a/plugins/extensions/pykrita/plugin/krita/__init__.py +++ b/plugins/extensions/pykrita/plugin/krita/__init__.py @@ -1,77 +1,78 @@ from __future__ import print_function import pykrita import os import sys from .api import * from .decorators import * from .dockwidgetfactory import * from PyKrita import krita import signal signal.signal(signal.SIGINT, signal.SIG_DFL) krita_path = os.path.dirname(os.path.abspath(__file__)) sys.path.insert(0, krita_path) print("%s added to PYTHONPATH" % krita_path, file=sys.stderr) # Look for PyQt try: from PyQt5 import QtCore except ImportError: print("Python cannot find the Qt5 bindings.", file=sys.stderr) print("Please make sure, that the needed packages are installed.", file=sys.stderr) raise # Shows nice looking error dialog if an unhandled exception occurs. import excepthook excepthook.install() if sys.version_info[0] > 2: import builtins else: import __builtin__ as builtins builtins.i18n = Krita.krita_i18n +builtins.i18nc = Krita.krita_i18nc builtins.Scripter = Krita.instance() builtins.Application = Krita.instance() builtins.Krita = Krita.instance() def qDebug(text): '''Use KDE way to show debug info TODO Add a way to control debug output from partucular plugins (?) ''' plugin = sys._getframe(1).f_globals['__name__'] pykrita.qDebug('{}: {}'.format(plugin, text)) @pykritaEventHandler('_pluginLoaded') def on_load(plugin): if plugin in init.functions: # Call registered init functions for the plugin init.fire(plugin=plugin) del init.functions[plugin] return True @pykritaEventHandler('_pluginUnloading') def on_unload(plugin): if plugin in unload.functions: # Deinitialize plugin unload.fire(plugin=plugin) del unload.functions[plugin] return True @pykritaEventHandler('_pykritaLoaded') def on_pykrita_loaded(): qDebug('PYKRITA LOADED') return True @pykritaEventHandler('_pykritaUnloading') def on_pykrita_unloading(): qDebug('UNLOADING PYKRITA') return True diff --git a/plugins/extensions/pykrita/plugin/pyqtpluginsettings.cpp b/plugins/extensions/pykrita/plugin/pyqtpluginsettings.cpp index d0e3e0b5c1..314ab30ba5 100644 --- a/plugins/extensions/pykrita/plugin/pyqtpluginsettings.cpp +++ b/plugins/extensions/pykrita/plugin/pyqtpluginsettings.cpp @@ -1,112 +1,112 @@ /* * Copyright (c) 2014 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; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY 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 "pyqtpluginsettings.h" #include "ui_manager.h" #include #include #include #include #include #include -#include #include "kis_config.h" +#include "kis_icon_utils.h" #include "PythonPluginManager.h" PyQtPluginSettings::PyQtPluginSettings(PythonPluginManager *pluginManager, QWidget *parent) : KisPreferenceSet(parent) , m_pluginManager(pluginManager) , m_page(new Ui::ManagerPage) { m_page->setupUi(this); QSortFilterProxyModel* const proxy_model = new QSortFilterProxyModel(this); proxy_model->setSourceModel(pluginManager->model()); m_page->pluginsList->setModel(proxy_model); m_page->pluginsList->resizeColumnToContents(0); m_page->pluginsList->sortByColumn(0, Qt::AscendingOrder); const bool is_enabled = bool(pluginManager); const bool is_visible = !is_enabled; m_page->errorLabel->setVisible(is_visible); m_page->pluginsList->setEnabled(is_enabled); m_page->textBrowser->setEnabled(is_enabled); connect(m_page->pluginsList, SIGNAL(clicked(QModelIndex)), SLOT(updateManual(QModelIndex))); } PyQtPluginSettings::~PyQtPluginSettings() { delete m_page; } QString PyQtPluginSettings::id() { return QString("pykritapluginmanager"); } QString PyQtPluginSettings::name() { return header(); } QString PyQtPluginSettings::header() { return QString(i18n("Python Plugin Manager")); } QIcon PyQtPluginSettings::icon() { - return koIcon("applications-development"); + return KisIconUtils::loadIcon("python"); } void PyQtPluginSettings::savePreferences() const { Q_EMIT(settingsChanged()); } void PyQtPluginSettings::loadPreferences() { } void PyQtPluginSettings::loadDefaultPreferences() { } void PyQtPluginSettings::updateManual(const QModelIndex &index) { QModelIndex unsortedIndex = static_cast(m_page->pluginsList->model())->mapToSource(index); PythonPlugin *plugin = m_pluginManager->model()->plugin(unsortedIndex); if (plugin && !plugin->manual().isEmpty()) { QString manual = plugin->manual(); if (manual.startsWith("textBrowser->setHtml(manual); } else { m_page->textBrowser->setText(manual); } } else { m_page->textBrowser->setHtml("

No Manual Available

"); } } diff --git a/plugins/extensions/pykrita/sip/krita/Krita.sip b/plugins/extensions/pykrita/sip/krita/Krita.sip index e4ca56ba0a..aa488fa9e8 100644 --- a/plugins/extensions/pykrita/sip/krita/Krita.sip +++ b/plugins/extensions/pykrita/sip/krita/Krita.sip @@ -1,64 +1,65 @@ class Krita : QObject { %TypeHeaderCode #include "Krita.h" %End public: Krita(QObject* parent /TransferThis/ = 0); virtual ~Krita(); public Q_SLOTS: Document * activeDocument() const /Factory/; void setActiveDocument(Document* value); bool batchmode() const; void setBatchmode(bool value); QList actions() const; QAction *action(const QString & name) const; QList documents() const /Factory/; QStringList filters() const; Filter * filter(const QString &name) const /Factory/; QStringList colorModels() const; QStringList colorDepths(const QString &colorModel) const; QStringList filterStrategies() const; QStringList profiles(const QString &colorModel, const QString &ColorDepth) const; bool addProfile(const QString &profilePath); Notifier * notifier() const; QString version() const; QList views() const /Factory/; Window * activeWindow() const /Factory/; QList windows() const /Factory/; QMap resources(const QString &type) const /Factory/; QStringList recentDocuments() const; Document * createDocument(int width, int height, const QString &name, const QString &colorModel, const QString &colorDepth, const QString &profile, double resolution) /Factory/; QList extensions() /Factory/; Document * openDocument(const QString &filename) /Factory/; Window * openWindow(); QIcon icon(QString &iconName) const; void addExtension(Extension* _extension /GetWrapper/); %MethodCode Py_BEGIN_ALLOW_THREADS sipCpp->addExtension(a0); Py_END_ALLOW_THREADS sipTransferTo(a0Wrapper, Py_None); %End void addDockWidgetFactory(DockWidgetFactoryBase* _factory /GetWrapper/); %MethodCode Py_BEGIN_ALLOW_THREADS sipCpp->addDockWidgetFactory(a0); Py_END_ALLOW_THREADS sipTransferTo(a0Wrapper, Py_None); %End void writeSetting(const QString &group, const QString &name, const QString &value); QString readSetting(const QString &group, const QString &name, const QString &defaultValue); static Krita * instance(); static QObject * fromVariant(const QVariant & v); static QString krita_i18n(const QString &text); + static QString krita_i18nc(const QString &context, const QString &text); private: Krita(const Krita &); // Generated }; diff --git a/plugins/extensions/pykrita/sip/krita/Scratchpad.sip b/plugins/extensions/pykrita/sip/krita/Scratchpad.sip new file mode 100644 index 0000000000..9f056193d9 --- /dev/null +++ b/plugins/extensions/pykrita/sip/krita/Scratchpad.sip @@ -0,0 +1,19 @@ +class Scratchpad : public QWidget /NoDefaultCtors/ +{ +%TypeHeaderCode +#include "Scratchpad.h" +%End + +public: + Scratchpad(View* view , const QString & defaultColor, QWidget* parent /TransferThis/ = 0); + virtual ~Scratchpad(); + +public Q_SLOTS: + void clear(); + void setModeManually(bool value); + void setMode(QString modeName); + void setFillColor(QColor color); + void loadScratchpadImage(QImage image); + QImage copyScratchpadImageData(); + +}; diff --git a/plugins/extensions/pykrita/sip/krita/kritamod.sip b/plugins/extensions/pykrita/sip/krita/kritamod.sip index 92fd05ad91..bf3450748b 100644 --- a/plugins/extensions/pykrita/sip/krita/kritamod.sip +++ b/plugins/extensions/pykrita/sip/krita/kritamod.sip @@ -1,50 +1,51 @@ %Module PyKrita.krita %ModuleHeaderCode #pragma GCC visibility push(default) %End %Import QtCore/QtCoremod.sip %Import QtGui/QtGuimod.sip %Import QtXml/QtXmlmod.sip %Import QtWidgets/QtWidgetsmod.sip %Include Conversions.sip %Include Shape.sip %Include GroupShape.sip %Include Canvas.sip %Include Channel.sip %Include DockWidgetFactoryBase.sip %Include DockWidget.sip %Include Document.sip %Include Filter.sip %Include InfoObject.sip %Include Extension.sip %Include View.sip %Include Window.sip %Include Krita.sip %Include Node.sip %Include GroupLayer.sip %Include CloneLayer.sip %Include FilterLayer.sip %Include FileLayer.sip %Include FillLayer.sip %Include VectorLayer.sip %Include FilterMask.sip %Include SelectionMask.sip %Include Notifier.sip %Include Resource.sip %Include Selection.sip %Include Extension.sip %Include PresetChooser.sip +%Include Scratchpad.sip %Include Palette.sip %Include PaletteView.sip %Include ManagedColor.sip %Include Swatch.sip %Include KisCubicCurve.sip diff --git a/plugins/generators/pattern/kis_wdg_pattern.cpp b/plugins/generators/pattern/kis_wdg_pattern.cpp index 3e5e70164c..ae1297f242 100644 --- a/plugins/generators/pattern/kis_wdg_pattern.cpp +++ b/plugins/generators/pattern/kis_wdg_pattern.cpp @@ -1,70 +1,163 @@ /* * 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_wdg_pattern.h" #include #include +#include #include #include #include #include #include #include #include "ui_wdgpatternoptions.h" KisWdgPattern::KisWdgPattern(QWidget* parent) : KisConfigWidget(parent) { m_widget = new Ui_WdgPatternOptions(); m_widget->setupUi(this); m_widget->lblPattern->setVisible(false); - m_widget->lblColor->setVisible(false); - m_widget->bnColor->setVisible(false); + + m_widget->sldShearX->setSuffix(QChar(Qt::Key_Percent)); + m_widget->sldShearY->setSuffix(QChar(Qt::Key_Percent)); + m_widget->sldShearX->setRange(-500, 500, 2); + m_widget->sldShearY->setRange(-500, 500, 2); + m_widget->sldShearX->setSingleStep(1); + m_widget->sldShearY->setSingleStep(1); + m_widget->sldShearX->setValue(0.0); + m_widget->sldShearY->setValue(0.0); + + + m_widget->spbOffsetX->setSuffix(i18n(" px")); + m_widget->spbOffsetY->setSuffix(i18n(" px")); + m_widget->spbOffsetX->setRange(-10000, 10000); + m_widget->spbOffsetY->setRange(-10000, 10000); + + m_widget->sldRotationX->setSuffix(QChar(Qt::Key_degree)); + m_widget->sldRotationY->setSuffix(QChar(Qt::Key_degree)); + m_widget->sldRotationZ->setSuffix(QChar(Qt::Key_degree)); + m_widget->sldRotationX->setRange(0.0, 360.0, 2); + m_widget->sldRotationY->setRange(0.0, 360.0, 2); + m_widget->sldRotationZ->setRange(0.0, 360.0, 2); + m_widget->sldRotationX->setValue(0.0); + m_widget->sldRotationY->setValue(0.0); + m_widget->sldRotationZ->setValue(0.0); + m_widget->sldRotationX->setSingleStep(1.0); + m_widget->sldRotationY->setSingleStep(1.0); + m_widget->sldRotationZ->setSingleStep(1.0); + + m_widget->gb3dRotation->setVisible(false); + connect(m_widget->patternChooser, SIGNAL(resourceSelected(KoResourceSP)), this, SIGNAL(sigConfigurationUpdated())); + + connect(m_widget->sldShearX, SIGNAL(valueChanged(double)), this, SIGNAL(sigConfigurationUpdated())); + connect(m_widget->sldShearY, SIGNAL(valueChanged(double)), this, SIGNAL(sigConfigurationUpdated())); + + connect(m_widget->spbOffsetX, SIGNAL(valueChanged(int)), this, SIGNAL(sigConfigurationUpdated())); + connect(m_widget->spbOffsetY, SIGNAL(valueChanged(int)), this, SIGNAL(sigConfigurationUpdated())); + + connect(m_widget->spbScaleWidth, SIGNAL(valueChanged(double)), this, SLOT(slotWidthChanged(double))); + connect(m_widget->spbScaleHeight, SIGNAL(valueChanged(double)), this, SLOT(slotHeightChanged(double))); + + connect(m_widget->sldRotationX, SIGNAL(valueChanged(double)), this, SIGNAL(sigConfigurationUpdated())); + connect(m_widget->sldRotationY, SIGNAL(valueChanged(double)), this, SIGNAL(sigConfigurationUpdated())); + connect(m_widget->sldRotationZ, SIGNAL(valueChanged(double)), this, SIGNAL(sigConfigurationUpdated())); } KisWdgPattern::~KisWdgPattern() { delete m_widget; } void KisWdgPattern::setConfiguration(const KisPropertiesConfigurationSP config) { auto source = KisGlobalResourcesInterface::instance()->source(ResourceType::Patterns); KoPatternSP pattern = source.resourceForName(config->getString("pattern", "Grid01.pat")); widget()->patternChooser->setCurrentPattern(pattern ? pattern : source.fallbackResource()); + m_widget->spbOffsetX->setValue(config->getInt("transform_offset_x", 0)); + m_widget->spbOffsetY->setValue(config->getInt("transform_offset_y", 0)); + + m_widget->spbScaleWidth->setValue(config->getInt("transform_scale_x", 1.0) * 100); + m_widget->spbScaleHeight->setValue(config->getInt("transform_scale_y", 1.0) * 100); + m_widget->btnLockAspectRatio->setKeepAspectRatio(config->getBool("transform_keep_scale_aspect", true)); + + m_widget->sldShearX->setValue(config->getDouble("transform_shear_x", 0.0) * 100); + m_widget->sldShearY->setValue(config->getDouble("transform_shear_y", 0.0) * 100); + + widget()->sldRotationX->setValue(config->getDouble("transform_rotation_x", 0.0)); + widget()->sldRotationY->setValue(config->getDouble("transform_rotation_y", 0.0)); + widget()->sldRotationZ->setValue(config->getDouble("transform_rotation_z", 0.0)); } KisPropertiesConfigurationSP KisWdgPattern::configuration() const { KisGeneratorSP generator = KisGeneratorRegistry::instance()->get("pattern"); KisFilterConfigurationSP config = generator->factoryConfiguration(KisGlobalResourcesInterface::instance()); QVariant v; - v.setValue(widget()->patternChooser->currentResource()->name()); - config->setProperty("pattern", v); + if (widget()->patternChooser->currentResource()) { + v.setValue(widget()->patternChooser->currentResource()->name()); + config->setProperty("pattern", v); + } + + config->setProperty("transform_offset_x", m_widget->spbOffsetX->value()); + config->setProperty("transform_offset_y", m_widget->spbOffsetY->value()); + + config->setProperty("transform_scale_x", m_widget->spbScaleWidth->value() / 100); + config->setProperty("transform_scale_y", m_widget->spbScaleHeight->value() / 100); + + config->setProperty("transform_keep_scale_aspect", m_widget->btnLockAspectRatio->keepAspectRatio()); + + config->setProperty("transform_shear_x", widget()->sldShearX->value() / 100); + config->setProperty("transform_shear_y", widget()->sldShearY->value() / 100); + + config->setProperty("transform_rotation_x", widget()->sldRotationX->value()); + config->setProperty("transform_rotation_y", widget()->sldRotationY->value()); + config->setProperty("transform_rotation_z", widget()->sldRotationZ->value()); return config; } +void KisWdgPattern::slotWidthChanged(double w) +{ + if (m_widget->btnLockAspectRatio->keepAspectRatio()) { + m_widget->spbScaleHeight->blockSignals(true); + m_widget->spbScaleHeight->setValue(w); + m_widget->spbScaleHeight->blockSignals(false); + } + emit sigConfigurationUpdated(); +} + +void KisWdgPattern::slotHeightChanged(double h) +{ + if (m_widget->btnLockAspectRatio->keepAspectRatio()) { + m_widget->spbScaleWidth->blockSignals(true); + m_widget->spbScaleWidth->setValue(h); + m_widget->spbScaleWidth->blockSignals(false); + } + emit sigConfigurationUpdated(); +} + diff --git a/plugins/generators/pattern/kis_wdg_pattern.h b/plugins/generators/pattern/kis_wdg_pattern.h index 771186fd6f..442c69af01 100644 --- a/plugins/generators/pattern/kis_wdg_pattern.h +++ b/plugins/generators/pattern/kis_wdg_pattern.h @@ -1,45 +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_WDG_PATTERN_H #define KIS_WDG_PATTERN_H #include class Ui_WdgPatternOptions; class KisWdgPattern : public KisConfigWidget { Q_OBJECT public: KisWdgPattern(QWidget* parent = 0); ~KisWdgPattern() override; public: inline const Ui_WdgPatternOptions* widget() const { return m_widget; } void setConfiguration(const KisPropertiesConfigurationSP) override; KisPropertiesConfigurationSP configuration() const override; +private Q_SLOTS: + + void slotWidthChanged(double w); + void slotHeightChanged(double h); private: Ui_WdgPatternOptions* m_widget; }; #endif diff --git a/plugins/generators/pattern/patterngenerator.cpp b/plugins/generators/pattern/patterngenerator.cpp index a29c0f5c42..8c1dc72207 100644 --- a/plugins/generators/pattern/patterngenerator.cpp +++ b/plugins/generators/pattern/patterngenerator.cpp @@ -1,170 +1,201 @@ /* * 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() { } /****************************************************************************/ /* KoPatternGeneratorConfiguration */ /****************************************************************************/ class KoPatternGeneratorConfiguration : public KisFilterConfiguration { public: KoPatternGeneratorConfiguration(const QString & name, qint32 version, KisResourcesInterfaceSP resourcesInterface) : KisFilterConfiguration(name, version, resourcesInterface) { } KoPatternGeneratorConfiguration(const KoPatternGeneratorConfiguration &rhs) : KisFilterConfiguration(rhs) { } virtual KisFilterConfigurationSP clone() const override { return new KoPatternGeneratorConfiguration(*this); } KoPatternSP pattern(KisResourcesInterfaceSP resourcesInterface) const { const QString patternName = this->getString("pattern", "Grid01.pat"); auto source = resourcesInterface->source(ResourceType::Patterns); return source.resourceForName(patternName); } KoPatternSP pattern() const { return pattern(resourcesInterface()); } + QTransform transform() const { + QTransform transform; + + transform.shear(this->getDouble("transform_shear_x", 0.0), this->getDouble("transform_shear_y", 0.0)); + + transform.scale(this->getDouble("transform_scale_x", 1.0), this->getDouble("transform_scale_y", 1.0)); + transform.rotate(this->getDouble("transform_rotation_x", 0.0), Qt::XAxis); + transform.rotate(this->getDouble("transform_rotation_y", 0.0), Qt::YAxis); + transform.rotate(this->getDouble("transform_rotation_z", 0.0), Qt::ZAxis); + + transform.translate(this->getInt("transform_offset_x", 0), this->getInt("transform_offset_y", 0)); + return transform; + } + QList linkedResources(KisResourcesInterfaceSP globalResourcesInterface) const override { KoPatternSP pattern = this->pattern(globalResourcesInterface); QList resources; if (pattern) { resources << pattern; } return resources; } }; /****************************************************************************/ /* KoPatternGenerator */ /****************************************************************************/ KoPatternGenerator::KoPatternGenerator() : KisGenerator(id(), KoID("basic"), i18n("&Pattern...")) { setColorSpaceIndependence(FULLY_INDEPENDENT); setSupportsPainting(true); } KisFilterConfigurationSP KoPatternGenerator::factoryConfiguration(KisResourcesInterfaceSP resourcesInterface) const { return new KoPatternGeneratorConfiguration(id().id(), 1, resourcesInterface); } KisFilterConfigurationSP KoPatternGenerator::defaultConfiguration(KisResourcesInterfaceSP resourcesInterface) const { KisFilterConfigurationSP config = factoryConfiguration(resourcesInterface); auto source = resourcesInterface->source(ResourceType::Patterns); config->setProperty("pattern", QVariant::fromValue(source.fallbackResource()->name())); + + config->setProperty("transform_shear_x", QVariant::fromValue(0.0)); + config->setProperty("transform_shear_y", QVariant::fromValue(0.0)); + + config->setProperty("transform_scale_x", QVariant::fromValue(1.0)); + config->setProperty("transform_scale_y", QVariant::fromValue(1.0)); + + config->setProperty("transform_rotation_x", QVariant::fromValue(0.0)); + config->setProperty("transform_rotation_y", QVariant::fromValue(0.0)); + config->setProperty("transform_rotation_z", QVariant::fromValue(0.0)); + + config->setProperty("transform_offset_x", QVariant::fromValue(0)); + config->setProperty("transform_offset_y", QVariant::fromValue(0)); + + config->setProperty("transform_keep_scale_aspect", QVariant::fromValue(true)); + 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()); const KoPatternGeneratorConfiguration *config = dynamic_cast(_config.data()); KIS_SAFE_ASSERT_RECOVER_RETURN(config); KoPatternSP pattern = config->pattern(); + QTransform transform = config->transform(); KisFillPainter gc(dst); gc.setPattern(pattern); 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.fillRect(QRect(dstInfo.topLeft(), size), pattern, transform); gc.end(); } #include "patterngenerator.moc" diff --git a/plugins/generators/pattern/wdgpatternoptions.ui b/plugins/generators/pattern/wdgpatternoptions.ui index 47e3dd0916..5e35c950dc 100644 --- a/plugins/generators/pattern/wdgpatternoptions.ui +++ b/plugins/generators/pattern/wdgpatternoptions.ui @@ -1,86 +1,283 @@ WdgPatternOptions 0 0 - 158 - 78 + 484 + 477 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - - &Pattern: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - patternChooser - - - - - - - &Color: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - bnColor - - - - - - - - - - - 0 - 0 - - - - - + + + + + 0 + + + + Pattern + + + + + + &Pattern: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + patternChooser + + + + + + + + 0 + 0 + + + + + + + + + Transform + + + + + + 3d Rotation: + + + + + + X-Rotation: + + + + + + + Y-Rotation: + + + + + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + + + + + + + + Scale: + + + + + + Width: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + % + + + 1 + + + 500.000000000000000 + + + 100.000000000000000 + + + + + + + + + + Height: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + % + + + 1 + + + 500.000000000000000 + + + 100.000000000000000 + + + + + + + + + + Offset: + + + + + + X: + + + + + + + Y: + + + + + + + + + + + + + + + + Shear: + + + + + + X: + + + + + + + Y: + + + + + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + + + + + + + + Rotation: + + + + + + + + + + + - - KisColorButton - QPushButton -
kis_color_button.h
-
KisPatternChooser QWidget
kis_pattern_chooser.h
1
+ + KoAspectButton + QWidget +
KoAspectButton.h
+ 1 +
+ + KisDoubleParseSpinBox + QDoubleSpinBox +
kis_double_parse_spin_box.h
+
+ + KisDoubleSliderSpinBox + QWidget +
kis_slider_spin_box.h
+ 1 +
+ + KisIntParseSpinBox + QSpinBox +
kis_int_parse_spin_box.h
+
diff --git a/plugins/impex/libkra/kis_kra_load_visitor.cpp b/plugins/impex/libkra/kis_kra_load_visitor.cpp index b33f3fc0ee..455acc28e8 100644 --- a/plugins/impex/libkra/kis_kra_load_visitor.cpp +++ b/plugins/impex/libkra/kis_kra_load_visitor.cpp @@ -1,803 +1,803 @@ /* * 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" #include "kis_generator_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; } KisFilterSP filter = KisFilterRegistry::instance()->value(layer->filter()->name()); KisFilterConfigurationSP kfc = filter->factoryConfiguration(KisGlobalResourcesInterface::instance()); loadFilterConfiguration(kfc, getLocation(layer, DOT_FILTERCONFIG)); fixOldFilterConfigurations(kfc); kfc->createLocalResourcesSnapshot(); layer->setFilter(kfc); 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()); KisGeneratorSP filter = KisGeneratorRegistry::instance()->value(layer->filter()->name()); KisFilterConfigurationSP kfc = filter->factoryConfiguration(KisGlobalResourcesInterface::instance()); result = loadFilterConfiguration(kfc, getLocation(layer, DOT_FILTERCONFIG)); kfc->createLocalResourcesSnapshot(); layer->setFilter(kfc); 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()); if (!srcNode.isNull()) { KisLayerSP srcLayer = qobject_cast(srcNode.data()); Q_ASSERT(srcLayer); layer->setCopyFrom(srcLayer); } else { m_warningMessages.append(i18nc("Loading a .kra file", "The file contains a clone layer that has an incorrect source node id. " "This layer will be converted into a paint layer.")); } // 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()); KisFilterSP filter = KisFilterRegistry::instance()->value(mask->filter()->name()); KisFilterConfigurationSP kfc = filter->factoryConfiguration(KisGlobalResourcesInterface::instance()); result = loadFilterConfiguration(kfc, getLocation(mask, DOT_FILTERCONFIG)); fixOldFilterConfigurations(kfc); kfc->createLocalResourcesSnapshot(); mask->setFilter(kfc); 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(); QByteArray hash = KoMD5Generator::generateHash(data); if (m_profileCache.contains(hash)) { if (device->setProfile(m_profileCache[hash], 0)) { 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, 0)) { return true; } } } m_warningMessages << i18n("Could not load profile: %1.", location); return true; } bool KisKraLoadVisitor::loadFilterConfiguration(KisFilterConfigurationSP kfc, const QString& location) { 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); + dstSelection->convertToVectorSelectionNoUndo(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_save_visitor.cpp b/plugins/impex/libkra/kis_kra_save_visitor.cpp index a61f5ba456..e9b4d124ff 100644 --- a/plugins/impex/libkra/kis_kra_save_visitor.cpp +++ b/plugins/impex/libkra/kis_kra_save_visitor.cpp @@ -1,549 +1,549 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2005 C. Boemann * Copyright (c) 2007-2008 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_kra_save_visitor.h" #include "kis_kra_tags.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 "lazybrush/kis_colorize_mask.h" #include #include #include #include #include "kis_config.h" #include "kis_store_paintdevice_writer.h" #include "flake/kis_shape_selection.h" #include "kis_raster_keyframe_channel.h" #include "kis_paint_device_frames_interface.h" #include "lazybrush/kis_lazy_fill_tools.h" #include #include "kis_colorize_dom_utils.h" #include "kis_dom_utils.h" using namespace KRA; KisKraSaveVisitor::KisKraSaveVisitor(KoStore *store, const QString & name, QMap nodeFileNames) : KisNodeVisitor() , m_store(store) , m_external(false) , m_name(name) , m_nodeFileNames(nodeFileNames) , m_writer(new KisStorePaintDeviceWriter(store)) { } KisKraSaveVisitor::~KisKraSaveVisitor() { delete m_writer; } void KisKraSaveVisitor::setExternalUri(const QString &uri) { m_external = true; m_uri = uri; } bool KisKraSaveVisitor::visit(KisExternalLayer * layer) { bool result = false; if (auto* referencesLayer = dynamic_cast(layer)) { result = true; Q_FOREACH(KoShape *shape, referencesLayer->shapes()) { auto *reference = dynamic_cast(shape); KIS_ASSERT_RECOVER_RETURN_VALUE(reference, false); bool saved = reference->saveImage(m_store); if (!saved) { m_errorMessages << i18n("Failed to save reference image %1.", reference->internalFile()); result = false; } } } else if (KisShapeLayer *shapeLayer = dynamic_cast(layer)) { if (!saveMetaData(layer)) { m_errorMessages << i18n("Failed to save the metadata for layer %1.", layer->name()); return false; } m_store->pushDirectory(); QString location = getLocation(layer, DOT_SHAPE_LAYER); result = m_store->enterDirectory(location); if (!result) { m_errorMessages << i18n("Failed to open %1.", location); } else { result = shapeLayer->saveLayer(m_store); m_store->popDirectory(); } } else if (KisFileLayer *fileLayer = dynamic_cast(layer)) { Q_UNUSED(fileLayer); // We don't save data for file layers, but we still want to save the masks. result = true; } return result && visitAllInverse(layer); } bool KisKraSaveVisitor::visit(KisPaintLayer *layer) { if (!savePaintDevice(layer->paintDevice(), getLocation(layer))) { m_errorMessages << i18n("Failed to save the pixel data for layer %1.", layer->name()); return false; } if (!saveAnnotations(layer)) { m_errorMessages << i18n("Failed to save the annotations for layer %1.", layer->name()); return false; } if (!saveMetaData(layer)) { m_errorMessages << i18n("Failed to save the metadata for layer %1.", layer->name()); return false; } return visitAllInverse(layer); } bool KisKraSaveVisitor::visit(KisGroupLayer *layer) { if (!saveMetaData(layer)) { m_errorMessages << i18n("Failed to save the metadata for layer %1.", layer->name()); return false; } return visitAllInverse(layer); } bool KisKraSaveVisitor::visit(KisAdjustmentLayer* layer) { if (!layer->filter()) { m_errorMessages << i18n("Failed to save the filter layer %1: it has no filter.", layer->name()); return false; } if (!saveSelection(layer)) { m_errorMessages << i18n("Failed to save the selection for filter layer %1.", layer->name()); return false; } if (!saveFilterConfiguration(layer)) { m_errorMessages << i18n("Failed to save the filter configuration for filter layer %1.", layer->name()); return false; } if (!saveMetaData(layer)) { m_errorMessages << i18n("Failed to save the metadata for layer %1.", layer->name()); return false; } return visitAllInverse(layer); } bool KisKraSaveVisitor::visit(KisGeneratorLayer * layer) { if (!saveSelection(layer)) { m_errorMessages << i18n("Failed to save the selection for layer %1.", layer->name()); return false; } if (!saveFilterConfiguration(layer)) { m_errorMessages << i18n("Failed to save the generator configuration for layer %1.", layer->name()); return false; } if (!saveMetaData(layer)) { m_errorMessages << i18n("Failed to save the metadata for layer %1.", layer->name()); return false; } return visitAllInverse(layer); } bool KisKraSaveVisitor::visit(KisCloneLayer *layer) { // Clone layers do not have a profile if (!saveMetaData(layer)) { m_errorMessages << i18n("Failed to save the metadata for layer %1.", layer->name()); return false; } return visitAllInverse(layer); } bool KisKraSaveVisitor::visit(KisFilterMask *mask) { if (!mask->filter()) { m_errorMessages << i18n("Failed to save filter mask %1. It has no filter.", mask->name()); return false; } if (!saveSelection(mask)) { m_errorMessages << i18n("Failed to save the selection for filter mask %1.", mask->name()); return false; } if (!saveFilterConfiguration(mask)) { m_errorMessages << i18n("Failed to save the filter configuration for filter mask %1.", mask->name()); return false; } return true; } bool KisKraSaveVisitor::visit(KisTransformMask *mask) { QDomDocument doc("transform_params"); QDomElement root = doc.createElement("transform_params"); QDomElement main = doc.createElement("main"); main.setAttribute("id", mask->transformParams()->id()); QDomElement data = doc.createElement("data"); mask->transformParams()->toXML(&data); doc.appendChild(root); root.appendChild(main); root.appendChild(data); QString location = getLocation(mask, DOT_TRANSFORMCONFIG); if (m_store->open(location)) { QByteArray a = doc.toByteArray(); bool retval = m_store->write(a) == a.size(); if (!retval) { warnFile << "Could not write transform mask configuration"; } if (!m_store->close()) { warnFile << "Could not close store after writing transform mask configuration"; retval = false; } return retval; } return false; } bool KisKraSaveVisitor::visit(KisTransparencyMask *mask) { if (!saveSelection(mask)) { m_errorMessages << i18n("Failed to save the selection for transparency mask %1.", mask->name()); return false; } return true; } bool KisKraSaveVisitor::visit(KisSelectionMask *mask) { if (!saveSelection(mask)) { m_errorMessages << i18n("Failed to save the selection for local selection %1.", mask->name()); return false; } return true; } bool KisKraSaveVisitor::visit(KisColorizeMask *mask) { m_store->pushDirectory(); QString location = getLocation(mask, DOT_COLORIZE_MASK); bool result = m_store->enterDirectory(location); if (!result) { m_errorMessages << i18n("Failed to open %1.", location); return false; } if (!m_store->open("content.xml")) return false; KoStoreDevice storeDev(m_store); QDomDocument doc("doc"); QDomElement root = doc.createElement("colorize"); doc.appendChild(root); KisDomUtils::saveValue(&root, COLORIZE_KEYSTROKES_SECTION, QVector::fromList(mask->fetchKeyStrokesDirect())); QTextStream stream(&storeDev); stream << doc; if (!m_store->close()) return false; int i = 0; Q_FOREACH (const KisLazyFillTools::KeyStroke &stroke, mask->fetchKeyStrokesDirect()) { const QString fileName = QString("%1_%2").arg(COLORIZE_KEYSTROKE).arg(i++); savePaintDevice(stroke.dev, fileName); } savePaintDevice(mask->coloringProjection(), COLORIZE_COLORING_DEVICE); m_store->popDirectory(); return true; } QStringList KisKraSaveVisitor::errorMessages() const { return m_errorMessages; } struct SimpleDevicePolicy { bool write(KisPaintDeviceSP dev, KisPaintDeviceWriter &store) { return dev->write(store); } KoColor defaultPixel(KisPaintDeviceSP dev) const { return dev->defaultPixel(); } }; struct FramedDevicePolicy { FramedDevicePolicy(int frameId) : m_frameId(frameId) {} bool write(KisPaintDeviceSP dev, KisPaintDeviceWriter &store) { return dev->framesInterface()->writeFrame(store, m_frameId); } KoColor defaultPixel(KisPaintDeviceSP dev) const { return dev->framesInterface()->frameDefaultPixel(m_frameId); } int m_frameId; }; bool KisKraSaveVisitor::savePaintDevice(KisPaintDeviceSP device, QString location) { // Layer data KisConfig cfg(true); m_store->setCompressionEnabled(cfg.compressKra()); KisPaintDeviceFramesInterface *frameInterface = device->framesInterface(); QList frames; if (frameInterface) { frames = frameInterface->frames(); } if (!frameInterface || frames.count() <= 1) { savePaintDeviceFrame(device, location, SimpleDevicePolicy()); } else { KisRasterKeyframeChannel *keyframeChannel = device->keyframeChannel(); for (int i = 0; i < frames.count(); i++) { int id = frames[i]; QString frameFilename = getLocation(keyframeChannel->frameFilename(id)); Q_ASSERT(!frameFilename.isEmpty()); if (!savePaintDeviceFrame(device, frameFilename, FramedDevicePolicy(id))) { return false; } } } m_store->setCompressionEnabled(true); return true; } template bool KisKraSaveVisitor::savePaintDeviceFrame(KisPaintDeviceSP device, QString location, DevicePolicy policy) { if (m_store->open(location)) { if (!policy.write(device, *m_writer)) { device->disconnect(); m_store->close(); return false; } m_store->close(); } if (m_store->open(location + ".defaultpixel")) { m_store->write((char*)policy.defaultPixel(device).data(), device->colorSpace()->pixelSize()); m_store->close(); } return true; } bool KisKraSaveVisitor::saveAnnotations(KisLayer* layer) { if (!layer) return false; if (!layer->paintDevice()) return false; if (!layer->paintDevice()->colorSpace()) return false; if (layer->paintDevice()->colorSpace()->profile()) { const KoColorProfile *profile = layer->paintDevice()->colorSpace()->profile(); KisAnnotationSP annotation; if (profile) { QByteArray profileRawData = profile->rawData(); if (!profileRawData.isEmpty()) { if (profile->type() == "icc") { annotation = new KisAnnotation(ICC, profile->name(), profile->rawData()); } else { annotation = new KisAnnotation(PROFILE, profile->name(), profile->rawData()); } } } if (annotation) { // save layer profile if (m_store->open(getLocation(layer, DOT_ICC))) { m_store->write(annotation->annotation()); m_store->close(); } else { return false; } } } return true; } bool KisKraSaveVisitor::saveSelection(KisNode* node) { KisSelectionSP selection; if (node->inherits("KisMask")) { selection = static_cast(node)->selection(); } else if (node->inherits("KisAdjustmentLayer")) { selection = static_cast(node)->internalSelection(); } else if (node->inherits("KisGeneratorLayer")) { selection = static_cast(node)->internalSelection(); } else { return false; } bool retval = true; - if (selection->hasPixelSelection()) { + if (selection->hasNonEmptyPixelSelection()) { KisPaintDeviceSP dev = selection->pixelSelection(); if (!savePaintDevice(dev, getLocation(node, DOT_PIXEL_SELECTION))) { m_errorMessages << i18n("Failed to save the pixel selection data for layer %1.", node->name()); retval = false; } } - if (selection->hasShapeSelection()) { + if (selection->hasNonEmptyShapeSelection()) { m_store->pushDirectory(); retval = m_store->enterDirectory(getLocation(node, DOT_SHAPE_SELECTION)); if (retval) { KisShapeSelection* shapeSelection = dynamic_cast(selection->shapeSelection()); if (!shapeSelection) { retval = false; } if (retval && !shapeSelection->saveSelection(m_store)) { m_errorMessages << i18n("Failed to save the vector selection data for layer %1.", node->name()); retval = false; } } m_store->popDirectory(); } return retval; } bool KisKraSaveVisitor::saveFilterConfiguration(KisNode* node) { KisNodeFilterInterface *filterInterface = dynamic_cast(node); KisFilterConfigurationSP filter; if (filterInterface) { filter = filterInterface->filter(); } bool retval = false; if (filter) { QString location = getLocation(node, DOT_FILTERCONFIG); if (m_store->open(location)) { QString s = filter->toXML(); retval = (m_store->write(s.toUtf8(), qstrlen(s.toUtf8())) == qstrlen(s.toUtf8())); m_store->close(); } } return retval; } bool KisKraSaveVisitor::saveMetaData(KisNode* node) { if (!node->inherits("KisLayer")) return true; KisMetaData::Store* metadata = (static_cast(node))->metaData(); if (metadata->isEmpty()) return true; // Serialize all the types of metadata there are KisMetaData::IOBackend* backend = KisMetaData::IOBackendRegistry::instance()->get("xmp"); if (!backend->supportSaving()) { dbgFile << "Backend " << backend->id() << " does not support saving."; return false; } QString location = getLocation(node, QString(".") + backend->id() + DOT_METADATA); dbgFile << "going to save " << backend->id() << ", " << backend->name() << " to " << location; QBuffer buffer; // not that the metadata backends every return anything but true... bool retval = backend->saveTo(metadata, &buffer); if (!retval) { m_errorMessages << i18n("The metadata backend failed to save the metadata for %1", node->name()); } else { QByteArray data = buffer.data(); dbgFile << "\t information size is" << data.size(); if (data.size() > 0 && m_store->open(location)) { retval = m_store->write(data, data.size()); m_store->close(); } if (!retval) { m_errorMessages << i18n("Could not write for %1 metadata to the file.", node->name()); } } return retval; } QString KisKraSaveVisitor::getLocation(KisNode* node, const QString& suffix) { Q_ASSERT(m_nodeFileNames.contains(node)); return getLocation(m_nodeFileNames[node], suffix); } QString KisKraSaveVisitor::getLocation(const QString &filename, const QString& suffix) { QString location = m_external ? QString() : m_uri; location += m_name + LAYER_PATH + filename + suffix; return location; } diff --git a/plugins/impex/libkra/tests/kis_kra_saver_test.cpp b/plugins/impex/libkra/tests/kis_kra_saver_test.cpp index 8cf9e50351..5f29ab0282 100644 --- a/plugins/impex/libkra/tests/kis_kra_saver_test.cpp +++ b/plugins/impex/libkra/tests/kis_kra_saver_test.cpp @@ -1,553 +1,553 @@ /* * 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 #include "kis_transform_mask_params_interface.h" #include #include #include #include const QString KraMimetype = "application/x-krita"; void KisKraSaverTest::initTestCase() { KoResourcePaths::addResourceDir(ResourceType::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->cloneWithResourcesSnapshot(), 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->defaultConfiguration(KisGlobalResourcesInterface::instance()); 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->defaultConfiguration(KisGlobalResourcesInterface::instance()); 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().dynamicCast()); style->dropShadow()->setAngle(180); style->dropShadow()->setUseGlobalLight(true); layer2->setLayerStyle(style->clone().dynamicCast()); style->dropShadow()->setAngle(90); style->dropShadow()->setUseGlobalLight(false); layer3->setLayerStyle(style->clone().dynamicCast()); 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->isPinnedToTimeline()); layer1->setPinnedToTimeline(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->isPinnedToTimeline()); } #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); + selection->convertToVectorSelectionNoUndo(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(newMask->selection()->hasNonEmptyShapeSelection()); QVERIFY(chk.testPassed()); } void KisKraSaverTest::testExportToReadonly() { TestUtil::testExportToReadonly(QString(FILES_DATA_DIR), KraMimetype); } KISTEST_MAIN(KisKraSaverTest) diff --git a/plugins/impex/libkra/tests/util.h b/plugins/impex/libkra/tests/util.h index ce4c705662..1481543127 100644 --- a/plugins/impex/libkra/tests/util.h +++ b/plugins/impex/libkra/tests/util.h @@ -1,222 +1,222 @@ /* * Copyright (c) 2008 Boudewijn Rempt boud@valdyas.org * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _UTIL_H_ #define _UTIL_H_ #include #include #include #include #include #include #include #include #include #include "kis_types.h" #include "filter/kis_filter_registry.h" #include "filter/kis_filter_configuration.h" #include "filter/kis_filter.h" #include "KisPart.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 "kis_default_bounds.h" #include "kis_transform_mask_params_interface.h" #include KisSelectionSP createPixelSelection(KisPaintDeviceSP paintDevice) { KisSelectionSP pixelSelection = new KisSelection(new KisSelectionDefaultBounds(paintDevice)); KisFillPainter gc(pixelSelection->pixelSelection()); gc.fillRect(10, 10, 200, 200, KoColor(gc.device()->colorSpace())); gc.fillRect(150, 150, 200, 200, KoColor(QColor(100, 100, 100, 100), gc.device()->colorSpace())); gc.end(); return pixelSelection; } KisSelectionSP createVectorSelection(KisPaintDeviceSP paintDevice, KisImageSP image, KoShapeControllerBase *shapeController) { - KisSelectionSP vectorSelection = new KisSelection(new KisSelectionDefaultBounds(paintDevice)); + KisSelectionSP selection = new KisSelection(new KisSelectionDefaultBounds(paintDevice)); KoPathShape* path = new KoPathShape(); path->setShapeId(KoPathShapeId); path->moveTo(QPointF(10, 10)); path->lineTo(QPointF(10, 10) + QPointF(100, 0)); path->lineTo(QPointF(100, 100)); path->lineTo(QPointF(10, 10) + QPointF(0, 100)); path->close(); path->normalize(); - KisShapeSelection* shapeSelection = new KisShapeSelection(shapeController, image, vectorSelection); + KisShapeSelection* shapeSelection = new KisShapeSelection(shapeController, image, selection); shapeSelection->addShape(path); - vectorSelection->setShapeSelection(shapeSelection); + selection->convertToVectorSelectionNoUndo(shapeSelection); - return vectorSelection; + return selection; } QTransform createTestingTransform() { return QTransform(1,2,3,4,5,6,7,8,9); } KisDocument* createCompleteDocument() { KisImageSP image = new KisImage(0, 1024, 1024, KoColorSpaceRegistry::instance()->rgb8(), "test for roundtrip"); KisDocument *doc = qobject_cast(KisPart::instance()->createDocument()); doc->setCurrentImage(image); doc->documentInfo()->setAboutInfo("title", image->objectName()); KisGroupLayerSP group1 = new KisGroupLayer(image, "group1", 50); KisGroupLayerSP group2 = new KisGroupLayer(image, "group2", 100); KisPaintLayerSP paintLayer1 = new KisPaintLayer(image, "paintlayer1", OPACITY_OPAQUE_U8); paintLayer1->setUserLocked(true); QBitArray channelFlags(4); channelFlags[0] = true; channelFlags[2] = true; paintLayer1->setChannelFlags(channelFlags); { KisFillPainter gc(paintLayer1->paintDevice()); gc.fillRect(10, 10, 200, 200, KoColor(Qt::red, paintLayer1->paintDevice()->colorSpace())); gc.end(); } KisPaintLayerSP paintLayer2 = new KisPaintLayer(image, "paintlayer2", OPACITY_TRANSPARENT_U8, KoColorSpaceRegistry::instance()->lab16()); paintLayer2->setVisible(false); { KisFillPainter gc(paintLayer2->paintDevice()); gc.fillRect(0, 0, 900, 1024, KoColor(QColor(10, 20, 30), paintLayer2->paintDevice()->colorSpace())); gc.end(); } KisCloneLayerSP cloneLayer1 = new KisCloneLayer(group1, image, "clonelayer1", 150); cloneLayer1->setX(100); cloneLayer1->setY(100); KisSelectionSP pixelSelection = createPixelSelection(paintLayer1->paintDevice()); KisFilterConfigurationSP kfc = KisFilterRegistry::instance()->get("pixelize")->defaultConfiguration(KisGlobalResourcesInterface::instance()); Q_ASSERT(kfc); KisAdjustmentLayerSP adjustmentLayer1 = new KisAdjustmentLayer(image, "adjustmentLayer1", kfc->cloneWithResourcesSnapshot(), pixelSelection); KisSelectionSP vectorSelection = createVectorSelection(paintLayer2->paintDevice(), image, doc->shapeController()); KisAdjustmentLayerSP adjustmentLayer2 = new KisAdjustmentLayer(image, "adjustmentLayer2", kfc->cloneWithResourcesSnapshot(), vectorSelection); image->addNode(paintLayer1); image->addNode(group1); image->addNode(paintLayer2, group1); image->addNode(group2); image->addNode(cloneLayer1, group2); image->addNode(adjustmentLayer1, group2); // KoShapeContainer * parentContainer = // dynamic_cast(doc->shapeForNode(group1)); KoPathShape* path = new KoPathShape(); path->setShapeId(KoPathShapeId); path->moveTo(QPointF(10, 10)); path->lineTo(QPointF(10, 10) + QPointF(100, 0)); path->lineTo(QPointF(100, 100)); path->lineTo(QPointF(10, 10) + QPointF(0, 100)); path->close(); path->normalize(); KisShapeLayerSP shapeLayer = new KisShapeLayer(doc->shapeController(), image, "shapeLayer1", 75); shapeLayer->addShape(path); image->addNode(shapeLayer, group1); image->addNode(adjustmentLayer2, group1); KisFilterMaskSP filterMask1 = new KisFilterMask(); filterMask1->setName("filterMask1"); kfc = KisFilterRegistry::instance()->get("pixelize")->defaultConfiguration(KisGlobalResourcesInterface::instance()); filterMask1->setFilter(kfc->cloneWithResourcesSnapshot()); kfc = 0; // kfc cannot be shared! filterMask1->setSelection(createPixelSelection(paintLayer1->paintDevice())); image->addNode(filterMask1, paintLayer1); KisFilterMaskSP filterMask2 = new KisFilterMask(); filterMask2->setName("filterMask2"); kfc = KisFilterRegistry::instance()->get("pixelize")->defaultConfiguration(KisGlobalResourcesInterface::instance()); filterMask2->setFilter(kfc); kfc = 0; // kfc cannot be shared! filterMask2->setSelection(createVectorSelection(paintLayer2->paintDevice(), image, doc->shapeController())); image->addNode(filterMask2, paintLayer2); KisTransparencyMaskSP transparencyMask1 = new KisTransparencyMask(); transparencyMask1->setName("transparencyMask1"); transparencyMask1->setSelection(createPixelSelection(paintLayer1->paintDevice())); image->addNode(transparencyMask1, group1); KisTransparencyMaskSP transparencyMask2 = new KisTransparencyMask(); transparencyMask2->setName("transparencyMask2"); transparencyMask2->setSelection(createPixelSelection(paintLayer1->paintDevice())); image->addNode(transparencyMask2, group2); KisSelectionMaskSP selectionMask1 = new KisSelectionMask(image); image->addNode(selectionMask1, paintLayer1); selectionMask1->setName("selectionMask1"); selectionMask1->setSelection(createPixelSelection(paintLayer1->paintDevice())); KisSelectionMaskSP selectionMask2 = new KisSelectionMask(image); selectionMask2->setName("selectionMask2"); selectionMask2->setSelection(createPixelSelection(paintLayer2->paintDevice())); image->addNode(selectionMask2, paintLayer2); KisTransformMaskSP transformMask = new KisTransformMask(); transformMask->setName("testTransformMask"); transformMask->setTransformParams(KisTransformMaskParamsInterfaceSP( new KisDumbTransformMaskParams(createTestingTransform()))); image->addNode(transformMask, paintLayer2); return doc; } KisDocument *createEmptyDocument() { KisImageSP image = new KisImage(0, 1024, 1024, KoColorSpaceRegistry::instance()->rgb8(), "test for roundtrip"); KisDocument *doc = qobject_cast(KisPart::instance()->createDocument()); doc->setCurrentImage(image); doc->documentInfo()->setAboutInfo("title", image->objectName()); return doc; } #endif diff --git a/plugins/paintops/libpaintop/forms/wdgautobrush.ui b/plugins/paintops/libpaintop/forms/wdgautobrush.ui index a9eccf67c1..b106b8af4d 100644 --- a/plugins/paintops/libpaintop/forms/wdgautobrush.ui +++ b/plugins/paintops/libpaintop/forms/wdgautobrush.ui @@ -1,432 +1,435 @@ KisWdgAutoBrush 0 0 429 - 279 + 288 0 0 0 0 0 0 8 110 110 110 110 0 0 Mask Type: 0 0 Shape: Qt::ClickFocus Circle Square The border of the brush will be smoothed to avoid aliasing Anti-alias Qt::Vertical QSizePolicy::Expanding 20 13 Qt::Vertical QSizePolicy::Expanding 20 17 - + 200 0 Angle: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Diameter: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Ratio: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - + 0 0 0 0 - 1 + 0 0 0 1024 1024 + + + Fade 4 Horizontal: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - + - + Vertical: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - + - + 0 0 80 0 Softness: Qt::AlignHCenter|Qt::AlignTop - + 0 0 0 0 10000 10000 Qt::Horizontal 40 20 Randomness: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Spikes: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - + + 200 0 - + - + - + Density: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - + Spacing: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter KisSliderSpinBox QWidget
kis_slider_spin_box.h
1
KoAspectButton QWidget
KoAspectButton.h
1
KisDoubleSliderSpinBox QWidget
kis_slider_spin_box.h
1
KisCurveWidget QWidget
widgets/kis_curve_widget.h
1
KisSpacingSelectionWidget QWidget
kis_spacing_selection_widget.h
1
diff --git a/plugins/paintops/libpaintop/kis_dynamic_sensor.h b/plugins/paintops/libpaintop/kis_dynamic_sensor.h index e06e98ea2f..d1ba080c3e 100644 --- a/plugins/paintops/libpaintop/kis_dynamic_sensor.h +++ b/plugins/paintops/libpaintop/kis_dynamic_sensor.h @@ -1,229 +1,229 @@ /* * Copyright (c) 2006 Cyrille Berger * Copyright (c) 2011 Lukáš Tvrdý * * 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_DYNAMIC_SENSOR_H_ #define _KIS_DYNAMIC_SENSOR_H_ #include #include #include #include #include "kis_serializable_configuration.h" #include "kis_curve_label.h" #include #include #include class QWidget; class KisPaintInformation; -const KoID FuzzyPerDabId("fuzzy", ki18n("Fuzzy Dab")); ///< generate a random number -const KoID FuzzyPerStrokeId("fuzzystroke", ki18n("Fuzzy Stroke")); ///< generate a random number -const KoID SpeedId("speed", ki18n("Speed")); ///< generate a number depending on the speed of the cursor -const KoID FadeId("fade", ki18n("Fade")); ///< generate a number that increase every time you call it (e.g. per dab) -const KoID DistanceId("distance", ki18n("Distance")); ///< generate a number that increase with distance -const KoID TimeId("time", ki18n("Time")); ///< generate a number that increase with time -const KoID DrawingAngleId("drawingangle", ki18n("Drawing angle")); ///< number depending on the angle -const KoID RotationId("rotation", ki18n("Rotation")); ///< rotation coming from the device -const KoID PressureId("pressure", ki18n("Pressure")); ///< number depending on the pressure -const KoID PressureInId("pressurein", ki18n("PressureIn")); ///< number depending on the pressure -const KoID XTiltId("xtilt", ki18n("X-Tilt")); ///< number depending on X-tilt -const KoID YTiltId("ytilt", ki18n("Y-Tilt")); ///< number depending on Y-tilt +const KoID FuzzyPerDabId("fuzzy", ki18nc("Context: dynamic sensors", "Fuzzy Dab")); ///< generate a random number +const KoID FuzzyPerStrokeId("fuzzystroke", ki18nc("Context: dynamic sensors", "Fuzzy Stroke")); ///< generate a random number +const KoID SpeedId("speed", ki18nc("Context: dynamic sensors", "Speed")); ///< generate a number depending on the speed of the cursor +const KoID FadeId("fade", ki18nc("Context: dynamic sensors", "Fade")); ///< generate a number that increase every time you call it (e.g. per dab) +const KoID DistanceId("distance", ki18nc("Context: dynamic sensors", "Distance")); ///< generate a number that increase with distance +const KoID TimeId("time", ki18nc("Context: dynamic sensors", "Time")); ///< generate a number that increase with time +const KoID DrawingAngleId("drawingangle", ki18nc("Context: dynamic sensors", "Drawing angle")); ///< number depending on the angle +const KoID RotationId("rotation", ki18nc("Context: dynamic sensors", "Rotation")); ///< rotation coming from the device +const KoID PressureId("pressure", ki18nc("Context: dynamic sensors", "Pressure")); ///< number depending on the pressure +const KoID PressureInId("pressurein", ki18nc("Context: dynamic sensors", "PressureIn")); ///< number depending on the pressure +const KoID XTiltId("xtilt", ki18nc("Context: dynamic sensors", "X-Tilt")); ///< number depending on X-tilt +const KoID YTiltId("ytilt", ki18nc("Context: dynamic sensors", "Y-Tilt")); ///< number depending on Y-tilt /** * "TiltDirection" and "TiltElevation" parameters are written to * preset files as "ascension" and "declination" to keep backward * compatibility with older presets from the days when they were called * differently. */ -const KoID TiltDirectionId("ascension", ki18n("Tilt direction")); /// < number depending on the X and Y tilt, tilt direction is 0 when stylus nib points to you and changes clockwise from -180 to +180. -const KoID TiltElevationId("declination", ki18n("Tilt elevation")); /// < tilt elevation is 90 when stylus is perpendicular to tablet and 0 when it's parallel to tablet +const KoID TiltDirectionId("ascension", ki18nc("Context: dynamic sensors", "Tilt direction")); /// < number depending on the X and Y tilt, tilt direction is 0 when stylus nib points to you and changes clockwise from -180 to +180. +const KoID TiltElevationId("declination", ki18nc("Context: dynamic sensors", "Tilt elevation")); /// < tilt elevation is 90 when stylus is perpendicular to tablet and 0 when it's parallel to tablet -const KoID PerspectiveId("perspective", ki18n("Perspective")); ///< number depending on the distance on the perspective grid -const KoID TangentialPressureId("tangentialpressure", ki18n("Tangential pressure")); ///< the wheel on an airbrush device +const KoID PerspectiveId("perspective", ki18nc("Context: dynamic sensors", "Perspective")); ///< number depending on the distance on the perspective grid +const KoID TangentialPressureId("tangentialpressure", ki18nc("Context: dynamic sensors", "Tangential pressure")); ///< the wheel on an airbrush device const KoID SensorsListId("sensorslist", "SHOULD NOT APPEAR IN THE UI !"); ///< this a non user-visible sensor that can store a list of other sensors, and multiply their output class KisDynamicSensor; typedef KisSharedPtr KisDynamicSensorSP; enum DynamicSensorType { FUZZY_PER_DAB, FUZZY_PER_STROKE, SPEED, FADE, DISTANCE, TIME, ANGLE, ROTATION, PRESSURE, XTILT, YTILT, TILT_DIRECTION, TILT_ELEVATATION, PERSPECTIVE, TANGENTIAL_PRESSURE, SENSORS_LIST, PRESSURE_IN, UNKNOWN = 255 }; /** * Sensors are used to extract from KisPaintInformation a single * double value which can be used to control the parameters of * a brush. */ class PAINTOP_EXPORT KisDynamicSensor : public KisSerializableConfiguration { public: enum ParameterSign { NegativeParameter = -1, UnSignedParameter = 0, PositiveParameter = 1 }; protected: KisDynamicSensor(DynamicSensorType type); public: ~KisDynamicSensor() override; /** * @return the value of this sensor for the given KisPaintInformation */ qreal parameter(const KisPaintInformation& info); /** * @return the value of this sensor for the given KisPaintInformation * curve -- a custom, temporary curve that should be used instead of the one for the sensor * customCurve -- if it's a new curve or not; should always be true if the function is called from outside * (aka not in parameter(info) function) */ qreal parameter(const KisPaintInformation& info, const KisCubicCurve curve, const bool customCurve); /** * This function is call before beginning a stroke to reset the sensor. * Default implementation does nothing. */ virtual void reset(); /** * @param parent the parent QWidget * @param selector is a \ref QWidget that contains a signal called "parametersChanged()" */ virtual QWidget* createConfigurationWidget(QWidget* parent, QWidget* selector); /** * Creates a sensor from its identifier. */ static KisDynamicSensorSP id2Sensor(const KoID& id, const QString &parentOptionName); static KisDynamicSensorSP id2Sensor(const QString& s, const QString &parentOptionName) { return id2Sensor(KoID(s), parentOptionName); } static DynamicSensorType id2Type(const KoID& id); static DynamicSensorType id2Type(const QString& s) { return id2Type(KoID(s)); } /** * type2Sensor creates a new sensor for the give type */ static KisDynamicSensorSP type2Sensor(DynamicSensorType sensorType, const QString &parentOptionName); static QString minimumLabel(DynamicSensorType sensorType); static QString maximumLabel(DynamicSensorType sensorType, int max = -1); static int minimumValue(DynamicSensorType sensorType); static int maximumValue(DynamicSensorType sensorType, int max = -1); static QString valueSuffix(DynamicSensorType sensorType); static KisDynamicSensorSP createFromXML(const QString&, const QString &parentOptionName); static KisDynamicSensorSP createFromXML(const QDomElement&, const QString &parentOptionName); /** * @return the list of sensors */ static QList sensorsIds(); static QList sensorsTypes(); /** * @return the identifier of this sensor */ static QString id(DynamicSensorType sensorType); using KisSerializableConfiguration::fromXML; using KisSerializableConfiguration::toXML; void toXML(QDomDocument&, QDomElement&) const override; void fromXML(const QDomElement&) override; void setCurve(const KisCubicCurve& curve); const KisCubicCurve& curve() const; void removeCurve(); bool hasCustomCurve() const; void setActive(bool active); bool isActive() const; virtual bool dependsOnCanvasRotation() const; virtual bool isAdditive() const; virtual bool isAbsoluteRotation() const; inline DynamicSensorType sensorType() const { return m_type; } /** * @return the currently set length or -1 if not relevant */ int length() { return m_length; } public: static inline qreal scalingToAdditive(qreal x) { return -1.0 + 2.0 * x; } static inline qreal additiveToScaling(qreal x) { return 0.5 * (1.0 + x); } protected: virtual qreal value(const KisPaintInformation& info) = 0; int m_length; private: Q_DISABLE_COPY(KisDynamicSensor) DynamicSensorType m_type; bool m_customCurve; KisCubicCurve m_curve; bool m_active; }; #endif diff --git a/plugins/python/CMakeLists.txt b/plugins/python/CMakeLists.txt index 3e46581bfd..88e5a0db60 100644 --- a/plugins/python/CMakeLists.txt +++ b/plugins/python/CMakeLists.txt @@ -1,118 +1,120 @@ # Copyright (C) 2012, 2013 Shaheed Haque # Copyright (C) 2013 Alex Turbov # Copyright (C) 2014-2016 Boudewijn Rempt # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. include(CMakeParseArguments) # # Simple helper function to install plugin and related files # having only a name of the plugin... # (just to reduce syntactic noise when a lot of plugins get installed) # function(install_pykrita_plugin name) set(_options) set(_one_value_args) set(_multi_value_args PATTERNS FILE) cmake_parse_arguments(install_pykrita_plugin "${_options}" "${_one_value_args}" "${_multi_value_args}" ${ARGN}) if(NOT name) message(FATAL_ERROR "Plugin filename is not given") endif() if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${name}.py) install(FILES kritapykrita_${name}.desktop DESTINATION ${DATA_INSTALL_DIR}/krita/pykrita) foreach(_f ${name}.py ${name}.ui ${install_pykrita_plugin_FILE}) if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${_f}) install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${_f} DESTINATION ${DATA_INSTALL_DIR}/krita/pykrita) endif() endforeach() elseif(IS_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/${name}) install(FILES ${name}/kritapykrita_${name}.desktop DESTINATION ${DATA_INSTALL_DIR}/krita/pykrita) install( DIRECTORY ${name} DESTINATION ${DATA_INSTALL_DIR}/krita/pykrita FILES_MATCHING PATTERN "*.py" PATTERN "*.ui" PATTERN "*.txt" PATTERN "*.csv" PATTERN "*.html" PATTERN "__pycache__*" EXCLUDE PATTERN "tests*" EXCLUDE ) # TODO Is there any way to form a long PATTERN options string # and use it in a single install() call? # NOTE Install specified patterns one-by-one... foreach(_pattern ${install_pykrita_plugin_PATTERNS}) install( DIRECTORY ${name} DESTINATION ${DATA_INSTALL_DIR}/krita/pykrita FILES_MATCHING PATTERN "${_pattern}" PATTERN "__pycache__*" EXCLUDE PATTERN "tests*" EXCLUDE ) endforeach() else() message(FATAL_ERROR "Do not know what to do with ${name}") endif() endfunction() install_pykrita_plugin(hello) install_pykrita_plugin(assignprofiledialog) install_pykrita_plugin(scripter) install_pykrita_plugin(colorspace) install_pykrita_plugin(documenttools) install_pykrita_plugin(filtermanager) install_pykrita_plugin(exportlayers) +install_pykrita_plugin(batch_exporter) #install_pykrita_plugin(highpass) install_pykrita_plugin(tenbrushes) install_pykrita_plugin(tenscripts) #install_pykrita_plugin(palette_docker) # Needs fixing -> bug 405194 install_pykrita_plugin(quick_settings_docker) install_pykrita_plugin(lastdocumentsdocker) # install_pykrita_plugin(scriptdocker) install_pykrita_plugin(comics_project_management_tools) install_pykrita_plugin(krita_script_starter) install_pykrita_plugin(plugin_importer) install_pykrita_plugin(mixer_slider_docker) +install_pykrita_plugin(channels2layers) # if(PYTHON_VERSION_MAJOR VERSION_EQUAL 3) # install_pykrita_plugin(cmake_utils) # install_pykrita_plugin(js_utils PATTERNS "*.json") # install_pykrita_plugin(expand PATTERNS "*.expand" "templates/*.tpl") # endif() install( FILES hello/hello.action tenbrushes/tenbrushes.action tenscripts/tenscripts.action plugin_importer/plugin_importer.action DESTINATION ${DATA_INSTALL_DIR}/krita/actions) install( DIRECTORY libkritapykrita DESTINATION ${DATA_INSTALL_DIR}/krita/pykrita FILES_MATCHING PATTERN "*.py" PATTERN "__pycache__*" EXCLUDE ) diff --git a/plugins/python/assignprofiledialog/kritapykrita_assignprofiledialog.desktop b/plugins/python/assignprofiledialog/kritapykrita_assignprofiledialog.desktop index fb4a784a97..968d3173f6 100644 --- a/plugins/python/assignprofiledialog/kritapykrita_assignprofiledialog.desktop +++ b/plugins/python/assignprofiledialog/kritapykrita_assignprofiledialog.desktop @@ -1,59 +1,60 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=assignprofiledialog X-Python-2-Compatible=true X-Krita-Manual=Manual.html Name=Assign Profile to Image Name[ar]=إسناد اللاحات إلى الصور Name[ca]=Assigna un perfil a una imatge Name[ca@valencia]=Assigna un perfil a una imatge Name[cs]=Přiřadit obrázku profil Name[el]=Αντιστοίχιση προφίλ σε εικόνα Name[en_GB]=Assign Profile to Image Name[es]=Asignar perfil a imagen Name[et]=Pildile profiili omistamine Name[eu]=Esleitu profila irudiari Name[fi]=Liitä kuvaan profiili Name[fr]=Attribuer un profil à l'image Name[gl]=Asignar un perfil á imaxe Name[is]=Úthluta litasniði á myndina Name[it]=Assegna profilo a immagine Name[ko]=이미지에 프로필 할당 Name[nl]=Profiel aan afbeelding toewijzen Name[nn]=Tildel profil til bilete Name[pl]=Przypisz profil do obrazu Name[pt]=Atribuir um Perfil à Imagem Name[pt_BR]=Atribuir perfil a imagem +Name[sk]=Priradiť profil obrázku Name[sv]=Tilldela profil till bild Name[tr]=Görüntüye Profil Ata Name[uk]=Призначити профіль до зображення Name[x-test]=xxAssign Profile to Imagexx Name[zh_CN]=为图像指定特性文件 Name[zh_TW]=指定設定檔到圖像 Comment=Assign a profile to an image without converting it. Comment[ar]=أسنِد لاحة إلى صورة دون تحويلها. Comment[ca]=Assigna un perfil a una imatge sense convertir-la. Comment[ca@valencia]=Assigna un perfil a una imatge sense convertir-la. Comment[el]=Αντιστοιχίζει ένα προφίλ σε μια εικόνα χωρίς μετατροπή. Comment[en_GB]=Assign a profile to an image without converting it. Comment[es]=Asignar un perfil a una imagen sin convertirla. Comment[et]=Pildile profiili omistamine ilma seda teisendamata. Comment[eu]=Esleitu profil bat irudi bati hura bihurtu gabe. Comment[fi]=Liitä kuvaan profiili muuntamatta kuvaa Comment[fr]=Attribuer un profil à une image sans la convertir. Comment[gl]=Asignar un perfil a unha imaxe sen convertela. Comment[is]=Úthluta litasniði á myndina án þess að umbreyta henni. Comment[it]=Assegna un profilo a un'immagine senza convertirla. Comment[ko]=프로필을 변환하지 않고 이미지에 할당합니다. Comment[nl]=Een profiel aan een afbeelding toewijzen zonder het te converteren. Comment[nn]=Tildel fargeprofil til eit bilete utan å konvertera det til profilen Comment[pl]=Przypisz profil do obrazu bez jego przekształcania. Comment[pt]=Atribui um perfil à imagem sem a converter. Comment[pt_BR]=Atribui um perfil para uma imagem sem convertê-la. Comment[sv]=Tilldela en profil till en bild utan att konvertera den. Comment[tr]=Bir görüntüye, görüntüyü değiştirmeden bir profil ata. Comment[uk]=Призначити профіль до зображення без його перетворення. Comment[x-test]=xxAssign a profile to an image without converting it.xx Comment[zh_CN]=仅为图像指定特性文件,不转换其色彩空间 Comment[zh_TW]=將設定檔指定給圖像,而不進行轉換。 diff --git a/plugins/python/batch_exporter/COATools.py b/plugins/python/batch_exporter/COATools.py new file mode 100644 index 0000000000..02214ed5cd --- /dev/null +++ b/plugins/python/batch_exporter/COATools.py @@ -0,0 +1,99 @@ +import os +import json + + +class COAToolsFormat: + def __init__(self, cfg, statusBar): + self.cfg = cfg + self.statusBar = statusBar + self.reset() + + def reset(self): + self.nodes = [] + + def showError(self, msg): + msg, timeout = (self.cfg["error"]["msg"].format(msg), self.cfg["error"]["timeout"]) + self.statusBar.showMessage(msg, timeout) + + def collect(self, node): + print("COAToolsFormat collecting %s" % (node.name)) + self.nodes.append(node) + + def remap(self, oldValue, oldMin, oldMax, newMin, newMax): + if oldMin == newMin and oldMax == newMax: + return oldValue + return (((oldValue - oldMin) * (newMax - newMin)) / (oldMax - oldMin)) + newMin + + def save(self, output_dir=""): + """ + Parses layers configured to export to COA Tools and builds the JSON data + COA Tools need to import the files + """ + # For each top-level node (Group Layer) + cfg = self.cfg + export_dir = output_dir + for wn in self.nodes: + children = wn.children + path = wn.path + + if path != "": + export_dir = path + + print("COAToolsFormat exporting %d items from %s" % (len(children), wn.name)) + try: + if len(children) <= 0: + raise ValueError(wn.name, "has no children to export") + + coa_data = {"name": wn.name, "nodes": []} + print("COAToolsFormat exporting %s to %s" % (wn.name, export_dir)) + for idx, child in enumerate(children): + sheet_meta = dict() + if child.coa != "": + fn, sheet_meta = child.saveCOASpriteSheet(export_dir) + else: + fn = child.saveCOA(export_dir) + + node = child.node + coords = node.bounds().getCoords() + relative_coords = coords + + parent_node = node.parentNode() + parent_coords = parent_node.bounds().getCoords() + relative_coords = [coords[0] - parent_coords[0], coords[1] - parent_coords[1]] + + p_width = parent_coords[2] - parent_coords[0] + p_height = parent_coords[3] - parent_coords[1] + + tiles_x, tiles_y = 1, 1 + if len(sheet_meta) > 0: + tiles_x, tiles_y = sheet_meta["tiles_x"], sheet_meta["tiles_y"] + + coa_entry = { + "children": [], + "frame_index": 0, + "name": child.name, + "node_path": child.name, + "offset": [-p_width / 2, p_height / 2], + "opacity": self.remap(node.opacity(), 0, 255, 0, 1), + "pivot_offset": [0.0, 0.0], + "position": relative_coords, + "resource_path": fn.replace( + export_dir + os.path.sep + cfg["outDir"] + os.path.sep, "" + ), + "rotation": 0.0, + "scale": [1.0, 1.0], + "tiles_x": tiles_x, + "tiles_y": tiles_y, + "type": "SPRITE", + "z": idx - len(children) + 1, + } + coa_data["nodes"].append(coa_entry) + + json_data = json.dumps(coa_data, sort_keys=True, indent=4, separators=(",", ": ")) + with open( + export_dir + os.path.sep + cfg["outDir"] + os.path.sep + wn.name + ".json", "w" + ) as fh: + fh.write(json_data) + + except ValueError as e: + self.showError(e) diff --git a/plugins/python/batch_exporter/Config.py b/plugins/python/batch_exporter/Config.py new file mode 100644 index 0000000000..a7b31281dc --- /dev/null +++ b/plugins/python/batch_exporter/Config.py @@ -0,0 +1,15 @@ +import re +from collections import OrderedDict + + +CONFIG = { + "outDir": "export", + "rootPat": r"^root", + "sym": r"[^a-zA-Z0-9_-]", + "error": {"msg": "ERROR: {}", "timeout": 8000}, + "done": {"msg": "DONE: {}", "timeout": 5000}, + "delimiters": OrderedDict((("assign", "="), ("separator", ","))), # yapf: disable + "meta": {"c": [""], "e": ["png"], "m": [0], "p": [""], "s": [100]}, +} +CONFIG["rootPat"] = re.compile(CONFIG["rootPat"]) +CONFIG["sym"] = re.compile(CONFIG["sym"]) diff --git a/plugins/python/batch_exporter/Infrastructure.py b/plugins/python/batch_exporter/Infrastructure.py new file mode 100644 index 0000000000..90ff682514 --- /dev/null +++ b/plugins/python/batch_exporter/Infrastructure.py @@ -0,0 +1,363 @@ +import os +import re +from collections import OrderedDict +from functools import partial +from itertools import groupby, product, starmap, tee + +from krita import Krita +from PyQt5.QtCore import QSize +from PyQt5.QtGui import QColor, QImage, QPainter + +from .Utils import flip, kickstart +from .Utils.Export import exportPath, sanitize +from .Utils.Tree import pathFS + +KI = Krita.instance() + + +def nodeToImage(wnode): + """ + Returns an QImage 8-bit sRGB + """ + SRGB_PROFILE = "sRGB-elle-V2-srgbtrc.icc" + [x, y, w, h] = wnode.bounds + + is_srgb = ( + wnode.node.colorModel() == "RGBA" + and wnode.node.colorDepth() == "U8" + and wnode.node.colorProfile().lower() == SRGB_PROFILE.lower() + ) + + if is_srgb: + pixel_data = wnode.node.projectionPixelData(x, y, w, h).data() + else: + temp_node = wnode.node.duplicate() + temp_node.setColorSpace("RGBA", "U8", SRGB_PROFILE) + pixel_data = temp_node.projectionPixelData(x, y, w, h).data() + + return QImage(pixel_data, w, h, QImage.Format_ARGB32) + + +def expandAndFormat(img, margin=0, is_jpg=False): + """ + Draws the image with transparent background if `is_jpg == False`, otherwise with a white background. + It's done in a single function, to avoid creating extra images + """ + if not margin and not is_jpg: + return img + corner = QSize(margin, margin) + white = QColor(255, 255, 255) if is_jpg else QColor(255, 255, 255, 0) + canvas = QImage( + img.size() + corner * 2, QImage.Format_RGB32 if is_jpg else QImage.Format_ARGB32 + ) + canvas.fill(white) + p = QPainter(canvas) + p.drawImage(margin, margin, img) + return canvas + + +class WNode: + """ + Wrapper around Krita's Node class, that represents a layer. + Adds support for export metadata and methods to export the layer + based on its metadata. + See the meta property for a list of supported metadata. + """ + + def __init__(self, cfg, node): + self.cfg = cfg + self.node = node + + def __bool__(self): + return bool(self.node) + + @property + def name(self): + a = self.cfg["delimiters"]["assign"] + name = self.node.name() + name = name.split() + name = filter(lambda n: a not in n, name) + name = "_".join(name) + return sanitize(name) + + @property + def meta(self): + a, s = self.cfg["delimiters"].values() + meta = self.node.name().strip().split(a) + meta = starmap(lambda fst, snd: (fst[-1], snd.split()[0]), zip(meta[:-1], meta[1:])) + meta = filter(lambda m: m[0] in self.cfg["meta"].keys(), meta) + meta = OrderedDict((k, v.lower().split(s)) for k, v in meta) + meta.update({k: list(map(int, v)) for k, v in meta.items() if k in "ms"}) + meta.setdefault("c", self.cfg["meta"]["c"]) # coa_tools + meta.setdefault("e", self.cfg["meta"]["e"]) # extension + meta.setdefault("m", self.cfg["meta"]["m"]) # margin + meta.setdefault("p", self.cfg["meta"]["p"]) # path + meta.setdefault("s", self.cfg["meta"]["s"]) # scale + return meta + + @property + def path(self): + return self.meta["p"][0] + + @property + def coa(self): + return self.meta["c"][0] + + @property + def parent(self): + return WNode(self.cfg, self.node.parentNode()) + + @property + def children(self): + return [WNode(self.cfg, n) for n in self.node.childNodes()] + + @property + def type(self): + return self.node.type() + + @property + def position(self): + bounds = self.node.bounds() + return bounds.x(), bounds.y() + + @property + def bounds(self): + bounds = self.node.bounds() + return bounds.x(), bounds.y(), bounds.width(), bounds.height() + + @property + def size(self): + bounds = self.node.bounds() + return bounds.width(), bounds.height() + + def hasDestination(self): + return "d=" in self.node.name() + + def isExportable(self): + return ( + self.isPaintLayer() or self.isGroupLayer() or self.isFileLayer() or self.isVectorLayer() + ) # yapf: disable + + def isMarked(self): + return "e=" in self.node.name() + + def isLayer(self): + return "layer" in self.type + + def isMask(self): + return "mask" in self.type + + def isPaintLayer(self): + return self.type == "paintlayer" + + def isGroupLayer(self): + return self.type == "grouplayer" + + def isFileLayer(self): + return self.type == "filelayer" + + def isFilterLayer(self): + return self.type == "filterlayer" + + def isFillLayer(self): + return self.type == "filllayer" + + def isCloneLayer(self): + return self.type == "clonelayer" + + def isVectorLayer(self): + return self.type == "vectorlayer" + + def isTransparencyMask(self): + return self.type == "transparencyMask" + + def isFilterMask(self): + return self.type == "filtermask" + + def isTransformMask(self): + return self.type == "transformmask" + + def isSelectionMask(self): + return self.type == "selectionmask" + + def isColorizeMask(self): + return self.type == "colorizemask" + + def rename(self, pattern): + """ + Renames the layer, scanning for patterns in the user's input trying to preserve metadata. + Patterns have the form meta_name=value, + E.g. s=50,100 to tell the tool to export two copies of the layer at 50% and 100% of its size + This function will only replace or update corresponding metadata. + If the rename string starts with a name, the layer's name will change to that. + """ + patterns = pattern.strip().split() + a = self.cfg["delimiters"]["assign"] + + patterns = map(partial(flip(str.split), a), patterns) + + success, patterns = tee(patterns) + success = map(lambda p: len(p) == 2, success) + if not all(success): + raise ValueError("malformed pattern.") + + key = lambda p: p[0] in self.cfg["meta"].keys() + patterns = sorted(patterns, key=key) + patterns = groupby(patterns, key) + + newName = self.node.name() + for k, ps in patterns: + for p in ps: + how = ( + "replace" + if k is False + else "add" + if p[1] != "" and "{}{}".format(p[0], a) not in newName + else "subtract" + if p[1] == "" + else "update" + ) + pat = ( + p + if how == "replace" + else (r"$", r" {}{}{}".format(p[0], a, p[1])) + if how == "add" + else ( + r"\s*({}{})[\w,]+\s*".format(p[0], a), + " " if how == "subtract" else r" \g<1>{} ".format(p[1]), + ) + ) + newName = re.sub(pat[0], pat[1], newName).strip() + self.node.setName(newName) + + def save(self, dirname=""): + """ + Transform Node to a QImage + processes the image, names it based on metadata, and saves the image to the disk. + """ + img = nodeToImage(self) + meta = self.meta + margin, scale = meta["m"], meta["s"] + extension, path = meta["e"], meta["p"][0] + + dirPath = ( + exportPath(self.cfg, path, dirname) + if path + else exportPath(self.cfg, pathFS(self.parent), dirname) + ) + os.makedirs(dirPath, exist_ok=True) + + def append_name(path, name, scale, margin, extension): + """ + Appends a formatted name to the path argument + Returns the full path with the file + """ + meta_s = self.cfg["meta"]["s"][0] + out = os.path.join(path, name) + out += "_@{}x".format(scale / 100) if scale != meta_s else "" + out += "_m{:03d}".format(margin) if margin else "" + out += "." + extension + return out + + it = product(scale, margin, extension) + # Below: scale for scale, margin for margin, extension for extension + it = starmap( + lambda scale, margin, extension: ( + scale, + margin, + extension, + append_name(dirPath, self.name, scale, margin, extension), + ), + it, + ) + it = starmap( + lambda scale, margin, extension, path: ( + [int(1e-2 * wh * scale) for wh in self.size], + 100 - scale != 0, + margin, + extension, + path, + ), + it, + ) + it = starmap( + lambda width_height, should_scale, margin, extension, path: ( + img.smoothScaled(*width_height) if should_scale else img, + margin, + extension in ("jpg", "jpeg"), + path, + ), + it, + ) + it = starmap( + lambda image, margin, is_jpg, path: ( + expandAndFormat(image, margin, is_jpg=is_jpg), + path, + is_jpg + ), + it, + ) + it = starmap(lambda image, path, is_jpg: image.save(path, quality=90 if is_jpg else -1), it) + kickstart(it) + + def saveCOA(self, dirname=""): + img = nodeToImage(self) + meta = self.meta + path, extension = "", meta["e"] + + dirPath = ( + exportPath(self.cfg, path, dirname) + if path + else exportPath(self.cfg, pathFS(self.parent), dirname) + ) + os.makedirs(dirPath, exist_ok=True) + ext = extension[0] + path = "{}{}".format(os.path.join(dirPath, self.name), ".{e}") + path = path.format(e=ext) + is_jpg = ext in ("jpg", "jpeg") + if is_jpg in ("jpg", "jpeg"): + img = expandAndFormat(img, is_jpg=is_jpg) + img.save(path, quality=90 if is_jpg else -1) + + return path + + def saveCOASpriteSheet(self, dirname=""): + """ + Generate a vertical sheet of equaly sized frames + Each child of self is pasted to a master sheet + """ + images = self.children + tiles_x, tiles_y = 1, len(images) # Length of vertical sheet + image_width, image_height = self.size # Target frame size + sheet_width, sheet_height = (image_width, image_height * tiles_y) # Sheet dimensions + + sheet = QImage(sheet_width, sheet_height, QImage.Format_ARGB32) + sheet.fill(QColor(255, 255, 255, 0)) + painter = QPainter(sheet) + + p_coord_x, p_coord_y = self.position + for count, image in enumerate(images): + coord_x, coord_y = image.position + coord_rel_x, coord_rel_y = coord_x - p_coord_x, coord_y - p_coord_y + + painter.drawImage( + coord_rel_x, image_height * count + coord_rel_y, nodeToImage(image), + ) + + meta = self.meta + path, extension = "", meta["e"] + + dirPath = ( + exportPath(self.cfg, path, dirname) + if path + else exportPath(self.cfg, pathFS(self.parent), dirname) + ) + os.makedirs(dirPath, exist_ok=True) + path = "{}{}".format(os.path.join(dirPath, self.name), ".{e}") + path = path.format(e=extension[0]) + is_jpg = extension in ("jpg", "jpeg") + if is_jpg: + sheet = expandAndFormat(sheet, is_jpg=True) + sheet.save(path, quality=90 if is_jpg else -1) + + return path, {"tiles_x": tiles_x, "tiles_y": tiles_y} diff --git a/plugins/python/batch_exporter/Manual.html b/plugins/python/batch_exporter/Manual.html new file mode 100644 index 0000000000..d3cb2f87a0 --- /dev/null +++ b/plugins/python/batch_exporter/Manual.html @@ -0,0 +1,57 @@ + + + + + + + Manual + + + +

Batch Exporter: Krita Plugin for Game Developers and Graphic Designers

+

Free Krita plugin for designers, game artists and digital artists to work more productively:

+
    +
  • Batch export assets to multiple sizes, file types, and custom paths. Supports jpg and png.
  • +
  • Rename layers quickly with the smart rename tool
  • +
+

Batch Export Layers

+

Batch Exporter exports individual layers to image files based on metadata in the layer name. The supported options are:

+
    +
  • [e=jpg,png] - supported export image extensions
  • +
  • [s=20,50,100,150] - size in %
  • +
  • [p=path/to/custom/export/directory] - custom output path. Paths can be absolute or relative to the Krita document.
  • +
  • [m=20,30,100] - extra margin in px. The layer is trimmed to the smallest bounding box by default. This option adds extra padding around the layer.
  • +
+

A typical layer name with metadata looks like: CharacterTorso e=png m=30 s=50,100. This exports the layer as two images, with an added padding of 30 pixels on each side: CharacterTorso_s100_m030.png, and CharacterTorso_s050_m030.png, a copy of the layer scaled down to half the original size.

+

All the metadata tags are optional. Each tag can contain one or multiple options separated by comma ,. Write e=jpg to export the layer to jpg only and e=jpg,png to export the layer twice, as a jpg and as a png file. Note that the other tag, p= has been left out. Below we describe how the plugin works.

+

Getting Started

+

Batch Exporter gives two options to batch export layers: Export All Layers or Export Selected Layers.

+

Export All Layers only takes layers with the e=extension[s] tag into account. For example, if the layer name is LeftArm e=png s=50,100, Export All Layers will take it into account. If the layer name is LeftArm s=50,100, it will not be exported with this option.

+

Export Selected Layers exports all selected layers regardless of the tags.

+

By default, the plugin exports the images in an export folder next to your Krita document. The export follows the structure of your layer stack. The group layers become directories and other layers export as files.

+
+

Supported layer types: paint, vector, group & file layers.

+
+

Smart Layer Rename tool

+

Say we have this Krita document structure:

+
GodetteGroupLayer
+  +-- HeadGroupLayer
+    +-- Hair
+    +-- Eyes
+    +-- Rest
+  +-- Torso
+  +-- LeftArm
+  +-- RightArm
+Background
+

If you want to export GodetteGroupLayer, HeadGroupLayer, Torso, LeftArm, and RightArm, but not the other layers, you can select these layers and write the following in the Update Layer Name text box: e=png s=40,100 and press Enter. In this example, Art Tools will export two copies of the selected layers to png at 40% and 100% scale. This is what s=40,100 does.

+

Say that we made a mistake: we want to export to 50% instead of 40%. Select the layers once more and write s=50,100 in the text box. Press Enter. This will update the size tag and leave e=png untouched.

+

The tool can do more than add and update meta tags. If you want to remove GroupLayer from the name on GodetteGroupLayer and HeadGroupLayer, select them and write GroupLayer= in the text box. Press Enter and the GroupLayer text will disappear from the selected layers.

+

The = tells the tool to search and replace. this=[that] will replace this with [that]. If you don’t write anything after the equal sign, the tool will erase the text you searched for.

+

The rename tool is smarter with meta tags. Writing e= will remove the extension tag entirely. For example, Godete e=png s=50,100 will become Godette s=50,100.

+ + diff --git a/plugins/python/batch_exporter/Manual.md b/plugins/python/batch_exporter/Manual.md new file mode 100644 index 0000000000..c9d32fcba6 --- /dev/null +++ b/plugins/python/batch_exporter/Manual.md @@ -0,0 +1,151 @@ +# Batch Exporter: Krita Plugin for Game Developers and Graphic Designers + +Free Krita plugin for designers, game artists and digital artists to work more +productively: + +- Batch export assets to multiple sizes, file types, and custom paths. Supports + `jpg` and `png`. +- Rename layers quickly with the smart rename tool + +## Batch Export Layers + +Batch Exporter exports individual layers to image files based on metadata in +the layer name. The supported options are: + +- `[e=jpg,png]` - supported export image extensions +- `[s=20,50,100,150]` - size in `%` +- `[p=path/to/custom/export/directory]` - custom output path. + Paths can be absolute or relative to the Krita document. +- `[m=20,30,100]` - extra margin in `px`. The layer is trimmed to the + smallest bounding box by default. This option adds extra padding around the + layer. + +A typical layer name with metadata looks like: `CharacterTorso e=png m=30 +s=50,100`. This exports the layer as two images, with an added padding of 30 pixels +on each side: `CharacterTorso_s100_m030.png`, and `CharacterTorso_s050_m030.png`, +a copy of the layer scaled down to half the original size. + +All the metadata tags are optional. Each tag can contain one or multiple options +separated by comma `,`. Write `e=jpg` to export the layer to `jpg` only and +`e=jpg,png` to export the layer twice, as a `jpg` and as a `png` file. Note that +the other tag, `p=` has been left out. Below we describe how the plugin works. + +## Getting Started + +Batch Exporter gives two options to batch export layers: `Export All Layers` +or `Export Selected Layers`. + +`Export All Layers` only takes layers with the `e=extension[s]` tag into +account. For example, if the layer name is `LeftArm e=png s=50,100`, `Export All +Layers` will take it into account. If the layer name is `LeftArm s=50,100`, it +will not be exported with this option. + +`Export Selected Layers` exports all selected layers regardless of the tags. + +By default, the plugin exports the images in an `export` folder next to your +Krita document. The export follows the structure of your layer stack. The group +layers become directories and other layers export as files. + +> **Supported layer types:** paint, vector, group & file layers. + +## Smart Layer Rename tool + +Say we have this Krita document structure: + +``` +GodetteGroupLayer + +-- HeadGroupLayer + +-- Hair + +-- Eyes + +-- Rest + +-- Torso + +-- LeftArm + +-- RightArm +Background +``` + +If you want to export `GodetteGroupLayer`, `HeadGroupLayer`, `Torso`, `LeftArm`, +and `RightArm`, but not the other layers, you can select these layers and write +the following in the `Update Layer Name` text box: `e=png s=40,100` and press +Enter. In this example, Art Tools will export two copies of the +selected layers to png at `40%` and `100%` scale. This is what `s=40,100` does. + +Say that we made a mistake: we want to export to `50%` instead of `40%`. Select +the layers once more and write `s=50,100` in the text box. Press +Enter. This will update the size tag and leave `e=png` untouched. + +The tool can do more than add and update meta tags. If you want to remove +`GroupLayer` from the name on `GodetteGroupLayer` and `HeadGroupLayer`, select them +and write `GroupLayer=` in the text box. Press Enter and the +`GroupLayer` text will disappear from the selected layers. + +The `=` tells the tool to search and replace. `this=[that]` will replace `this` +with `[that]`. If you don't write anything after the equal sign, the tool will +erase the text you searched for. + +The rename tool is smarter with meta tags. Writing `e=` will remove the +extension tag entirely. For example, `Godete e=png s=50,100` will become +`Godette s=50,100`. + +## COA Tools format + +The exporter will generate the necessary sprite contents and metadata file for +easy import in COA Tools / Blender. + +If you want to export your krita document to COA Tools format, +simply click the `Document` button under COA Tools. + +If you want to export multiple or specific COA Tool documents from one Krita document +(if you have e.g. multiple characters in one Krita document), +you can do so by selecting a Group Layer to serve as root for each COA Tool export +you want done. + +### Example +You want to export two characters from the same Krita document in one go +``` +Root + +-- Robot (Group Layer) <-- Select this layer + | +-- Head + | +-- Body + | +-- Legs + | + +-- Sketches + | +-- ... + | + +-- Minion (Group Layer) <-- ... and this layer + | +-- Hat + | +-- Head + | + Background +``` +Once the Group Layers are selected you push "COA Tools -> Selected Layers". + +Each export root supports the following metadata: +- `[p=path/to/custom/export/directory]` - custom output path. + Paths can be absolute or relative to the Krita document. + +Each child node of an export root supports the following metadata: +- `[e=jpg,png]` - supported export image extensions + +Generating frames to a sprite sheet from a Group Layer is also possible. +Simply mark the layer containing each frame you want in the sheet with a +`c=sheet` and each child layer will act as one frame you can switch when +Working with COA Tools in Blender. + +### Example +You want to export a character from the document, and be +able to switch between each state of e.g. the mouth: +``` +Root + +-- Robot (Group Layer) <-- If this is the export root + | +-- Mouth States c=sheet <-- ... mark this layer + | | +-- Open + | | +-- Half Open + | | +-- Closed + | | + | +-- Head + | +-- Body + | +-- Legs + | + Background +``` diff --git a/plugins/python/batch_exporter/Utils/Export.py b/plugins/python/batch_exporter/Utils/Export.py new file mode 100644 index 0000000000..0867267a7b --- /dev/null +++ b/plugins/python/batch_exporter/Utils/Export.py @@ -0,0 +1,19 @@ +import os +import re +from ..Config import CONFIG + + +def exportPath(cfg, path, dirname=""): + return os.path.join(dirname, subRoot(cfg, path)) + + +def subRoot(cfg, path): + patF, patR = cfg["rootPat"], CONFIG["outDir"] + return re.sub(patF, patR, path, count=1) + + +def sanitize(path): + ps = path.split(os.path.sep) + ps = map(lambda p: re.sub(CONFIG["sym"], "_", p), ps) + ps = os.path.sep.join(ps) + return ps diff --git a/plugins/python/batch_exporter/Utils/Tree.py b/plugins/python/batch_exporter/Utils/Tree.py new file mode 100644 index 0000000000..b5ec25076e --- /dev/null +++ b/plugins/python/batch_exporter/Utils/Tree.py @@ -0,0 +1,152 @@ +import os +from itertools import chain + +def iterPre(node, maxDepth=-1): + """ + Visit nodes in pre order. + + Parameters + ---------- + node: Node + maxDepth: int + Maximum depth level at which traversal will stop. + + Returns + ------- + out: iter(Node) + """ + + def go(nodes, depth=0): + for n in nodes: + yield n + # recursively call the generator if depth < maxDepth + it = go(n.children, depth + 1) if maxDepth == -1 or depth < maxDepth else iter() + yield from it + + return go([node]) + + +def iterLevel(node, maxDepth=-1): + """ + Visit nodes in level order. + + Parameters + ---------- + node: Node + maxDepth: int + Maximum depth level at which traversal will stop. + + Returns + ------- + out: iter(Node) + """ + + def go(nodes, depth=0): + yield from nodes + it = map(lambda n: go(n.children, depth + 1), nodes) + it = chain(*it) if maxDepth == -1 or depth < maxDepth else iter() + yield from it + + return go([node]) + + +def iterLevelGroup(node, maxDepth=-1): + """ + Visit nodes in level order just like `iterLevel`, but group nodes per level in an iterator. + + Parameters + ---------- + node: Node + maxDepth: int + Maximum depth level at which traversal will stop. + + Returns + ------- + out: iter(iter(Node)) + Returns an iterator that holds an iterator for each depth level. + """ + + def go(nodes, depth=0): + yield iter(nodes) + it = map(lambda n: go(n.children, depth + 1), nodes) + it = chain(*it) if maxDepth == -1 or depth < maxDepth else iter() + yield from filter(None, it) + + return go([node]) + + +def iterPost(node, maxDepth=-1): + """ + Visit nodes in post order. + + Parameters + ---------- + node: Node + maxDepth: int + Maximum depth level at which traversal will stop. + + Returns + ------- + out: iter(Node) + """ + + def go(nodes, depth=0): + for n in nodes: + it = go(n.children, depth + 1) if maxDepth == -1 or depth < maxDepth else iter() + yield from it + yield n + + return go([node]) + + +def path(node): + """ + Get the path of the given node. + + Parameters + ---------- + node: Node + + Return + ------ + out: list(Node) + The path of nodes going through all the parents to the given node. + """ + + def go(n, acc=[]): + acc += [n] + n.parent and go(n.parent, acc) + return reversed(acc) + + return list(go(node)) + + +def pathFS(node): + """ + Get the path of the given node just like `path`, but returns a OS filesystem path based on + node names. + + Parameters + ---------- + node: Node + A node that has a `name` method. + + Return + ------ + out: str + The path of nodes going through all the parents to the given node in filesystem-compatile + string format. + """ + it = filter(lambda n: n.parent, path(node)) + it = map(lambda n: n.name, it) + return os.path.join('', *it) + + +def iterDirs(node): + it = iterPre(node) + it = filter(lambda n: n.isGroupLayer(), it) + it = filter( + lambda n: any(i.isExportable() for i in chain(*map(lambda c: iterPre(c), n.children))), it + ) + it = map(pathFS, it) + return it diff --git a/plugins/python/batch_exporter/Utils/__init__.py b/plugins/python/batch_exporter/Utils/__init__.py new file mode 100644 index 0000000000..b9a90cd743 --- /dev/null +++ b/plugins/python/batch_exporter/Utils/__init__.py @@ -0,0 +1,9 @@ +from collections import deque + + +def flip(f): + return lambda *a: f(*reversed(a)) + + +def kickstart(it): + deque(it, maxlen=0) diff --git a/plugins/python/batch_exporter/__init__.py b/plugins/python/batch_exporter/__init__.py new file mode 100644 index 0000000000..bd7687d505 --- /dev/null +++ b/plugins/python/batch_exporter/__init__.py @@ -0,0 +1,3 @@ +from .batch_exporter import registerDocker # noqa + +registerDocker() diff --git a/plugins/python/batch_exporter/batch_exporter.py b/plugins/python/batch_exporter/batch_exporter.py new file mode 100644 index 0000000000..fc13e1e481 --- /dev/null +++ b/plugins/python/batch_exporter/batch_exporter.py @@ -0,0 +1,194 @@ +""" +GDQuest Batch Exporter +----------------- +Batch export art assets from Krita using layer metadata. +Updates and reads metadata in Krita's layer names, and uses it to smartly process and export layers. +Export to the Blender Cut-Out Animation tools for modular 2d game animation. +Licensed under the GNU GPL v3.0 terms +""" + +from functools import partial +from krita import DockWidget, DockWidgetFactory, DockWidgetFactoryBase, Krita +from PyQt5.QtWidgets import ( + QPushButton, + QStatusBar, + QLabel, + QLineEdit, + QHBoxLayout, + QVBoxLayout, + QGroupBox, + QWidget, +) +import os +from .Config import CONFIG +from .Infrastructure import WNode +from .COATools import COAToolsFormat +from .Utils import kickstart, flip +from .Utils.Tree import iterPre + +KI = Krita.instance() + + +def ensureRGBAU8(doc): + ensured = doc.colorModel() == "RGBA" and doc.colorDepth() == "U8" + if not ensured: + raise ValueError("only RGBA 8-bit depth supported!") + + +def exportAllLayers(cfg, statusBar): + msg, timeout = (cfg["done"]["msg"].format("Exported all layers."), cfg["done"]["timeout"]) + try: + doc = KI.activeDocument() + + root = doc.rootNode() + root = WNode(cfg, root) + + dirName = os.path.dirname(doc.fileName()) + it = filter(lambda n: n.isExportable() and n.isMarked(), iterPre(root)) + it = map(partial(flip(WNode.save), dirName), it) + kickstart(it) + except ValueError as e: + msg, timeout = cfg["error"]["msg"].format(e), cfg["error"]["timeout"] + statusBar.showMessage(msg, timeout) + + +def exportSelectedLayers(cfg, statusBar): + msg, timeout = (cfg["done"]["msg"].format("Exported selected layers."), cfg["done"]["timeout"]) + try: + doc = KI.activeDocument() + + dirName = os.path.dirname(doc.fileName()) + nodes = KI.activeWindow().activeView().selectedNodes() + it = map(partial(WNode, cfg), nodes) + it = map(partial(flip(WNode.save), dirName), it) + kickstart(it) + except ValueError as e: + msg, timeout = cfg["error"]["msg"].format(e), cfg["error"]["timeout"] + statusBar.showMessage(msg, timeout) + + +def exportCOATools(mode, cfg, statusBar): + msg, timeout = ( + cfg["done"]["msg"].format("Exported %s layers to COA Tools format." % (mode)), + cfg["done"]["timeout"], + ) + try: + doc = KI.activeDocument() + ensureRGBAU8(doc) + + coat_format = COAToolsFormat(cfg, statusBar) + dirName = os.path.dirname(doc.fileName()) + nodes = KI.activeWindow().activeView().selectedNodes() + + # If mode is document or no nodes are selected, use document root + if mode == "document" or len(nodes) == 0: + nodes = [doc.rootNode()] + + it = map(partial(WNode, cfg), nodes) + # By convention all selected nodes should be Group Layers + # This is to represent a logical root for each export in COATools format + it = filter(lambda n: n.isGroupLayer(), it) + it = map(coat_format.collect, it) + kickstart(it) + coat_format.save(dirName) + + except ValueError as e: + msg, timeout = cfg["error"]["msg"].format(e), cfg["error"]["timeout"] + statusBar.showMessage(msg, timeout) + + +def renameLayers(cfg, statusBar, lineEdit): + msg, timeout = (cfg["done"]["msg"].format("Renaming successful!"), cfg["done"]["timeout"]) + try: + nodes = KI.activeWindow().activeView().selectedNodes() + it = map(partial(WNode, cfg), nodes) + it = map(partial(flip(WNode.rename), lineEdit.text()), it) + kickstart(it) + except ValueError as e: + msg, timeout = cfg["error"]["msg"].format(e), cfg["error"]["timeout"] + statusBar.showMessage(msg, timeout) + + +class GameArtTools(DockWidget): + title = "Batch Exporter" + + def __init__(self): + super().__init__() + KI.setBatchmode(True) + self.setWindowTitle(self.title) + self.createInterface() + + def createInterface(self): + uiContainer = QWidget(self) + + exportLabel = QLabel("Export") + exportAllLayersButton = QPushButton("All Layers") + exportSelectedLayersButton = QPushButton("Selected Layers") + renameLabel = QLabel("Update Name and Metadata") + renameLineEdit = QLineEdit() + renameButton = QPushButton("Update") + statusBar = QStatusBar() + + exportLabel.setToolTip("Export individual images") + exportAllLayersButton.setToolTip("Export all layers with metadata") + exportSelectedLayersButton.setToolTip("Export selected layers only") + renameButton.setToolTip("Batch update selected layer names and metadata") + + # COA Tools GroupBox + coaToolsGroupBox = QGroupBox("COA Tools") + coaToolsHBoxLayout = QHBoxLayout() + coaToolsExportSelectedLayersButton = QPushButton("Selected Layers") + coaToolsExportDocumentButton = QPushButton("Document") + + coaToolsGroupBox.setToolTip("Blender Cut-Out Animation Tools") + coaToolsExportSelectedLayersButton.setToolTip("Export selected layers only") + coaToolsExportDocumentButton.setToolTip("Export all layers with metadata") + + coaToolsHBoxLayout.addWidget(coaToolsExportDocumentButton) + coaToolsHBoxLayout.addWidget(coaToolsExportSelectedLayersButton) + coaToolsGroupBox.setLayout(coaToolsHBoxLayout) + + vboxlayout = QVBoxLayout() + vboxlayout.addWidget(exportLabel) + vboxlayout.addWidget(exportAllLayersButton) + vboxlayout.addWidget(exportSelectedLayersButton) + + vboxlayout.addWidget(coaToolsGroupBox) + vboxlayout.addWidget(renameLabel) + vboxlayout.addWidget(renameLineEdit) + + hboxlayout = QHBoxLayout() + hboxlayout.addStretch() + hboxlayout.addWidget(renameButton) + + vboxlayout.addLayout(hboxlayout) + vboxlayout.addStretch() + vboxlayout.addWidget(statusBar) + + uiContainer.setLayout(vboxlayout) + self.setWidget(uiContainer) + + exportSelectedLayersButton.released.connect( + partial(exportSelectedLayers, CONFIG, statusBar) + ) + exportAllLayersButton.released.connect(partial(exportAllLayers, CONFIG, statusBar)) + coaToolsExportSelectedLayersButton.released.connect( + partial(exportCOATools, "selected", CONFIG, statusBar) + ) + coaToolsExportDocumentButton.released.connect( + partial(exportCOATools, "document", CONFIG, statusBar) + ) + renameLineEdit.returnPressed.connect( + partial(renameLayers, CONFIG, statusBar, renameLineEdit) + ) + renameButton.released.connect(partial(renameLayers, CONFIG, statusBar, renameLineEdit)) + + def canvasChanged(self, canvas): + pass + + +def registerDocker(): + docker = DockWidgetFactory( + "pykrita_gdquest_art_tools", DockWidgetFactoryBase.DockRight, GameArtTools + ) + KI.addDockWidgetFactory(docker) diff --git a/plugins/python/batch_exporter/kritapykrita_batch_exporter.desktop b/plugins/python/batch_exporter/kritapykrita_batch_exporter.desktop new file mode 100644 index 0000000000..5b5e8e1e58 --- /dev/null +++ b/plugins/python/batch_exporter/kritapykrita_batch_exporter.desktop @@ -0,0 +1,30 @@ +[Desktop Entry] +Type=Service +ServiceTypes=Krita/PythonPlugin +X-KDE-Library=batch_exporter +X-Krita-Manual=Manual.html +X-Python-2-Compatible=false +Name=Batch Exporter +Name[ca]=Exportador per lots +Name[en_GB]=Batch Exporter +Name[es]=Exportador por lotes +Name[et]=Hulgieksport +Name[nl]=Exportprogramma voor bulk +Name[nn]=Fleirbileteksport +Name[pt]=Exportação em Lote +Name[pt_BR]=Exportador em lote +Name[sv]=Bakgrundsexport +Name[uk]=Пакетне експортування +Name[x-test]=xxBatch Exporterxx +Comment=Smart export tool that uses layer names to scale and (re-)export art assets in batches fast +Comment[ca]=Eina d'exportació intel·ligent que usa els noms de les capes per escalar i (re)exportar elements d'art en lots ràpidament +Comment[en_GB]=Smart export tool that uses layer names to scale and (re-)export art assets in batches fast +Comment[es]=Herramienta de exportación inteligente que usa nombres de capas para escalar y (volver a) exportar recursos artísticos por lotes de un modo rápido +Comment[et]=Nutikas eksporditööriist, mid kasutab kihtide nimesid kunstiresursside kiireks hulgiskaleerimiseks ja -(taas)eksportimiseks +Comment[nl]=Slim hulpmiddel voor exporteren die lagennamen gebruikt om snel kunstbezittingen te schalen en (opnieuw) te exporteren in bulk +Comment[nn]=Smart eksportverktøy som brukar lagnamn til å skalera og (re)eksportera til fleire bilete +Comment[pt]=Uma ferramenta inteligente de exportação em lote que usa os nomes das camadas para definir a escala e (re-)exportar os itens gráficos em lote de forma rápida +Comment[pt_BR]=Ferramenta de exportação inteligente que usa nomes de camada para dimensionar e (re)exportar ativos de arte em lotes rapidamente +Comment[sv]=Smart exportverktyg som använder lagernamn för att snabbt skala och exportera om grafiktillgångar i bakgrunden +Comment[uk]=Інструмент пакетного експортування, який використовує назви шарів для масштабування і швидкого (повторного) пакетного експортування художніх елементів +Comment[x-test]=xxSmart export tool that uses layer names to scale and (re-)export art assets in batches fastxx diff --git a/plugins/python/batch_exporter/pyproject.toml b/plugins/python/batch_exporter/pyproject.toml new file mode 100644 index 0000000000..440365c381 --- /dev/null +++ b/plugins/python/batch_exporter/pyproject.toml @@ -0,0 +1,9 @@ +[tool.black] +line-length=100 +include = '\.py$' +exclude = ''' +/( + \.git + | Dependencies +)/ +''' \ No newline at end of file diff --git a/plugins/python/channels2layers/Manual.html b/plugins/python/channels2layers/Manual.html new file mode 100644 index 0000000000..e797ddc3fb --- /dev/null +++ b/plugins/python/channels2layers/Manual.html @@ -0,0 +1,78 @@ + + + + + Channels to Layers + + +

Channels to Layers

+ + A Krita plugin designed to split channels from a layer to sub-layers: +
    +
  • RGB
  • +
  • CMY
  • +
  • CMYK
  • +
  • RGB as greayscale values
  • +
  • CMY as greayscale values
  • +
  • CMYK as greayscale values
  • +
+ +

Screenshot

+ User interface to convert CMYK channel for a layer
+ +
+ + Testing picture from Wikimedia +
+ color model CMYK from Wikipedia +
+ +

Usage

+
    +
  1. Select a layer to convert +
    Selected layer must be a 8bits RGBA paint layer, other layer type are not (yet?) supported +
    +
  2. +
  3. Execute script from Tools > Scripts > Channels to Layers
  4. +
  5. Choose options from user interface +
    Layers management +
      +
    • New layer group name +
      New layers will be grouped in a Group layer for which name is defined here. +
      Possible keywords can be used to build layer name: +
      {mode}: conversion mode (RGB, CMY, CMYK, ...) +
      {source:name}: original layer name +
    • +
    • New layers color name +
      Define name for each layer created from channel. +
      Possible keywords can be used to build layer name: +
      {mode}: conversion mode (RGB, CMY, CMYK, ...) +
      {color:short}: current channel color, short value (R, G, B, C, M, Y, K) +
      {color:long}: current channel color, long value (Red, Green, Blue, Cyan, Magenta, Yellow, Black) +
      {source:name}: original layer name +
    • +
    • Original layer +
      Define what to do with original layer +
      Unchanged let original layer visibility unchanged +
      Visible set original layer visibility to 'Visible' +
      Hidden set original layer visibility to 'Hidden' +
      Remove remove original layer +
    • +
    +

    + Output results +
      +
    • Mode +
      Conversion to apply +
      RGB Colors Convert layer in to Red, Green, Blue color layers +
      CMY Colors Convert layer in to Cyan, Magenta, Yellow color layers +
      CMYK Colors Convert layer in to Cyan, Magenta, Yellow, Black color layers +
      RGB Grayscale levels Convert layer in to Red, Green, Blue grayscale layers +
      CMY Grayscale levels Convert layer in to Cyan, Magenta, Yellow grayscale layers +
      CMYK Grayscale levels Convert layer in to Cyan, Magenta, Yellow, Black grayscale layers +
    +
  6. +
+ + + \ No newline at end of file diff --git a/plugins/python/channels2layers/__init__.py b/plugins/python/channels2layers/__init__.py new file mode 100644 index 0000000000..d98ee57218 --- /dev/null +++ b/plugins/python/channels2layers/__init__.py @@ -0,0 +1,7 @@ +from .channels2layers import ChannelsToLayers + +# And add the extension to Krita's list of extensions: +app = Krita.instance() +# Instantiate your class: +extension = ChannelsToLayers(parent=app) +app.addExtension(extension) diff --git a/plugins/python/channels2layers/channels2layers.jpg b/plugins/python/channels2layers/channels2layers.jpg new file mode 100644 index 0000000000..596dacfb15 Binary files /dev/null and b/plugins/python/channels2layers/channels2layers.jpg differ diff --git a/plugins/python/channels2layers/channels2layers.py b/plugins/python/channels2layers/channels2layers.py new file mode 100644 index 0000000000..c03dcdb893 --- /dev/null +++ b/plugins/python/channels2layers/channels2layers.py @@ -0,0 +1,1461 @@ +#----------------------------------------------------------------------------- +# Channels to Layers +# Copyright (C) 2019 - Grum999 +# ----------------------------------------------------------------------------- +# This program is free software: you can 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, see https://www.gnu.org/licenses/ +# ----------------------------------------------------------------------------- +# A Krita plugin designed to split channels from a layer to sub-layers +# . RGB +# . CMY +# . CMYK +# . RGB as greayscale values +# . CMY as greayscale values +# . CMYK as greayscale values +# ----------------------------------------------------------------------------- + +import re +from krita import ( + Extension, + InfoObject, + Node, + Selection + ) +from PyQt5.Qt import * +from PyQt5 import QtCore +from PyQt5.QtCore import ( + pyqtSlot, + QBuffer, + QByteArray, + QIODevice + ) +from PyQt5.QtGui import ( + QColor, + QImage, + QPixmap, + ) +from PyQt5.QtWidgets import ( + QApplication, + QCheckBox, + QComboBox, + QDialog, + QDialogButtonBox, + QFormLayout, + QGroupBox, + QHBoxLayout, + QLabel, + QLineEdit, + QMessageBox, + QProgressBar, + QProgressDialog, + QVBoxLayout, + QWidget + ) + + +PLUGIN_VERSION = '1.1.0' + +EXTENSION_ID = 'pykrita_channels2layers' +PLUGIN_MENU_ENTRY = i18n('Channels to layers') +PLUGIN_DIALOG_TITLE = "{0} - {1}".format(i18n('Channels to layers'), PLUGIN_VERSION) + +# Define DialogBox types +DBOX_INFO = 'i' +DBOX_WARNING ='w' + + +# Define Output modes +OUTPUT_MODE_RGB = i18n('RGB Colors') +OUTPUT_MODE_CMY = i18n('CMY Colors') +OUTPUT_MODE_CMYK = i18n('CMYK Colors') +OUTPUT_MODE_LRGB = i18n('RGB Grayscale levels') +OUTPUT_MODE_LCMY = i18n('CMY Grayscale levels') +OUTPUT_MODE_LCMYK = i18n('CMYK Grayscale levels') + +OUTPUT_PREVIEW_MAXSIZE = 320 + +# Define original layer action +ORIGINAL_LAYER_KEEPUNCHANGED = i18n('Unchanged') +ORIGINAL_LAYER_KEEPVISIBLE = i18n('Visible') +ORIGINAL_LAYER_KEEPHIDDEN = i18n('Hidden') +ORIGINAL_LAYER_REMOVE = i18n('Remove') + +# define dialog option minimum dimension +DOPT_MIN_WIDTH = OUTPUT_PREVIEW_MAXSIZE * 5 + 200 +DOPT_MIN_HEIGHT = 480 + + + +OUTPUT_MODE_NFO = { + OUTPUT_MODE_RGB : { + 'description' : 'Extract channels (Red, Green, Blue) and create a colored layer per channel', + 'groupLayerName' : 'RGB', + 'layers' : [ + { + 'color' : 'B', + 'process': [ + { + 'action' : 'duplicate', + 'value' : '@original' + }, + { + 'action' : 'new', + 'value' : { + 'type' : 'filllayer', + 'color' : QColor(Qt.blue) + } + }, + { + 'action' : 'blending mode', + 'value' : 'inverse_subtract' + }, + { + 'action' : 'merge down', + 'value' : None + }, + { + 'action' : 'blending mode', + 'value' : 'add' + } + ] + }, + { + 'color' : 'G', + 'process': [ + { + 'action' : 'duplicate', + 'value' : '@original' + }, + { + 'action' : 'new', + 'value' : { + 'type' : 'filllayer', + 'color' : QColor(Qt.green) + } + }, + { + 'action' : 'blending mode', + 'value' : 'inverse_subtract' + }, + { + 'action' : 'merge down', + 'value' : None + }, + { + 'action' : 'blending mode', + 'value' : 'add' + } + ] + }, + { + 'color' : 'R', + 'process': [ + { + 'action' : 'duplicate', + 'value' : '@original' + }, + { + 'action' : 'new', + 'value' : { + 'type' : 'filllayer', + 'color' : QColor(Qt.red) + } + }, + { + 'action' : 'blending mode', + 'value' : 'inverse_subtract' + }, + { + 'action' : 'merge down', + 'value' : None + }, + { + 'action' : 'blending mode', + 'value' : 'add' + } + ] + } + ] + }, + OUTPUT_MODE_CMY : { + 'description' : 'Extract channels (Cyan, Mangenta, Yellow) and create a colored layer per channel', + 'groupLayerName' : 'CMY', + 'layers' : [ + { + 'color' : 'Y', + 'process': [ + { + 'action' : 'duplicate', + 'value' : '@original' + }, + { + 'action' : 'new', + 'value' : { + 'type' : 'filllayer', + 'color' : QColor(Qt.yellow) + } + }, + { + 'action' : 'blending mode', + 'value' : 'add' + }, + { + 'action' : 'merge down', + 'value' : None + }, + { + 'action' : 'blending mode', + 'value' : 'multiply' + } + ] + }, + { + 'color' : 'M', + 'process': [ + { + 'action' : 'duplicate', + 'value' : '@original' + }, + { + 'action' : 'new', + 'value' : { + 'type' : 'filllayer', + 'color' : QColor(Qt.magenta) + } + }, + { + 'action' : 'blending mode', + 'value' : 'add' + }, + { + 'action' : 'merge down', + 'value' : None + }, + { + 'action' : 'blending mode', + 'value' : 'multiply' + } + ] + }, + { + 'color' : 'C', + 'process': [ + { + 'action' : 'duplicate', + 'value' : '@original' + }, + { + 'action' : 'new', + 'value' : { + 'type' : 'filllayer', + 'color' : QColor(Qt.cyan) + } + }, + { + 'action' : 'blending mode', + 'value' : 'add' + }, + { + 'action' : 'merge down', + 'value' : None + }, + { + 'action' : 'blending mode', + 'value' : 'multiply' + } + ] + } + ] + }, + OUTPUT_MODE_CMYK : { + 'description' : 'Extract channels (Cyan, Mangenta, Yellow, Black) and create a colored layer per channel', + 'groupLayerName' : 'CMYK', + 'layers' : [ + { + 'color' : 'K', + 'process': [ + { + 'action' : 'duplicate', + 'value' : '@original' + }, + { + 'action' : 'filter', + 'value' : 'name=desaturate;type=5' # desaturate method = max + } + ] + }, + { + 'color' : 'Y', + 'process': [ + { + 'action' : 'duplicate', + 'value' : '@original' + }, + { + 'action' : 'new', + 'value' : { + 'type' : 'filllayer', + 'color' : QColor(Qt.yellow) + } + }, + { + 'action' : 'blending mode', + 'value' : 'add' + }, + { + 'action' : 'merge down', + 'value' : None + }, + { + 'action' : 'duplicate', + 'value' : '@K' + }, + { + 'action' : 'blending mode', + 'value' : 'divide' + }, + { + 'action' : 'merge down', + 'value' : None + }, + { + 'action' : 'blending mode', + 'value' : 'multiply' + } + ] + }, + { + 'color' : 'M', + 'process': [ + { + 'action' : 'duplicate', + 'value' : '@original' + }, + { + 'action' : 'new', + 'value' : { + 'type' : 'filllayer', + 'color' : QColor(Qt.magenta) + } + }, + { + 'action' : 'blending mode', + 'value' : 'add' + }, + { + 'action' : 'merge down', + 'value' : None + }, + { + 'action' : 'duplicate', + 'value' : '@K' + }, + { + 'action' : 'blending mode', + 'value' : 'divide' + }, + { + 'action' : 'merge down', + 'value' : None + }, + { + 'action' : 'blending mode', + 'value' : 'multiply' + } + ] + }, + { + 'color' : 'C', + 'process': [ + { + 'action' : 'duplicate', + 'value' : '@original' + }, + { + 'action' : 'new', + 'value' : { + 'type' : 'filllayer', + 'color' : QColor(Qt.cyan) + } + }, + { + 'action' : 'blending mode', + 'value' : 'add' + }, + { + 'action' : 'merge down', + 'value' : None + }, + { + 'action' : 'duplicate', + 'value' : '@K' + }, + { + 'action' : 'blending mode', + 'value' : 'divide' + }, + { + 'action' : 'merge down', + 'value' : None + }, + { + 'action' : 'blending mode', + 'value' : 'multiply' + } + ] + } + ] + }, + OUTPUT_MODE_LRGB : { + 'description' : 'Extract channels (Red, Green, Blue) and create a grayscale layer per channel', + 'groupLayerName' : 'RGB[GS]', + 'layers' : [ + { + 'color' : 'B', + 'process': [ + { + 'action' : 'duplicate', + 'value' : '@original' + }, + { + 'action' : 'new', + 'value' : { + 'type' : 'filllayer', + 'color' : QColor(Qt.blue) + } + }, + { + 'action' : 'blending mode', + 'value' : 'inverse_subtract' + }, + { + 'action' : 'merge down', + 'value' : None + }, + { + 'action' : 'blending mode', + 'value' : 'add' + }, + { + 'action' : 'filter', + 'value' : 'name=desaturate;type=5' # desaturate method = max + } + ] + }, + { + 'color' : 'G', + 'process': [ + { + 'action' : 'duplicate', + 'value' : '@original' + }, + { + 'action' : 'new', + 'value' : { + 'type' : 'filllayer', + 'color' : QColor(Qt.green) + } + }, + { + 'action' : 'blending mode', + 'value' : 'inverse_subtract' + }, + { + 'action' : 'merge down', + 'value' : None + }, + { + 'action' : 'blending mode', + 'value' : 'add' + }, + { + 'action' : 'filter', + 'value' : 'name=desaturate;type=5' # desaturate method = max + } + ] + }, + { + 'color' : 'R', + 'process': [ + { + 'action' : 'duplicate', + 'value' : '@original' + }, + { + 'action' : 'new', + 'value' : { + 'type' : 'filllayer', + 'color' : QColor(Qt.red) + } + }, + { + 'action' : 'blending mode', + 'value' : 'inverse_subtract' + }, + { + 'action' : 'merge down', + 'value' : None + }, + { + 'action' : 'blending mode', + 'value' : 'add' + }, + { + 'action' : 'filter', + 'value' : 'name=desaturate;type=5' # desaturate method = max + } + ] + } + ] + }, + OUTPUT_MODE_LCMY : { + 'description' : 'Extract channels (Cyan, Mangenta, Yellow) and create a grayscale layer per channel', + 'groupLayerName' : 'CMY[GS]', + 'layers' : [ + { + 'color' : 'Y', + 'process': [ + { + 'action' : 'duplicate', + 'value' : '@original' + }, + { + 'action' : 'new', + 'value' : { + 'type' : 'filllayer', + 'color' : QColor(Qt.yellow) + } + }, + { + 'action' : 'blending mode', + 'value' : 'add' + }, + { + 'action' : 'merge down', + 'value' : None + }, + { + 'action' : 'blending mode', + 'value' : 'add' + }, + { + 'action' : 'filter', + 'value' : 'name=desaturate;type=4' # desaturate method = min + } + ] + }, + { + 'color' : 'M', + 'process': [ + { + 'action' : 'duplicate', + 'value' : '@original' + }, + { + 'action' : 'new', + 'value' : { + 'type' : 'filllayer', + 'color' : QColor(Qt.magenta) + } + }, + { + 'action' : 'blending mode', + 'value' : 'add' + }, + { + 'action' : 'merge down', + 'value' : None + }, + { + 'action' : 'blending mode', + 'value' : 'add' + }, + { + 'action' : 'filter', + 'value' : 'name=desaturate;type=4' # desaturate method = min + } + ] + }, + { + 'color' : 'C', + 'process': [ + { + 'action' : 'duplicate', + 'value' : '@original' + }, + { + 'action' : 'new', + 'value' : { + 'type' : 'filllayer', + 'color' : QColor(Qt.cyan) + } + }, + { + 'action' : 'blending mode', + 'value' : 'add' + }, + { + 'action' : 'merge down', + 'value' : None + }, + { + 'action' : 'blending mode', + 'value' : 'add' + }, + { + 'action' : 'filter', + 'value' : 'name=desaturate;type=4' # desaturate method = min + } + ] + } + ] + }, + OUTPUT_MODE_LCMYK : { + 'description' : 'Extract channels (Cyan, Mangenta, Yellow, Black) and create a grayscale layer per channel', + 'groupLayerName' : 'CMYK[GS]', + 'layers' : [ + { + 'color' : 'K', + 'process': [ + { + 'action' : 'duplicate', + 'value' : '@original' + }, + { + 'action' : 'filter', + 'value' : 'name=desaturate;type=5' # desaturate method = max + } + ] + }, + { + 'color' : 'Y', + 'process': [ + { + 'action' : 'duplicate', + 'value' : '@original' + }, + { + 'action' : 'new', + 'value' : { + 'type' : 'filllayer', + 'color' : QColor(Qt.yellow) + } + }, + { + 'action' : 'blending mode', + 'value' : 'add' + }, + { + 'action' : 'merge down', + 'value' : None + }, + { + 'action' : 'duplicate', + 'value' : '@K' + }, + { + 'action' : 'blending mode', + 'value' : 'divide' + }, + { + 'action' : 'merge down', + 'value' : None + }, + { + 'action' : 'blending mode', + 'value' : 'multiply' + }, + { + 'action' : 'filter', + 'value' : 'name=desaturate;type=4' # desaturate method = min + } + ] + }, + { + 'color' : 'M', + 'process': [ + { + 'action' : 'duplicate', + 'value' : '@original' + }, + { + 'action' : 'new', + 'value' : { + 'type' : 'filllayer', + 'color' : QColor(Qt.magenta) + } + }, + { + 'action' : 'blending mode', + 'value' : 'add' + }, + { + 'action' : 'merge down', + 'value' : None + }, + { + 'action' : 'duplicate', + 'value' : '@K' + }, + { + 'action' : 'blending mode', + 'value' : 'divide' + }, + { + 'action' : 'merge down', + 'value' : None + }, + { + 'action' : 'blending mode', + 'value' : 'multiply' + }, + { + 'action' : 'filter', + 'value' : 'name=desaturate;type=4' # desaturate method = min + } + ] + }, + { + 'color' : 'C', + 'process': [ + { + 'action' : 'duplicate', + 'value' : '@original' + }, + { + 'action' : 'new', + 'value' : { + 'type' : 'filllayer', + 'color' : QColor(Qt.cyan) + } + }, + { + 'action' : 'blending mode', + 'value' : 'add' + }, + { + 'action' : 'merge down', + 'value' : None + }, + { + 'action' : 'duplicate', + 'value' : '@K' + }, + { + 'action' : 'blending mode', + 'value' : 'divide' + }, + { + 'action' : 'merge down', + 'value' : None + }, + { + 'action' : 'blending mode', + 'value' : 'multiply' + }, + { + 'action' : 'filter', + 'value' : 'name=desaturate;type=4' # desaturate method = min + } + ] + } + ] + } +} + +TRANSLATIONS_DICT = { + 'colorDepth' : { + 'U8' : '8bits', + 'U16' : '16bits', + 'F16' : '16bits floating point', + 'F32' : '32bits floating point' + }, + 'colorModel' : { + 'A' : 'Alpha mask', + 'RGBA' : 'RGB with alpha channel', + 'XYZA' : 'XYZ with alpha channel', + 'LABA' : 'LAB with alpha channel', + 'CMYKA' : 'CMYK with alpha channel', + 'GRAYA' : 'Gray with alpha channel', + 'YCbCrA' : 'YCbCr with alpha channel' + }, + 'layerType' : { + 'paintlayer' : 'Paint layer', + 'grouplayer' : 'Group layer', + 'filelayer' : 'File layer', + 'filterlayer' : 'Filter layer', + 'filllayer' : 'Fill layer', + 'clonelayer' : 'Clone layer', + 'vectorlayer' : 'Vector layer', + 'transparencymask' : 'Transparency mask', + 'filtermask' : 'Filter mask', + 'transformmask': 'Transform mask', + 'selectionmask': 'Selection mask', + 'colorizemask' : 'Colorize mask' + } +} + + + + +class ChannelsToLayers(Extension): + + def __init__(self, parent): + # Default options + self.__outputOptions = { + 'outputMode': OUTPUT_MODE_RGB, + 'originalLayerAction': ORIGINAL_LAYER_KEEPHIDDEN, + 'layerGroupName': '{mode}-{source:name}', + 'layerColorName': '{mode}[{color:short}]-{source:name}' + } + + self.__sourceDocument = None + self.__sourceLayer = None + + # Always initialise the superclass. + # This is necessary to create the underlying C++ object + super().__init__(parent) + self.parent = parent + + + def setup(self): + pass + + + def createActions(self, window): + action = window.createAction(EXTENSION_ID, PLUGIN_MENU_ENTRY, "tools/scripts") + action.triggered.connect(self.action_triggered) + + def dBoxMessage(self, msgType, msg): + """Simplified function for DialogBox 'OK' message""" + if msgType == DBOX_WARNING: + QMessageBox.warning( + QWidget(), + PLUGIN_DIALOG_TITLE, + i18n(msg) + ) + else: + QMessageBox.information( + QWidget(), + PLUGIN_DIALOG_TITLE, + i18n(msg) + ) + + + def action_triggered(self): + """Action called when script is executed from Kitra menu""" + if self.checkCurrentLayer(): + if self.openDialogOptions(): + self.run() + + + def translateDictKey(self, key, value): + """Translate key from dictionnary (mostly internal Krita internal values) to human readable values""" + returned = i18n('Unknown') + + if key in TRANSLATIONS_DICT.keys(): + if value in TRANSLATIONS_DICT[key].keys(): + returned = i18n(TRANSLATIONS_DICT[key][value]) + + return returned + + + def checkCurrentLayer(self): + """Check if current layer is valid + - A document must be opened + - Active layer properties must be: + . Layer type: a paint layer + . Color model: RGBA + . Color depth: 8bits + """ + self.__sourceDocument = Application.activeDocument() + # Check if there's an active document + if self.__sourceDocument is None: + self.dBoxMessage(DBOX_WARNING, "There's no active document!") + return False + + self.__sourceLayer = self.__sourceDocument.activeNode() + + # Check if current layer can be processed + if self.__sourceLayer.type() != "paintlayer" or self.__sourceLayer.colorModel() != "RGBA" or self.__sourceLayer.colorDepth() != "U8": + self.dBoxMessage(DBOX_WARNING, "Selected layer must be a 8bits RGBA Paint Layer!" + "\n\nCurrent layer '{0}' properties:" + "\n- Layer type: {1}" + "\n- Color model: {2} ({3})" + "\n- Color depth: {4}" + "\n\n> Action is cancelled".format(self.__sourceLayer.name(), + self.translateDictKey('layerType', self.__sourceLayer.type()), + self.__sourceLayer.colorModel(), self.translateDictKey('colorModel', self.__sourceLayer.colorModel()), + self.translateDictKey('colorDepth', self.__sourceLayer.colorDepth()) + )) + return False + + return True + + + + def toQImage(self, layerNode, rect=None): + """Return `layerNode` content as a QImage (as ARGB32) + + The `rect` value can be: + - None, in this case will return all `layerNode` content + - A QRect() object, in this case return `layerNode` content reduced to given rectangle bounds + """ + srcRect = layerNode.bounds() + + if len(layerNode.childNodes()) == 0: + projectionMode = False + else: + projectionMode = True + + if projectionMode == True: + img = QImage(layerNode.projectionPixelData(srcRect.left(), srcRect.top(), srcRect.width(), srcRect.height()), srcRect.width(), srcRect.height(), QImage.Format_ARGB32) + else: + img = QImage(layerNode.pixelData(srcRect.left(), srcRect.top(), srcRect.width(), srcRect.height()), srcRect.width(), srcRect.height(), QImage.Format_ARGB32) + + return img.scaled(rect.width(), rect.height(), Qt.IgnoreAspectRatio, Qt.SmoothTransformation) + + + def openDialogOptions(self): + """Open dialog box to let user define channel extraction options""" + + tmpDocument = None + previewBaSrc = QByteArray() + lblPreview = [QLabel(), QLabel(), QLabel(), QLabel()] + lblPreviewLbl = [QLabel(), QLabel(), QLabel(), QLabel()] + + + # ---------------------------------------------------------------------- + # Define signal and slots for UI widgets + @pyqtSlot('QString') + def ledLayerGroupName_Changed(value): + self.__outputOptions['layerGroupName'] = value + + @pyqtSlot('QString') + def ledLayerColorName_Changed(value): + self.__outputOptions['layerColorName'] = value + + @pyqtSlot('QString') + def cmbOutputMode_Changed(value): + self.__outputOptions['outputMode'] = value + buildPreview() + + @pyqtSlot('QString') + def cmbOriginalLayerAction_Changed(value): + self.__outputOptions['originalLayerAction'] = value + + def buildPreview(): + pbProgress.setVisible(True) + + backupValue = self.__outputOptions['layerColorName'] + self.__outputOptions['layerColorName'] = '{color:long}' + + # create a temporary document to work + tmpDocument = Application.createDocument(imgThumbSrc.width(), imgThumbSrc.height(), "tmp", "RGBA", "U8", "", 120.0) + + # create a layer used as original layer + originalLayer = tmpDocument.createNode("Original", "paintlayer") + tmpDocument.rootNode().addChildNode(originalLayer, None) + # and set original image content + originalLayer.setPixelData(previewBaSrc, 0, 0, tmpDocument.width(), tmpDocument.height()) + + # execute process + groupLayer = self.process(tmpDocument, originalLayer, pbProgress) + + self.__outputOptions['layerColorName'] = backupValue + + originalLayer.setVisible(False) + groupLayer.setVisible(True) + + for layer in groupLayer.childNodes(): + layer.setBlendingMode('normal') + layer.setVisible(False) + tmpDocument.refreshProjection() + + index = 0 + for layer in groupLayer.childNodes(): + layer.setVisible(True) + tmpDocument.refreshProjection() + + lblPreview[index].setPixmap(QPixmap.fromImage(tmpDocument.projection(0, 0, tmpDocument.width(), tmpDocument.height()))) + lblPreviewLbl[index].setText("{0}".format(layer.name())) + layer.setVisible(False) + + index+=1 + + if index > 3: + lblPreview[3].setVisible(True) + lblPreviewLbl[3].setVisible(True) + else: + lblPreview[3].setVisible(False) + lblPreviewLbl[3].setVisible(False) + + + tmpDocument.close() + + pbProgress.setVisible(False) + + + # ---------------------------------------------------------------------- + # Create dialog box + dlgMain = QDialog(Application.activeWindow().qwindow()) + dlgMain.setWindowTitle(PLUGIN_DIALOG_TITLE) + # resizeable with minimum size + dlgMain.setSizeGripEnabled(True) + dlgMain.setMinimumSize(DOPT_MIN_WIDTH, DOPT_MIN_HEIGHT) + dlgMain.setModal(True) + + # ...................................................................... + # main dialog box, container + vbxMainContainer = QVBoxLayout(dlgMain) + + # main dialog box, current layer name + lblLayerName = QLabel("{0} {1}".format(i18n("Processing layer"), self.__sourceLayer.name())) + lblLayerName.setFixedHeight(30) + vbxMainContainer.addWidget(lblLayerName) + + # main dialog box, groupbox for layers options + gbxLayersMgt = QGroupBox("Layers management") + vbxMainContainer.addWidget(gbxLayersMgt) + + # main dialog box, groupbox for output options + gbxOutputResults = QGroupBox("Output results") + vbxMainContainer.addWidget(gbxOutputResults) + + vbxMainContainer.addStretch() + + # main dialog box, OK/Cancel buttons + dbbxOkCancel = QDialogButtonBox(dlgMain) + dbbxOkCancel.setOrientation(QtCore.Qt.Horizontal) + dbbxOkCancel.setStandardButtons(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) + dbbxOkCancel.accepted.connect(dlgMain.accept) + dbbxOkCancel.rejected.connect(dlgMain.reject) + vbxMainContainer.addWidget(dbbxOkCancel) + + # ...................................................................... + # create layout for groupbox "Layers management" + flLayersMgt = QFormLayout() + gbxLayersMgt.setLayout(flLayersMgt) + + ledLayerGroupName = QLineEdit() + ledLayerGroupName.setText(self.__outputOptions['layerGroupName']) + ledLayerGroupName.textChanged.connect(ledLayerGroupName_Changed) + flLayersMgt.addRow(i18nc('The name for a new group layer; the generated layers will be placed in this group.', 'New layer group name'), ledLayerGroupName) + + ledLayerColorName = QLineEdit() + ledLayerColorName.setText(self.__outputOptions['layerColorName']) + ledLayerColorName.textChanged.connect(ledLayerColorName_Changed) + flLayersMgt.addRow(i18nc('Defines how the name for each layer created from the channel is generated.', 'New layers color name'), ledLayerColorName) + + cmbOriginalLayerAction = QComboBox() + cmbOriginalLayerAction.addItems([ + ORIGINAL_LAYER_KEEPUNCHANGED, + ORIGINAL_LAYER_KEEPVISIBLE, + ORIGINAL_LAYER_KEEPHIDDEN, + ORIGINAL_LAYER_REMOVE + ]) + cmbOriginalLayerAction.setCurrentText(self.__outputOptions['originalLayerAction']) + cmbOriginalLayerAction.currentTextChanged.connect(cmbOriginalLayerAction_Changed) + flLayersMgt.addRow(i18n("Original layer"), cmbOriginalLayerAction) + + # ...................................................................... + # create layout for groupbox "Output results" + flOutputResults = QFormLayout() + gbxOutputResults.setLayout(flOutputResults) + + cmbOutputMode = QComboBox() + cmbOutputMode.addItems([ + OUTPUT_MODE_RGB, + OUTPUT_MODE_CMY, + OUTPUT_MODE_CMYK, + OUTPUT_MODE_LRGB, + OUTPUT_MODE_LCMY, + OUTPUT_MODE_LCMYK + ]) + cmbOutputMode.setCurrentText(self.__outputOptions['outputMode']) + cmbOutputMode.currentTextChanged.connect(cmbOutputMode_Changed) + flOutputResults.addRow(i18n("Mode"), cmbOutputMode) + + vbxPreviewLblContainer = QHBoxLayout() + flOutputResults.addRow('', vbxPreviewLblContainer) + vbxPreviewContainer = QHBoxLayout() + flOutputResults.addRow('', vbxPreviewContainer) + + # add preview progressbar + pbProgress = QProgressBar() + pbProgress.setFixedHeight(8) + pbProgress.setTextVisible(False) + pbProgress.setVisible(False) + pbProgress.setRange(0, 107) + flOutputResults.addRow('', pbProgress) + + + imageRatio = self.__sourceDocument.width() / self.__sourceDocument.height() + rect = QRect(0, 0, OUTPUT_PREVIEW_MAXSIZE, OUTPUT_PREVIEW_MAXSIZE) + + # always ensure that final preview width and/or height is lower or equal than OUTPUT_PREVIEW_MAXSIZE + if imageRatio < 1: + # width < height + rect.setWidth(int(imageRatio * OUTPUT_PREVIEW_MAXSIZE)) + else: + # width >= height + rect.setHeight(int(OUTPUT_PREVIEW_MAXSIZE / imageRatio)) + + imgThumbSrc = self.toQImage(self.__sourceLayer, rect) + + previewBaSrc.resize(imgThumbSrc.byteCount()) + ptr = imgThumbSrc.bits() + ptr.setsize(imgThumbSrc.byteCount()) + previewBaSrc = QByteArray(ptr.asstring()) + + + lblPreviewSrc = QLabel() + lblPreviewSrc.setPixmap(QPixmap.fromImage(imgThumbSrc)) + lblPreviewSrc.setFixedHeight(imgThumbSrc.height() + 4) + lblPreviewSrc.setFixedWidth(imgThumbSrc.width() + 4) + vbxPreviewContainer.addWidget(lblPreviewSrc) + + lblPreviewLblSrc = QLabel(i18n("Original")) + lblPreviewLblSrc.setFixedWidth(imgThumbSrc.width() + 4) + vbxPreviewLblContainer.addWidget(lblPreviewLblSrc) + + + vbxPreviewLblContainer.addWidget(QLabel(" ")) + vbxPreviewContainer.addWidget(QLabel(">")) + + lblPreview[3].setPixmap(QPixmap.fromImage(imgThumbSrc)) + lblPreview[3].setFixedHeight(imgThumbSrc.height() + 4) + lblPreview[3].setFixedWidth(imgThumbSrc.width() + 4) + vbxPreviewContainer.addWidget(lblPreview[3]) + + lblPreviewLbl[3] = QLabel(i18n("Cyan")) + lblPreviewLbl[3].setIndent(10) + lblPreviewLbl[3].setFixedWidth(imgThumbSrc.width() + 4) + vbxPreviewLblContainer.addWidget(lblPreviewLbl[3]) + + + lblPreview[2] = QLabel() + lblPreview[2].setPixmap(QPixmap.fromImage(imgThumbSrc)) + lblPreview[2].setFixedHeight(imgThumbSrc.height() + 4) + lblPreview[2].setFixedWidth(imgThumbSrc.width() + 4) + vbxPreviewContainer.addWidget(lblPreview[2]) + + lblPreviewLbl[2] = QLabel(i18n("Magenta")) + lblPreviewLbl[2].setIndent(10) + lblPreviewLbl[2].setFixedWidth(imgThumbSrc.width() + 4) + vbxPreviewLblContainer.addWidget(lblPreviewLbl[2]) + + + lblPreview[1] = QLabel() + lblPreview[1].setPixmap(QPixmap.fromImage(imgThumbSrc)) + lblPreview[1].setFixedHeight(imgThumbSrc.height() + 4) + lblPreview[1].setFixedWidth(imgThumbSrc.width() + 4) + vbxPreviewContainer.addWidget(lblPreview[1]) + + lblPreviewLbl[1] = QLabel(i18n("Yellow")) + lblPreviewLbl[1].setIndent(10) + lblPreviewLbl[1].setFixedWidth(imgThumbSrc.width() + 4) + vbxPreviewLblContainer.addWidget(lblPreviewLbl[1]) + + + lblPreview[0] = QLabel() + lblPreview[0].setPixmap(QPixmap.fromImage(imgThumbSrc)) + lblPreview[0].setFixedHeight(imgThumbSrc.height() + 4) + lblPreview[0].setFixedWidth(imgThumbSrc.width() + 4) + vbxPreviewContainer.addWidget(lblPreview[0]) + + lblPreviewLbl[0] = QLabel(i18n("Black")) + lblPreviewLbl[0].setIndent(10) + lblPreviewLbl[0].setFixedWidth(imgThumbSrc.width() + 4) + vbxPreviewLblContainer.addWidget(lblPreviewLbl[0]) + + + vbxPreviewLblContainer.addStretch() + vbxPreviewContainer.addStretch() + + buildPreview() + + returned = dlgMain.exec_() + + return returned + + + def progressNext(self, pProgress): + """Update progress bar""" + if pProgress is not None: + stepCurrent=pProgress.value()+1 + pProgress.setValue(stepCurrent) + QApplication.instance().processEvents() + + + def run(self): + """Run process for current layer""" + + pdlgProgress = QProgressDialog(self.__outputOptions['outputMode'], None, 0, 100, Application.activeWindow().qwindow()) + pdlgProgress.setWindowTitle(PLUGIN_DIALOG_TITLE) + pdlgProgress.setMinimumSize(640, 200) + pdlgProgress.setModal(True) + pdlgProgress.show() + + self.process(self.__sourceDocument, self.__sourceLayer, pdlgProgress) + + pdlgProgress.close() + + + + + def process(self, pDocument, pOriginalLayer, pProgress): + """Process given layer with current options""" + + self.layerNum = 0 + document = pDocument + originalLayer = pOriginalLayer + parentGroupLayer = None + currentProcessedLayer = None + originalLayerIsVisible = originalLayer.visible() + + + def getLayerByName(parent, value): + """search and return a layer by name, within given parent group""" + if parent == None: + return document.nodeByName(value) + + for layer in parent.childNodes(): + if layer.name() == value: + return layer + + return None + + + def duplicateLayer(currentProcessedLayer, value): + """Duplicate layer from given name + New layer become active layer + """ + + newLayer = None + srcLayer = None + srcName = re.match("^@(.*)", value) + + if not srcName is None: + # reference to a specific layer + if srcName[1] == 'original': + # original layer currently processed + srcLayer = originalLayer + else: + # a color layer previously built (and finished) + srcLayer = getLayerByName(parentGroupLayer, parseLayerName(self.__outputOptions['layerColorName'], srcName[1])) + else: + # a layer with a fixed name + srcLayer = document.nodeByName(parseLayerName(value, '')) + + + if not srcLayer is None: + newLayer = srcLayer.duplicate() + + self.layerNum+=1 + newLayer.setName("c2l-w{0}".format(self.layerNum)) + + parentGroupLayer.addChildNode(newLayer, currentProcessedLayer) + return newLayer + else: + return None + + + def newLayer(currentProcessedLayer, value): + """Create a new layer of given type + New layer become active layer + """ + + newLayer = None + + if value is None or not value['type'] in ['filllayer']: + # given type for new layer is not valid + # currently only one layer type is implemented + return None + + if value['type'] == 'filllayer': + infoObject = InfoObject(); + infoObject.setProperty("color", value['color']) + selection = Selection(); + selection.select(0, 0, document.width(), document.height(), 255) + + newLayer = document.createFillLayer(value['color'].name(), "color", infoObject, selection) + + + if newLayer: + self.layerNum+=1 + newLayer.setName("c2l-w{0}".format(self.layerNum)) + + parentGroupLayer.addChildNode(newLayer, currentProcessedLayer) + + # Need to force generator otherwise, information provided when creating layer seems to not be taken in + # account + newLayer.setGenerator("color", infoObject) + + return newLayer + else: + return None + + + def mergeDown(currentProcessedLayer, value): + """Merge current layer with layer below""" + if currentProcessedLayer is None: + return None + + newLayer = currentProcessedLayer.mergeDown() + # note: + # when layer is merged down: + # - a new layer seems to be created (reference to 'down' layer does not match anymore layer in group) + # - retrieved 'newLayer' reference does not match to new layer resulting from merge + # - activeNode() in document doesn't match to new layer resulting from merge + # maybe it's norpmal, maybe not... + # but the only solution to be able to work on merged layer (with current script) is to consider that from + # parent node, last child match to last added layer and then, to our merged layer + currentProcessedLayer = parentGroupLayer.childNodes()[-1] + # for an unknown reason, merged layer bounds are not corrects... :'-( + currentProcessedLayer.cropNode(0, 0, document.width(), document.height()) + return currentProcessedLayer + + + def applyBlendingMode(currentProcessedLayer, value): + """Set blending mode for current layer""" + if currentProcessedLayer is None or value is None or value == '': + return False + + currentProcessedLayer.setBlendingMode(value) + return True + + + def applyFilter(currentProcessedLayer, value): + """Apply filter to layer""" + if currentProcessedLayer is None or value is None or value == '': + return None + + filterName = re.match("name=([^;]+)", value) + + if filterName is None: + return None + + filter = Application.filter(filterName.group(1)) + filterConfiguration = filter.configuration() + + for parameter in value.split(';'): + parameterName = re.match("^([^=]+)=(.*)", parameter) + + if not parameterName is None and parameterName != 'name': + filterConfiguration.setProperty(parameterName.group(1), parameterName.group(2)) + + filter.setConfiguration(filterConfiguration) + filter.apply(currentProcessedLayer, 0, 0, document.width(), document.height()) + + return currentProcessedLayer + + + def parseLayerName(value, color): + """Parse layer name""" + + returned = value + + returned = returned.replace("{source:name}", originalLayer.name()) + returned = returned.replace("{mode}", OUTPUT_MODE_NFO[self.__outputOptions['outputMode']]['groupLayerName']) + returned = returned.replace("{color:short}", color) + if color == "C": + returned = returned.replace("{color:long}", i18n("Cyan")) + elif color == "M": + returned = returned.replace("{color:long}", i18n("Magenta")) + elif color == "Y": + returned = returned.replace("{color:long}", i18n("Yellow")) + elif color == "K": + returned = returned.replace("{color:long}", i18n("Black")) + elif color == "R": + returned = returned.replace("{color:long}", i18n("Red")) + elif color == "G": + returned = returned.replace("{color:long}", i18n("Green")) + elif color == "B": + returned = returned.replace("{color:long}", i18n("Blue")) + else: + returned = returned.replace("{color:long}", "") + + return returned + + + if document is None or originalLayer is None: + # should not occurs, but... + return None + + if not pProgress is None: + stepTotal = 4 + for layer in OUTPUT_MODE_NFO[self.__outputOptions['outputMode']]['layers']: + stepTotal+=len(layer['process']) + + pProgress.setRange(0, stepTotal) + + + if originalLayerIsVisible == False: + originalLayer.setVisible(True) + + # ---------------------------------------------------------------------- + # Create new group layer + parentGroupLayer = document.createGroupLayer(parseLayerName(self.__outputOptions['layerGroupName'], '')) + + self.progressNext(pProgress) + + currentProcessedLayer = None + + for layer in OUTPUT_MODE_NFO[self.__outputOptions['outputMode']]['layers']: + for process in layer['process']: + if process['action'] == 'duplicate': + currentProcessedLayer = duplicateLayer(currentProcessedLayer, process['value']) + elif process['action'] == 'new': + currentProcessedLayer = newLayer(currentProcessedLayer, process['value']) + elif process['action'] == 'merge down': + currentProcessedLayer = mergeDown(currentProcessedLayer, process['value']) + pass + elif process['action'] == 'blending mode': + applyBlendingMode(currentProcessedLayer, process['value']) + elif process['action'] == 'filter': + applyFilter(currentProcessedLayer, process['value']) + + self.progressNext(pProgress) + + if not currentProcessedLayer is None: + # rename currentProcessedLayer + currentProcessedLayer.setName(parseLayerName(self.__outputOptions['layerColorName'], layer['color'])) + + document.rootNode().addChildNode(parentGroupLayer, originalLayer) + self.progressNext(pProgress) + + if self.__outputOptions['originalLayerAction'] == ORIGINAL_LAYER_KEEPVISIBLE: + originalLayer.setVisible(True) + elif self.__outputOptions['originalLayerAction'] == ORIGINAL_LAYER_KEEPHIDDEN: + originalLayer.setVisible(False) + elif self.__outputOptions['originalLayerAction'] == ORIGINAL_LAYER_REMOVE: + originalLayer.remove() + else: + # ORIGINAL_LAYER_KEEPUNCHANGED + originalLayer.setVisible(originalLayerIsVisible) + + + self.progressNext(pProgress) + + document.refreshProjection() + self.progressNext(pProgress) + + document.setActiveNode(parentGroupLayer) + + return parentGroupLayer + + +#ChannelsToLayers(Krita.instance()).process(Application.activeDocument(), Application.activeDocument().activeNode(), None) +#ChannelsToLayers(Krita.instance()).action_triggered() diff --git a/plugins/python/channels2layers/kritapykrita_channels2layers.desktop b/plugins/python/channels2layers/kritapykrita_channels2layers.desktop new file mode 100644 index 0000000000..6ce6331805 --- /dev/null +++ b/plugins/python/channels2layers/kritapykrita_channels2layers.desktop @@ -0,0 +1,30 @@ +[Desktop Entry] +Type=Service +ServiceTypes=Krita/PythonPlugin +X-KDE-Library=channels2layers +X-Python-2-Compatible=false +X-Krita-Manual=Manual.html +Name=Channels to layers +Name[ca]=Canals a capes +Name[en_GB]=Channels to layers +Name[es]=Canales a capas +Name[et]=Kanalid kihtideks +Name[nl]=Kanalen naar lagen +Name[nn]=Kanalar til lag +Name[pt]=Canais para camadas +Name[pt_BR]=Canais para camadas +Name[sv]=Kanaler till lager +Name[uk]=Канали у шари +Name[x-test]=xxChannels to layersxx +Comment=Extract channels as color layers +Comment[ca]=Extreu canals com a capes de color +Comment[en_GB]=Extract channels as colour layers +Comment[es]=Extraer canales como capas de color +Comment[et]=Kanalite ekstraktimine värvikihtidena +Comment[nl]=Kanalen als kleurlagen extraheren +Comment[nn]=Hent ut kanalar som fargelag +Comment[pt]=Extrair os canais como camadas de cores +Comment[pt_BR]=Extraia canais como camadas de cores +Comment[sv]=Extrahera kanaler som färglager +Comment[uk]=Видобування каналів як колірних шарів +Comment[x-test]=xxExtract channels as color layersxx diff --git a/plugins/python/documenttools/kritapykrita_documenttools.desktop b/plugins/python/documenttools/kritapykrita_documenttools.desktop index 2e20c82584..a49b96d8fb 100644 --- a/plugins/python/documenttools/kritapykrita_documenttools.desktop +++ b/plugins/python/documenttools/kritapykrita_documenttools.desktop @@ -1,59 +1,61 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=documenttools X-Python-2-Compatible=true X-Krita-Manual=Manual.html Name=Document Tools Name[ar]=أدوات المستندات Name[ca]=Eines de document Name[ca@valencia]=Eines de document Name[cs]=Dokumentové nástroje Name[el]=Εργαλεία για έγγραφα Name[en_GB]=Document Tools Name[es]=Herramientas de documentos Name[et]=Dokumenditööriistad Name[eu]=Dokumentuen tresnak Name[fi]=Tiedostotyökalut Name[fr]=Outil Document Name[gl]=Ferramentas de documentos Name[it]=Strumenti per i documenti Name[ko]=문서 도구 Name[nl]=Documenthulpmiddelen Name[nn]=Dokumentverktøy Name[pl]=Narzędzia dokumentu Name[pt]=Ferramentas de Documentos Name[pt_BR]=Ferramentas de documento +Name[sk]=Nástroje dokumentu Name[sl]=Orodja za dokumente Name[sv]=Dokumentverktyg Name[tr]=Belge Araçları Name[uk]=Засоби документа Name[x-test]=xxDocument Toolsxx Name[zh_CN]=文档工具 Name[zh_TW]=檔案工具 Comment=Plugin to manipulate properties of selected documents Comment[ar]=ملحقة لتعديل خصائص المستندات المحددة Comment[ca]=Un connector per a manipular les propietats dels documents seleccionats Comment[ca@valencia]=Un connector per a manipular les propietats dels documents seleccionats Comment[cs]=Modul pro správu vlastností vybraných dokumentů Comment[el]=Πρόσθετο χειρισμού ιδιοτήτων σε επιλεγμένα έγγραφα Comment[en_GB]=Plugin to manipulate properties of selected documents Comment[es]=Complemento para manipular las propiedades de los documentos seleccionados Comment[et]=Plugin valitud dokumentide omaduste käitlemiseks Comment[eu]=Hautatutako dokumentuen propietateak manipulatzeko plugina Comment[fi]=Liitännäinen valittujen tiedostojen ominaisuuksien käsittelemiseksi Comment[fr]=Module externe de gestion des propriétés des documents sélectionnés Comment[gl]=Complemento para manipular as propiedades dos documentos seleccionados. Comment[it]=Estensione per manipolare le proprietà dei documenti selezionati Comment[ko]=선택한 문서의 속성을 변경하는 플러그인 Comment[nl]=Plug-in om eigenschappen van geselecteerde documenten te manipuleren Comment[nn]=Programtillegg for å endra eigenskapar på utvalde dokument Comment[pl]=Wtyczka do zmiany właściwości wybranych dokumentów Comment[pt]='Plugin' para manipular as propriedades dos documentos seleccionados Comment[pt_BR]=Plugin para manipular as propriedades de documentos selecionados +Comment[sk]=Zásuvný modul na manipuláciu s vlastnosťami vybraných dokumentov. Comment[sv]=Insticksprogram för att ändra egenskaper för valda dokument Comment[tr]=Seçili belgelerin özelliklerini değiştirmek için eklenti Comment[uk]=Додаток для керування властивостями позначених документів Comment[x-test]=xxPlugin to manipulate properties of selected documentsxx Comment[zh_CN]=用于编辑选定文档属性的插件 Comment[zh_TW]=用於修改所選檔案屬性的外掛程式 diff --git a/plugins/python/exportlayers/kritapykrita_exportlayers.desktop b/plugins/python/exportlayers/kritapykrita_exportlayers.desktop index 63e1511745..fc037603b9 100644 --- a/plugins/python/exportlayers/kritapykrita_exportlayers.desktop +++ b/plugins/python/exportlayers/kritapykrita_exportlayers.desktop @@ -1,61 +1,63 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=exportlayers X-Krita-Manual=Manual.html X-Python-2-Compatible=true Name=Export Layers Name[ar]=تصدير الطبقات Name[ca]=Exportació de capes Name[ca@valencia]=Exportació de capes Name[cs]=Exportovat vrstvy Name[de]=Ebenen exportieren Name[el]=Εξαγωγή επιπέδων Name[en_GB]=Export Layers Name[es]=Exportar capas Name[et]=Kihtide eksport Name[eu]=Esportatu geruzak Name[fi]=Vie tasoja Name[fr]=Exporter des calques Name[gl]=Exportar as capas Name[is]=Flytja út lög Name[it]=Esporta livelli Name[ko]=레이어 내보내기 Name[nl]=Lagen exporteren Name[nn]=Eksporter lag Name[pl]=Eksportuj warstwy Name[pt]=Exportar as Camadas Name[pt_BR]=Exportar camadas +Name[sk]=Exportovať vrstvy Name[sv]=Exportera lager Name[tr]=Katmanları Dışa Aktar Name[uk]=Експортувати шари Name[x-test]=xxExport Layersxx Name[zh_CN]=导出图层 Name[zh_TW]=匯出圖層 Comment=Plugin to export layers from a document Comment[ar]=ملحقة لتصدير الطبقات من مستند Comment[ca]=Un connector per exportar capes d'un document Comment[ca@valencia]=Un connector per exportar capes d'un document Comment[cs]=Modul pro export vrstev z dokumentu Comment[de]=Modul zum Exportieren von Ebenen aus einem Dokument Comment[el]=Πρόσθετο εξαγωγής επιπέδων από έγγραφο Comment[en_GB]=Plugin to export layers from a document Comment[es]=Complemento para exportar las capas de un documento Comment[et]=Plugin dokumendi kihtide eksportimiseks Comment[eu]=Dokumentu batetik geruzak esportatzeko plugina Comment[fi]=Liitännäisen tiedoston tasojen viemiseksi Comment[fr]=Module externe d'export de calques d'un document Comment[gl]=Complemento para exportar as capas dun documento. Comment[it]=Estensione per esportare i livelli da un documento Comment[ko]=문서에서 레이어를 내보내는 플러그인 Comment[nl]=Plug-in om lagen uit een document te exporteren Comment[nn]=Programtillegg for eksportering av biletlag i dokument Comment[pl]=Wtyczka do eksportowania warstw z dokumentu Comment[pt]='Plugin' para exportar as camadas de um documento Comment[pt_BR]=Plugin para exportar as camadas de um documento +Comment[sk]=Zásuvný modul na export vrstiev z dokumentu. Comment[sv]=Insticksprogram för att exportera lager från ett dokument Comment[tr]=Belgenin katmanlarını dışa aktarmak için eklenti Comment[uk]=Додаток для експортування шарів з документа Comment[x-test]=xxPlugin to export layers from a documentxx Comment[zh_CN]=用于从文档导出图层的插件 Comment[zh_TW]=用於從檔案匯出圖層的外掛程式 diff --git a/plugins/python/filtermanager/kritapykrita_filtermanager.desktop b/plugins/python/filtermanager/kritapykrita_filtermanager.desktop index 21241e34de..dbf8d1cb90 100644 --- a/plugins/python/filtermanager/kritapykrita_filtermanager.desktop +++ b/plugins/python/filtermanager/kritapykrita_filtermanager.desktop @@ -1,61 +1,62 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=filtermanager X-Python-2-Compatible=true X-Krita-Manual=Manual.html Name=Filter Manager Name[ar]=مدير المرشّحات Name[ca]=Gestor de filtres Name[ca@valencia]=Gestor de filtres Name[cs]=Správce filtrů Name[de]=Filterverwaltung Name[el]=Διαχειριστής φίλτρων Name[en_GB]=Filter Manager Name[es]=Gestor de filtros Name[et]=Filtrihaldur Name[eu]=Iragazki-kudeatzailea Name[fi]=Suodatinhallinta Name[fr]=Gestionnaire de fichiers Name[gl]=Xestor de filtros Name[it]=Gestore dei filtri Name[ko]=필터 관리자 Name[nl]=Beheerder van filters Name[nn]=Filterhandsamar Name[pl]=Zarządzanie filtrami Name[pt]=Gestor de Filtros Name[pt_BR]=Gerenciador de filtros +Name[sk]=Správca filtrov Name[sl]=Upravljanje filtrov Name[sv]=Filterhantering Name[tr]=Süzgeç Yöneticisi Name[uk]=Керування фільтрами Name[x-test]=xxFilter Managerxx Name[zh_CN]=滤镜管理器 Name[zh_TW]=濾鏡管理器 Comment=Plugin to filters management Comment[ar]=ملحقة إدارة المرشّحات Comment[ca]=Un connector per a gestionar filtres Comment[ca@valencia]=Un connector per a gestionar filtres Comment[cs]=Modul pro správu filtrů Comment[de]=Modul zum Verwalten von Filtern Comment[el]=Πρόσθετο για τη διαχείριση φίλτρων Comment[en_GB]=Plugin to filters management Comment[es]=Complemento para la gestión de filtros Comment[et]=Plugin filtrite haldamiseks Comment[eu]=Iragazkiak kudeatzeko plugina Comment[fi]=Liitännäinen suodatinten hallintaan Comment[fr]=Module externe de gestion des filtres Comment[gl]=Complemento para a xestión de filtros. Comment[it]=Estensione per la gestione dei filtri Comment[ko]=필터 관리에 대한 플러그인 Comment[nl]=Plug-in voor beheer van filters Comment[nn]=Programtillegg for filterhandsaming Comment[pl]=Wtyczka do zarządzania filtrami Comment[pt]='Plugin' para a gestão de filtros Comment[pt_BR]=Plugin para gerenciamento de filtros Comment[sv]=Insticksprogram för filterhantering Comment[tr]=Süzgeç yönetimi için eklenti Comment[uk]=Додаток для керування фільтрами Comment[x-test]=xxPlugin to filters managementxx Comment[zh_CN]=用于管理滤镜的插件。 Comment[zh_TW]=用於濾鏡管理的外掛程式 diff --git a/plugins/python/krita_script_starter/kritapykrita_krita_script_starter.desktop b/plugins/python/krita_script_starter/kritapykrita_krita_script_starter.desktop index c67432b5d7..df0e586aee 100644 --- a/plugins/python/krita_script_starter/kritapykrita_krita_script_starter.desktop +++ b/plugins/python/krita_script_starter/kritapykrita_krita_script_starter.desktop @@ -1,49 +1,51 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=krita_script_starter X-Python-2-Compatible=false X-Krita-Manual=Manual.html Name=Krita Script Starter Name[ca]=Iniciador de scripts del Krita Name[ca@valencia]=Iniciador de scripts del Krita Name[cs]=Spouštěč skriptů Krita Name[en_GB]=Krita Script Starter Name[es]=Iniciador de guiones de Krita Name[et]=Krita skriptialustaja Name[eu]=Krita-ren script abiarazlea +Name[fr]=Lanceur de scripts Krita Name[gl]=Iniciador de scripts de Krita Name[it]=Iniziatore di script per Krita Name[ko]=Krita 스크립트 시작 도구 Name[nl]=Script-starter van Krita Name[nn]=Krita skriptbyggjar Name[pl]=Starter skryptów Krity Name[pt]=Inicialização do Programa do Krita Name[pt_BR]=Inicializador de script do Krita Name[sv]=Krita skriptstart Name[tr]=Krita Betik Başlatıcı Name[uk]=Створення скрипту Krita Name[x-test]=xxKrita Script Starterxx Name[zh_CN]=Krita 空脚本生成器 Name[zh_TW]=Krita 指令啟動器 Comment=Create the metadata and file structure for a new Krita script Comment[ca]=Crea les metadades i l'estructura de fitxers d'un script nou del Krita Comment[ca@valencia]=Crea les metadades i l'estructura de fitxers d'un script nou del Krita Comment[en_GB]=Create the metadata and file structure for a new Krita script Comment[es]=Crear los metadatos y la estructura de archivos para un nuevo guion de Krita Comment[et]=Uue Krita skripti metaandmete ja failistruktuuri loomine Comment[eu]=Sortu Krita-script berri baterako meta-datuak eta fitxategi egitura +Comment[fr]=Créer les métadonnées et la structure du fichier pour le nouveau script Krita Comment[gl]=Crear os metadatos e a estrutura de ficheiros para un novo script de Krita. Comment[it]=Crea i metadati e la struttura dei file per un nuovo script di Krita Comment[ko]=새 Krita 스크립트에 대한 메타데이터 및 파일 구조 생성 Comment[nl]=Maak de metagegevens en bestandsstructuur voor een nieuw Krita-script Comment[nn]=Generer metadata og filstruktur for nye Krita-skript Comment[pl]=Utwórz metadane i strukturę plików dla nowego skryptu Krity Comment[pt]=Cria os meta-dados e a estrutura de ficheiros para um novo programa do Krita Comment[pt_BR]=Cria os metadados e a estrutura de arquivos para um novo script do Krita Comment[sv]=Skapa metadata och filstruktur för ett nytt Krita-skript Comment[tr]=Yeni Krita betiği için üstveri ve dosya yapısı oluştur Comment[uk]=Створення метаданих і структури файлів для нового скрипту Krita Comment[x-test]=xxCreate the metadata and file structure for a new Krita scriptxx Comment[zh_CN]=为新的 Krita 脚本创建元数据及文件结构 Comment[zh_TW]=為新的 Krita 指令稿建立中繼資料和檔案建構體 diff --git a/plugins/python/mixer_slider_docker/kritapykrita_mixer_slider_docker.desktop b/plugins/python/mixer_slider_docker/kritapykrita_mixer_slider_docker.desktop index 0deb3abd84..508c9b149b 100644 --- a/plugins/python/mixer_slider_docker/kritapykrita_mixer_slider_docker.desktop +++ b/plugins/python/mixer_slider_docker/kritapykrita_mixer_slider_docker.desktop @@ -1,42 +1,45 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=mixer_slider_docker X-Python-2-Compatible=false X-Krita-Manual=Manual.html Name=Mixer Slider docker Name[ca]=Acoblador Control lliscant del mesclador Name[ca@valencia]=Acoblador Control lliscant del mesclador Name[en_GB]=Mixer Slider docker Name[es]=Panel del deslizador del mezclador Name[et]=Mikseri liuguri dokk Name[eu]=Nahasle graduatzaile panela +Name[fr]=Panneau de mixage avec curseurs Name[it]=Area di aggancio cursore di miscelazione Name[ko]=믹서 슬라이더 도커 Name[nl]=Vastzetter van schuifregelaar van mixer Name[nn]=Fargeblandings-dokkpanel Name[pt]=Área da Barra de Mistura Name[pt_BR]=Docker do misturador de barras +Name[sk]=Panel posuvníkov miešača Name[sv]=Dockningsfönster för blandningsreglage Name[uk]=Бічна панель повзунка мікшера Name[x-test]=xxMixer Slider dockerxx Name[zh_CN]=混色滑动条工具面板 Name[zh_TW]=混色滑動條工具面板 Comment=A color slider. Comment[ca]=Un control lliscant per al color. Comment[ca@valencia]=Un control lliscant per al color. Comment[en_GB]=A colour slider. Comment[es]=Deslizador de color. Comment[et]=Värviliugur. Comment[eu]=Kolore graduatzaile bat. +Comment[fr]=Un curseur pour les couleurs. Comment[it]=Cursore del colore. Comment[ko]=컬러 슬라이더입니다. Comment[nl]=Een schuifregelaar voor kleur Comment[nn]=Palett med fargeovergangar Comment[pt]=Uma barra de cores. Comment[pt_BR]=Uma barra de cores. Comment[sv]=Ett färgreglage. Comment[uk]=Повзунок кольору. Comment[x-test]=xxA color slider.xx Comment[zh_CN]=这是一个通过滑动条控制颜色的面板。 Comment[zh_TW]=色彩滑動條。 diff --git a/plugins/python/plugin_importer/kritapykrita_plugin_importer.desktop b/plugins/python/plugin_importer/kritapykrita_plugin_importer.desktop index a8e80cca8e..192ff576df 100644 --- a/plugins/python/plugin_importer/kritapykrita_plugin_importer.desktop +++ b/plugins/python/plugin_importer/kritapykrita_plugin_importer.desktop @@ -1,50 +1,53 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=plugin_importer X-Python-2-Compatible=false X-Krita-Manual=manual.html Name=Python Plugin Importer Name[ca]=Importador de connectors en Python Name[ca@valencia]=Importador de connectors en Python Name[cs]=Importér modulů pro Python Name[en_GB]=Python Plugin Importer Name[es]=Importador de complementos de Python Name[et]=Pythoni plugina importija Name[eu]=Python plugin inportatzaile bat +Name[fr]=Importateur de modules externes Python Name[gl]=Importador de complementos de Python Name[it]=Importatore estensioni Python Name[ko]=Python 플러그인 가져오기 도구 Name[nl]=Importeur van Plugin voor Python Name[nn]=Importering av Python-tillegg Name[pl]=Import wtyczek Pythona Name[pt]=Importador de 'Plugins' do Python Name[pt_BR]=Importador de plugins em Python +Name[sk]=Importér zásuvných modulov Python Name[sv]=Python-insticksimport Name[tr]=Python Eklenti İçe Aktarıcı Name[uk]=Засіб імпортування додатків Python Name[x-test]=xxPython Plugin Importerxx Name[zh_CN]=Python 插件导入器 Name[zh_TW]=Python 外掛程式匯入工具 Comment=Imports Python plugins from zip files. Comment[ca]=Importa connectors en Python a partir de fitxers «zip». Comment[ca@valencia]=Importa connectors en Python a partir de fitxers «zip». Comment[cs]=Importuje moduly Pythonu ze souborů zip. Comment[en_GB]=Imports Python plugins from zip files. Comment[es]=Importa complementos de Python desde archivos zip. Comment[et]=Pythoni pluginate import zip-failidest. Comment[eu]=Python pluginak inportatzen ditu zip fitxategietatik. +Comment[fr]=Importe les modules externes Python à partir des fichiers « ZIP ». Comment[gl]=Importa complementos de Python de ficheiros zip. Comment[it]=Importa le estensioni Python dai file compressi. Comment[ko]=ZIP 파일에서 Python 플러그인을 가져옵니다. Comment[nl]=Importeert Python-plug-ins uit zip-bestanden. Comment[nn]=Importerer Python-baserte programtillegg frå .zip-filer Comment[pl]=Importuj wtyczki Pythona z plików zip. Comment[pt]=Importa os 'plugins' em Python a partir de ficheiros Zip. Comment[pt_BR]=Importa plugins em Python a partir de arquivos zip. Comment[sv]=Importerar Python-insticksprogram från zip-filer. Comment[tr]=Python eklentilerini zip dosyasından içe aktarır. Comment[uk]=Імпортує додатки Python з файлів zip. Comment[x-test]=xxImports Python plugins from zip files.xx Comment[zh_CN]=从 zip 文件导入 Python 插件。 Comment[zh_TW]=匯入 Zip 檔案中的 Python 外掛程式。 diff --git a/plugins/python/quick_settings_docker/kritapykrita_quick_settings_docker.desktop b/plugins/python/quick_settings_docker/kritapykrita_quick_settings_docker.desktop index fc7278f2ed..b59d1cd7c5 100644 --- a/plugins/python/quick_settings_docker/kritapykrita_quick_settings_docker.desktop +++ b/plugins/python/quick_settings_docker/kritapykrita_quick_settings_docker.desktop @@ -1,57 +1,58 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=quick_settings_docker X-Python-2-Compatible=true X-Krita-Manual=Manual.html Name=Quick Settings Docker Name[ar]=رصيف بإعدادات سريعة Name[ca]=Acoblador Arranjament ràpid Name[ca@valencia]=Acoblador Arranjament ràpid Name[cs]=Dok pro rychlé nastavení Name[el]=Προσάρτηση γρήγορων ρυθμίσεων Name[en_GB]=Quick Settings Docker Name[es]=Panel de ajustes rápidos Name[et]=Kiirseadistuste dokk Name[eu]=Ezarpen azkarren panela Name[fi]=Pika-asetustelakka Name[fr]=Réglages rapides Name[gl]=Doca de configuración rápida Name[it]=Area di aggancio delle impostazioni rapide Name[ko]=빠른 설정 도킹 패널 Name[nl]=Verankering voor snelle instellingen Name[nn]=Snøgginnstillingar-dokkpanel Name[pl]=Dok szybkich ustawień Name[pt]=Área de Configuração Rápida Name[pt_BR]=Área de configuração rápida +Name[sk]=Panel rýchlych nastavení Name[sv]=Dockningspanel med snabbinställningar Name[tr]=Hızlı Ayarlar Doku Name[uk]=Панель швидких параметрів Name[x-test]=xxQuick Settings Dockerxx Name[zh_CN]=快速设置工具面板 Name[zh_TW]=「快速設定」面板 Comment=A Python-based docker for quickly changing brush size and opacity. Comment[ar]=رصيف بِ‍«پيثون» لتغيير حجم الفرشاة وشفافيّتها بسرعة. Comment[ca]=Un acoblador basat en Python per a canviar ràpidament la mida i l'opacitat del pinzell. Comment[ca@valencia]=Un acoblador basat en Python per a canviar ràpidament la mida i l'opacitat del pinzell. Comment[el]=Ένα εργαλείο προσάρτησης σε Python για γρήγορη αλλαγή του μεγέθους και της αδιαφάνειας του πινέλου. Comment[en_GB]=A Python-based docker for quickly changing brush size and opacity. Comment[es]=Un panel basado en Python para cambiar rápidamente el tamaño y la opacidad del pincel. Comment[et]=Pythoni-põhine dokk pintsli suuruse ja läbipaistmatuse kiireks muutmiseks. Comment[eu]=Isipu-neurria eta -opakotasuna aldatzeko Python-oinarridun panel bat. Comment[fi]=Python-pohjainen telakka siveltimen koon ja läpikuultavuuden nopean muuttamiseen. Comment[fr]=Panneau en python pour modifier rapidement la taille et l'opacité des brosses. Comment[gl]=Unha doca baseada en Python para cambiar rapidamente a opacidade e o tamaño dos pinceis. Comment[it]=Un'area di aggancio basata su Python per cambiare rapidamente la dimensione del pennello e l'opacità. Comment[ko]=브러시 크기와 불투명도를 빠르게 변경할 수 있는 Python 기반 도킹 패널입니다. Comment[nl]=Een op Python gebaseerde docker voor snel wijzigen van penseelgrootte en dekking. Comment[nn]=Python-basert dokkpanel for kjapp endring av penselstorleik/-tettleik Comment[pl]=Dok oparty na Pythonie do szybkiej zmiany rozmiaru i nieprzezroczystości pędzla. Comment[pt]=Uma área acoplável em Python para mudar rapidamente o tamanho e opacidade do pincel. Comment[pt_BR]=Uma área acoplável em Python para mudar rapidamente o tamanho e opacidade do pincel. Comment[sv]=En Python-baserad dockningspanel för att snabbt ändra penselstorlek och ogenomskinlighet. Comment[tr]=Fırça boyutunu ve matlığını hızlıca değiştirmek için Python-tabanlı bir dok. Comment[uk]=Панель на основі мови програмування Python для швидкої зміни розміру та непрозорості пензля. Comment[x-test]=xxA Python-based docker for quickly changing brush size and opacity.xx Comment[zh_CN]=这是一个基于 Python 的工具面板,用于快速更改笔刷尺寸和透明度。 Comment[zh_TW]=基於 Python 的面板,用於快速變更筆刷尺寸和不透明度。 diff --git a/plugins/python/scriptdocker/kritapykrita_scriptdocker.desktop b/plugins/python/scriptdocker/kritapykrita_scriptdocker.desktop index d9f15ec2b1..3918d516a5 100644 --- a/plugins/python/scriptdocker/kritapykrita_scriptdocker.desktop +++ b/plugins/python/scriptdocker/kritapykrita_scriptdocker.desktop @@ -1,54 +1,56 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=scriptdocker X-Python-2-Compatible=false Name=Script Docker Name[ar]=رصيف سكربتات Name[ca]=Acoblador Script Name[ca@valencia]=Acoblador Script Name[cs]=Dok skriptu Name[el]=Προσάρτηση σεναρίων Name[en_GB]=Script Docker Name[es]=Panel de guiones Name[et]=Skriptidokk Name[eu]=Script-panela Name[fi]=Skriptitelakka Name[fr]=Panneau de script Name[gl]=Doca de scripts Name[it]=Area di aggancio degli script Name[ko]=스크립트 도킹 패널 Name[nl]=Verankering van scripts Name[nn]=Skript-dokkpanel Name[pl]=Dok skryptów Name[pt]=Área de Programas Name[pt_BR]=Área de scripts +Name[sk]=Panel skriptov Name[sv]=Dockningsfönster för skript Name[tr]=Betik Doku Name[uk]=Бічна панель скриптів Name[x-test]=xxScript Dockerxx Name[zh_CN]=脚本工具面板 Name[zh_TW]=「指令稿」面板 Comment=A Python-based docker for create actions and point to Python scripts Comment[ar]=رصيف بِ‍«پيثون» لإنشاء الإجراءات والإشارة إلى سكربتات «پيثون» Comment[ca]=Un acoblador basat en Python per a crear accions i apuntar a scripts en Python Comment[ca@valencia]=Un acoblador basat en Python per a crear accions i apuntar a scripts en Python Comment[el]=Ένα εργαλείο προσάρτησης σε Python για τη δημιουργία ενεργειών και τη δεικτοδότηση σεναρίων σε Python Comment[en_GB]=A Python-based docker for create actions and point to Python scripts Comment[es]=Un panel basado en Python para crear y apuntar a guiones de Python Comment[et]=Pythoni-põhine dokk toimingute loomiseks ja viitamiseks Pythoni skriptidele Comment[eu]=Python-oinarridun panel bat. +Comment[fr]=Un panneau réalisé en Python pour créer des actions et s'interfacer avec des scripts Python Comment[gl]=Unha doca escrita en Python para crear accións e apuntar a scripts escritos en Python. Comment[it]=Un'area di aggancio basata su Python per creare azioni e scegliere script Python Comment[ko]=작업 생성 및 Python 스크립트를 가리키는 Python 기반 도킹 패널 Comment[nl]=Een op Python gebaseerde vastzetter voor aanmaakacties en wijzen naar Python-scripts Comment[nn]=Python-basert dokkpanel for å laga handlingar og peika til Python-skript Comment[pl]=Dok oparty na pythonie do tworzenia działań i punktów dla skryptów pythona Comment[pt]=Uma área acoplável, feita em Python, para criar acções e apontar para programas em Python Comment[pt_BR]=Uma área acoplável feita em Python para criar ações e apontar para scripts em Python Comment[sv]=Ett Python-baserat dockningsfönster för att skapa åtgärder och peka ut Python-skript Comment[tr]=Eylemler oluşturmak ve Python betiklerine yönlendirmek için Python-tabanlı bir dok Comment[uk]=Бічна панель для створення дій і керування скриптами на основі Python. Comment[x-test]=xxA Python-based docker for create actions and point to Python scriptsxx Comment[zh_CN]=这是一个基于 Python 的工具面板,用于创建操作,并将他们指向 Python 脚本。 Comment[zh_TW]=基於 Python 的面板,用於建立動作並指向 Python 指令稿 diff --git a/plugins/python/scripter/kritapykrita_scripter.desktop b/plugins/python/scripter/kritapykrita_scripter.desktop index d157294c5e..4aa74a9459 100644 --- a/plugins/python/scripter/kritapykrita_scripter.desktop +++ b/plugins/python/scripter/kritapykrita_scripter.desktop @@ -1,53 +1,55 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=scripter X-Python-2-Compatible=true X-Krita-Manual=Manual.html Name=Scripter Name[ca]=Scripter Name[ca@valencia]=Scripter Name[de]=Scripter Name[el]=Σενάρια Name[en_GB]=Scripter Name[es]=Guionador Name[et]=Skriptija Name[eu]=Script egilea Name[fr]=Scripter Name[gl]=Executor de scripts Name[it]=Scripter Name[ko]=스크립트 도구 Name[nl]=Scriptmaker Name[nn]=Skriptkøyrer Name[pl]=Skrypter Name[pt]=Programador Name[pt_BR]=Criador de scripts +Name[sk]=Skriptér Name[sv]=Skriptgenerator Name[tr]=Betik yazarı Name[uk]=Скриптер Name[x-test]=xxScripterxx Name[zh_CN]=脚本工具 Name[zh_TW]=指令稿編寫者 Comment=Plugin to execute ad-hoc Python code Comment[ca]=Connector per executar codi «ad hoc» en Python Comment[ca@valencia]=Connector per executar codi «ad hoc» en Python Comment[el]=Πρόσθετο για την εκτέλεση συγκεκριμένου κώδικα Python Comment[en_GB]=Plugin to execute ad-hoc Python code Comment[es]=Complemento para ejecutar código Python a medida Comment[et]=Plugin kohapeal loodud Pythoni koodi täitmiseks Comment[eu]=Berariaz egindako Python kodea exekutatzeko plugina Comment[fi]=Liitännäinen satunnaisen Python-koodin suorittamiseksi +Comment[fr]=Module externe pour exécuter du code Python « ad-hoc » Comment[gl]=Complemento para executar código de Python escrito no momento. Comment[it]=Estensione per eseguire ad-hoc codice Python Comment[ko]=즉석에서 Python 코드를 실행하는 플러그인 Comment[nl]=Plug-in om ad-hoc Python code uit te voeren Comment[nn]=Programtillegg for køyring av ad hoc Python-kode Comment[pl]=Wtyczka do wykonywania kodu Pythona ad-hoc Comment[pt]='Plugin' para executar código em Python arbitrário Comment[pt_BR]=Plugin para executar código em Python arbitrário Comment[sv]=Insticksprogram för att köra godtycklig Python-kod Comment[tr]=Geçici Python kodu çalıştırmak için eklenti Comment[uk]=Додаток для виконання апріорного коду Python Comment[x-test]=xxPlugin to execute ad-hoc Python codexx Comment[zh_CN]=用于执行当场编写的 Python 代码的插件 Comment[zh_TW]=外掛程式,用於執行特定 Python 程式碼 diff --git a/plugins/python/tenbrushes/kritapykrita_tenbrushes.desktop b/plugins/python/tenbrushes/kritapykrita_tenbrushes.desktop index 8b4ace4635..d4e8bfb307 100644 --- a/plugins/python/tenbrushes/kritapykrita_tenbrushes.desktop +++ b/plugins/python/tenbrushes/kritapykrita_tenbrushes.desktop @@ -1,56 +1,58 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=tenbrushes X-Python-2-Compatible=true X-Krita-Manual=Manual.html Name=Ten Brushes Name[ar]=عشرُ فُرش Name[ca]=Deu pinzells Name[ca@valencia]=Deu pinzells Name[cs]=Deset štětců Name[el]=Δέκα πινέλα Name[en_GB]=Ten Brushes Name[es]=Diez pinceles Name[et]=Kümme pintslit Name[eu]=Hamar isipu Name[fi]=Kymmenen sivellintä Name[fr]=Raccourcis des préréglages de brosses Name[gl]=Dez pinceis Name[is]=Tíu penslar Name[it]=Dieci pennelli Name[ko]=브러시 10개 Name[nl]=Tien penselen Name[nn]=Ti penslar Name[pl]=Dziesięć pędzli Name[pt]=Dez Pincéis Name[pt_BR]=Dez pincéis +Name[sk]=10 Štetcov Name[sv]=Tio penslar Name[tr]=On Fırça Name[uk]=Десять пензлів Name[x-test]=xxTen Brushesxx Name[zh_CN]=常用笔刷快捷键 Name[zh_TW]=10 個筆刷 Comment=Assign a preset to ctrl-1 to ctrl-0 Comment[ca]=Assigna una predefinició des de Ctrl-1 a Ctrl-0 Comment[ca@valencia]=Assigna una predefinició des de Ctrl-1 a Ctrl-0 Comment[el]=Αντιστοίχιση προκαθορισμένου από ctrl-1 στο ctrl-0 Comment[en_GB]=Assign a preset to ctrl-1 to ctrl-0 Comment[es]=Asignar una preselección a Ctrl-1 hasta Ctrl-0 Comment[et]=Valmisvaliku omistamine Ctrl+1 kuni Ctrl+0 Comment[eu]=Aurrezarpena ezarri ktrl-1'etik ktrl-0'ra arte Comment[fi]=Kytke esiasetukset Ctrl-1:stä Ctrl-0:aan +Comment[fr]=Associer un préréglage à de la touche « CRTL - 1 » à « CTRL - 2 » Comment[gl]=Asigne unha predefinición do Ctrl+1 ao Ctrl+0. Comment[it]=Assegna una preimpostazione per ctrl-1 a ctrl-0 Comment[ko]=Ctrl+1부터 Ctrl+0까지 프리셋 할당 Comment[nl]=Een voorinstelling toekennen aan Ctrl-1 tot Ctrl-0 Comment[nn]=Byt til ferdigpenslar med «Ctrl + 1» til «Ctrl + 0» Comment[pl]=Przypisz nastawę do ctrl-1 lub ctrl-0 Comment[pt]=Atribuir uma predefinição de Ctrl-1 a Ctrl-0 Comment[pt_BR]=Atribuir uma predefinição de Ctrl-1 a Ctrl-0 Comment[sv]=Tilldela en förinställning för Ctrl+1 till Ctrl+0 Comment[tr]= ctrl-1 ve ctrl-0 için ayar atama Comment[uk]=Прив’язування наборів налаштувань до скорочень від ctrl-1 до ctrl-0 Comment[x-test]=xxAssign a preset to ctrl-1 to ctrl-0xx Comment[zh_CN]=将预设分配给由 Ctrl+1 到 Ctrl+0 的快捷键 Comment[zh_TW]=將預設指定給 ctrl-1 至 ctrl-0 diff --git a/plugins/python/tenscripts/kritapykrita_tenscripts.desktop b/plugins/python/tenscripts/kritapykrita_tenscripts.desktop index c8341c94e6..bbe83ba7b7 100644 --- a/plugins/python/tenscripts/kritapykrita_tenscripts.desktop +++ b/plugins/python/tenscripts/kritapykrita_tenscripts.desktop @@ -1,56 +1,57 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=tenscripts X-Python-2-Compatible=true X-Krita-Manual=Manual.html Name=Ten Scripts Name[ar]=عشرُ سكربتات Name[ca]=Deu scripts Name[ca@valencia]=Deu scripts Name[en_GB]=Ten Scripts Name[es]=Diez guiones Name[et]=Kümme skripti Name[eu]=Hamar script Name[fi]=Kymmenen skriptiä Name[fr]=Raccourcis des scripts Name[gl]=Dez scripts Name[is]=Tíu skriftur Name[it]=Dieci script Name[ko]=스크립트 10개 Name[nl]=Tien scripts Name[nn]=Ti skript Name[pl]=Skrypty Ten Name[pt]=Dez Programas Name[pt_BR]=Dez scripts +Name[sk]=Desať skriptov Name[sl]=Deset skriptov Name[sv]=Tio skript Name[tr]=On Betik Name[uk]=Десять скриптів Name[x-test]=xxTen Scriptsxx Name[zh_CN]=常用脚本快捷键 Name[zh_TW]=10 個指令稿 Comment=A Python-based plugin for creating ten actions and assign them to Python scripts Comment[ar]=ملحقة بِ‍«پيثون» لإنشاء ١٠ إجراءات وإسنادها إلى سكربتات «پيثون» Comment[ca]=Un connector basat en Python per a crear deu accions i assignar-les a scripts en Python Comment[ca@valencia]=Un connector basat en Python per a crear deu accions i assignar-les a scripts en Python Comment[en_GB]=A Python-based plugin for creating ten actions and assign them to Python scripts Comment[es]=Un complemento basado en Python para crear diez acciones y asignarlas a guiones de Python Comment[et]=Pythoni-põhine plugin kümne toimingu loomiseks ja nende omistamiseks Pythoni skriptidele Comment[eu]=Hamar ekintza sortu eta haiek Python-scriptei esleitzeko Python-oinarridun plugin bat Comment[fi]=Python-pohjainen liitännäinen kymmenen toiminnon luomiseksi kytkemiseksi Python-skripteihin -Comment[fr]=Module externe basé sur Python pour créer dix actions et les assigner à des scripts Python +Comment[fr]=Module externe développé à partir de Python pour créer dix actions et les assigner à des scripts Python Comment[gl]=Un complemento escrito en Python para crear dez accións e asignalas a scripts escritos en Python. Comment[it]=Un'estensione basata su Python per creare dieci azioni e assegnarle a script Python Comment[ko]=작업 10개를 생성하고 이를 Python 스크립트에 할당하는 Python 기반 플러그인 Comment[nl]=Een op Python gebaseerde plug-in voor aanmaken van tien acties en ze dan toewijzen aan Python-scripts Comment[nn]=Python-basert tillegg som legg til ti handlingar du kan tildela til Python-skript Comment[pl]=Wtyczka oparta na Pythonie do tworzenia działań i przypisywanie ich skryptom Pythona Comment[pt]=Um 'plugin' feito em Python para criar dez acções e atribuí-las a programas em Python Comment[pt_BR]=Um plugin feito em Python para criar dez ações e atribuí-las a scripts em Python Comment[sv]=Ett Python-baserat insticksprogram för att skapa tio åtgärder och tilldela dem till Python-skript Comment[tr]=On eylem oluşturmak ve Python betiklerine atamak için Python tabanlı bir eklenti Comment[uk]=Скрипт на основі Python для створення десяти дій і прив'язування до них скриптів Python Comment[x-test]=xxA Python-based plugin for creating ten actions and assign them to Python scriptsxx Comment[zh_CN]=这是一个基于 Python 编写的插件,它可以创建十种操作,并将它们指定到特定 Python 脚本。 Comment[zh_TW]=基於 Python 的外掛程式,用於建立 10 個動作並且將它們指定給 Python 指令稿 diff --git a/plugins/tools/basictools/kis_tool_ellipse.cc b/plugins/tools/basictools/kis_tool_ellipse.cc index 716b588edc..00e36ecf61 100644 --- a/plugins/tools/basictools/kis_tool_ellipse.cc +++ b/plugins/tools/basictools/kis_tool_ellipse.cc @@ -1,82 +1,83 @@ /* * kis_tool_ellipse.cc - part of Krayon * * Copyright (c) 2000 John Califf * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2004 Clarence Dang * Copyright (c) 2009 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_tool_ellipse.h" #include #include #include #include "kis_figure_painting_tool_helper.h" #include KisToolEllipse::KisToolEllipse(KoCanvasBase * canvas) : KisToolEllipseBase(canvas, KisToolEllipseBase::PAINT, KisCursor::load("tool_ellipse_cursor.png", 6, 6)) { setObjectName("tool_ellipse"); setSupportOutline(true); } KisToolEllipse::~KisToolEllipse() { } void KisToolEllipse::resetCursorStyle() { KisToolEllipseBase::resetCursorStyle(); overrideCursorIfNotEditable(); } void KisToolEllipse::finishRect(const QRectF& rect, qreal roundCornersX, qreal roundCornersY) { Q_UNUSED(roundCornersX); Q_UNUSED(roundCornersY); if (rect.isEmpty()) return; const KisToolShape::ShapeAddInfo info = shouldAddShape(currentNode()); if (!info.shouldAddShape) { KisFigurePaintingToolHelper helper(kundo2_i18n("Draw Ellipse"), image(), currentNode(), canvas()->resourceManager(), strokeStyle(), - fillStyle()); + fillStyle(), + fillTransform()); helper.paintEllipse(rect); } else { QRectF r = convertToPt(rect); KoShape* shape = KisShapeToolHelper::createEllipseShape(r); KoShapeStrokeSP border(new KoShapeStroke(currentStrokeWidth(), currentFgColor().toQColor())); shape->setStroke(border); info.markAsSelectionShapeIfNeeded(shape); addShape(shape); } } diff --git a/plugins/tools/basictools/kis_tool_fill.cc b/plugins/tools/basictools/kis_tool_fill.cc index e9ce96c7f3..ac883bf975 100644 --- a/plugins/tools/basictools/kis_tool_fill.cc +++ b/plugins/tools/basictools/kis_tool_fill.cc @@ -1,445 +1,490 @@ /* * kis_tool_fill.cc - part of Krayon * * Copyright (c) 2000 John Califf * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2004 Bart Coppens * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_tool_fill.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 "kis_resources_snapshot.h" #include "commands_new/KisMergeLabeledLayersCommand.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "KoCompositeOpRegistry.h" KisToolFill::KisToolFill(KoCanvasBase * canvas) : KisToolPaint(canvas, KisCursor::load("tool_fill_cursor.png", 6, 6)) , m_colorLabelCompressor(900, KisSignalCompressor::FIRST_INACTIVE) { setObjectName("tool_fill"); m_feather = 0; m_sizemod = 0; m_threshold = 80; m_usePattern = false; m_fillOnlySelection = false; connect(&m_colorLabelCompressor, SIGNAL(timeout()), SLOT(slotUpdateAvailableColorLabels())); } KisToolFill::~KisToolFill() { } void KisToolFill::resetCursorStyle() { KisToolPaint::resetCursorStyle(); overrideCursorIfNotEditable(); } void KisToolFill::slotUpdateAvailableColorLabels() { if (m_widgetsInitialized && m_cmbSelectedLabels) { m_cmbSelectedLabels->updateAvailableLabels(currentImage()->root()); } } void KisToolFill::activate(ToolActivation toolActivation, const QSet &shapes) { KisToolPaint::activate(toolActivation, shapes); m_configGroup = KSharedConfig::openConfig()->group(toolId()); if (m_widgetsInitialized && m_imageConnections.isEmpty()) { activateConnectionsToImage(); } } void KisToolFill::deactivate() { KisToolPaint::deactivate(); m_imageConnections.clear(); } void KisToolFill::beginPrimaryAction(KoPointerEvent *event) { // cannot use fill tool on non-painting layers. // this logic triggers with multiple layer types like vector layer, clone layer, file layer, group layer if ( currentNode().isNull() || currentNode()->inherits("KisShapeLayer") || nodePaintAbility()!=NodePaintAbility::PAINT ) { KisCanvas2 * kiscanvas = static_cast(canvas()); kiscanvas->viewManager()-> showFloatingMessage( i18n("You cannot use this tool with the selected layer type"), QIcon(), 2000, KisFloatingMessage::Medium, Qt::AlignCenter); event->ignore(); return; } if (!nodeEditable()) { event->ignore(); return; } setMode(KisTool::PAINT_MODE); m_startPos = convertToImagePixelCoordFloored(event); keysAtStart = event->modifiers(); } void KisToolFill::endPrimaryAction(KoPointerEvent *event) { Q_UNUSED(event); CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE); setMode(KisTool::HOVER_MODE); if (!currentNode() || (!image()->wrapAroundModePermitted() && !image()->bounds().contains(m_startPos))) { return; } bool useFastMode = m_useFastMode->isChecked(); Qt::KeyboardModifiers fillOnlySelectionModifier = Qt::AltModifier; // Not sure where to keep it if (keysAtStart == fillOnlySelectionModifier) { m_fillOnlySelection = true; } keysAtStart = Qt::NoModifier; // libs/ui/tool/kis_tool_select_base.h cleans it up in endPrimaryAction so i do it too KisProcessingApplicator applicator(currentImage(), currentNode(), KisProcessingApplicator::SUPPORTS_WRAPAROUND_MODE, KisImageSignalVector() << ModifiedSignal, kundo2_i18n("Flood Fill")); KisResourcesSnapshotSP resources = new KisResourcesSnapshot(image(), currentNode(), this->canvas()->resourceManager()); KisPaintDeviceSP refPaintDevice = 0; KisImageWSP currentImageWSP = currentImage(); KisNodeSP currentRoot = currentImageWSP->root(); KisImageSP refImage = KisMergeLabeledLayersCommand::createRefImage(image(), "Fill Tool Reference Image"); if (m_sampleLayersMode == SAMPLE_LAYERS_MODE_ALL) { refPaintDevice = currentImage()->projection(); } else if (m_sampleLayersMode == SAMPLE_LAYERS_MODE_CURRENT) { refPaintDevice = currentNode()->paintDevice(); } else if (m_sampleLayersMode == SAMPLE_LAYERS_MODE_COLOR_LABELED) { refPaintDevice = KisMergeLabeledLayersCommand::createRefPaintDevice(image(), "Fill Tool Reference Result Paint Device"); applicator.applyCommand(new KisMergeLabeledLayersCommand(refImage, refPaintDevice, currentRoot, m_selectedColors), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); } KIS_ASSERT(refPaintDevice); + QTransform transform; + + transform.rotate(m_patternRotation); + transform.scale(m_patternScale, m_patternScale); + resources->setFillTransform(transform); + KisProcessingVisitorSP visitor = new FillProcessingVisitor(refPaintDevice, m_startPos, resources->activeSelection(), resources, useFastMode, m_usePattern, m_fillOnlySelection, m_feather, m_sizemod, m_threshold, false, /* use the current device (unmerged) */ false); applicator.applyVisitor(visitor, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); applicator.end(); m_fillOnlySelection = m_checkFillSelection->isChecked(); } QWidget* KisToolFill::createOptionWidget() { QWidget *widget = KisToolPaint::createOptionWidget(); widget->setObjectName(toolId() + " option widget"); QLabel *lbl_fastMode = new QLabel(i18n("Fast mode: "), widget); m_useFastMode = new QCheckBox(QString(), widget); m_useFastMode->setToolTip( i18n("Fills area faster, but does not take composition " "mode into account. Selections and other extended " "features will also be disabled.")); QLabel *lbl_threshold = new QLabel(i18n("Threshold: "), widget); m_slThreshold = new KisSliderSpinBox(widget); m_slThreshold->setObjectName("int_widget"); m_slThreshold->setRange(1, 100); m_slThreshold->setPageStep(3); QLabel *lbl_sizemod = new QLabel(i18n("Grow selection: "), widget); m_sizemodWidget = new KisSliderSpinBox(widget); m_sizemodWidget->setObjectName("sizemod"); m_sizemodWidget->setRange(-40, 40); m_sizemodWidget->setSingleStep(1); m_sizemodWidget->setSuffix(i18n(" px")); QLabel *lbl_feather = new QLabel(i18n("Feathering radius: "), widget); m_featherWidget = new KisSliderSpinBox(widget); m_featherWidget->setObjectName("feather"); m_featherWidget->setRange(0, 40); m_featherWidget->setSingleStep(1); m_featherWidget->setSuffix(i18n(" px")); QLabel *lbl_usePattern = new QLabel(i18n("Use pattern:"), widget); m_checkUsePattern = new QCheckBox(QString(), widget); m_checkUsePattern->setToolTip(i18n("When checked do not use the foreground color, but the pattern selected to fill with")); + QLabel *lbl_patternRotation = new QLabel(i18n("Rotate:"), widget); + m_sldPatternRotate = new KisDoubleSliderSpinBox(widget); + m_sldPatternRotate->setObjectName("patternrotate"); + m_sldPatternRotate->setRange(0, 360, 2); + m_sldPatternRotate->setSingleStep(1.0); + m_sldPatternRotate->setSuffix(QChar(Qt::Key_degree)); + + QLabel *lbl_patternScale = new QLabel(i18n("Scale:"), widget); + m_sldPatternScale = new KisDoubleSliderSpinBox(widget); + m_sldPatternScale->setObjectName("patternscale"); + m_sldPatternScale->setRange(0, 500, 2); + m_sldPatternScale->setSingleStep(1.0); + m_sldPatternScale->setSuffix(QChar(Qt::Key_Percent)); + QLabel *lbl_sampleLayers = new QLabel(i18nc("This is a label before a combobox with different choices regarding which layers " "to take into considerationg when calculating the area to fill. " "Options together with the label are: /Sample current layer/ /Sample all layers/ " "/Sample color labeled layers/. Sample is a verb here and means something akin to 'take into account'.", "Sample:"), widget); m_cmbSampleLayersMode = new QComboBox(widget); m_cmbSampleLayersMode->addItem(sampleLayerModeToUserString(SAMPLE_LAYERS_MODE_CURRENT), SAMPLE_LAYERS_MODE_CURRENT); m_cmbSampleLayersMode->addItem(sampleLayerModeToUserString(SAMPLE_LAYERS_MODE_ALL), SAMPLE_LAYERS_MODE_ALL); m_cmbSampleLayersMode->addItem(sampleLayerModeToUserString(SAMPLE_LAYERS_MODE_COLOR_LABELED), SAMPLE_LAYERS_MODE_COLOR_LABELED); m_cmbSampleLayersMode->setEditable(false); QLabel *lbl_cmbLabel = new QLabel(i18nc("This is a string in tool options for Fill Tool to describe a combobox about " "a choice of color labels that a layer can be marked with. Those color labels " "will be used for calculating the area to fill.", "Labels used:"), widget); m_cmbSelectedLabels = new KisColorFilterCombo(widget, false, false); m_cmbSelectedLabels->updateAvailableLabels(currentImage().isNull() ? KisNodeSP() : currentImage()->root()); QLabel *lbl_fillSelection = new QLabel(i18n("Fill entire selection:"), widget); m_checkFillSelection = new QCheckBox(QString(), widget); m_checkFillSelection->setToolTip(i18n("When checked do not look at the current layer colors, but just fill all of the selected area")); connect (m_useFastMode , SIGNAL(toggled(bool)) , this, SLOT(slotSetUseFastMode(bool))); connect (m_slThreshold , SIGNAL(valueChanged(int)), this, SLOT(slotSetThreshold(int))); connect (m_sizemodWidget , SIGNAL(valueChanged(int)), this, SLOT(slotSetSizemod(int))); connect (m_featherWidget , SIGNAL(valueChanged(int)), this, SLOT(slotSetFeather(int))); connect (m_checkUsePattern , SIGNAL(toggled(bool)) , this, SLOT(slotSetUsePattern(bool))); connect (m_checkFillSelection, SIGNAL(toggled(bool)) , this, SLOT(slotSetFillSelection(bool))); connect (m_cmbSampleLayersMode , SIGNAL(currentIndexChanged(int)), this, SLOT(slotSetSampleLayers(int))); connect (m_cmbSelectedLabels , SIGNAL(selectedColorsChanged()), this, SLOT(slotSetSelectedColorLabels())); + connect (m_sldPatternRotate , SIGNAL(valueChanged(qreal)), this, SLOT(slotSetPatternRotation(qreal))); + connect (m_sldPatternScale , SIGNAL(valueChanged(qreal)), this, SLOT(slotSetPatternScale(qreal))); addOptionWidgetOption(m_useFastMode, lbl_fastMode); addOptionWidgetOption(m_slThreshold, lbl_threshold); addOptionWidgetOption(m_sizemodWidget , lbl_sizemod); addOptionWidgetOption(m_featherWidget , lbl_feather); addOptionWidgetOption(m_checkFillSelection, lbl_fillSelection); addOptionWidgetOption(m_cmbSampleLayersMode, lbl_sampleLayers); addOptionWidgetOption(m_cmbSelectedLabels, lbl_cmbLabel); addOptionWidgetOption(m_checkUsePattern, lbl_usePattern); + addOptionWidgetOption(m_sldPatternRotate, lbl_patternRotation); + addOptionWidgetOption(m_sldPatternScale, lbl_patternScale); + updateGUI(); widget->setFixedHeight(widget->sizeHint().height()); // load configuration options m_useFastMode->setChecked(m_configGroup.readEntry("useFastMode", false)); m_slThreshold->setValue(m_configGroup.readEntry("thresholdAmount", 80)); m_sizemodWidget->setValue(m_configGroup.readEntry("growSelection", 0)); m_featherWidget->setValue(m_configGroup.readEntry("featherAmount", 0)); m_checkUsePattern->setChecked(m_configGroup.readEntry("usePattern", false)); if (m_configGroup.hasKey("sampleLayersMode")) { m_sampleLayersMode = m_configGroup.readEntry("sampleLayersMode", SAMPLE_LAYERS_MODE_CURRENT); setCmbSampleLayersMode(m_sampleLayersMode); } else { // if neither option is present in the configuration, it will fall back to CURRENT bool sampleMerged = m_configGroup.readEntry("sampleMerged", false); m_sampleLayersMode = sampleMerged ? SAMPLE_LAYERS_MODE_ALL : SAMPLE_LAYERS_MODE_CURRENT; setCmbSampleLayersMode(m_sampleLayersMode); } m_checkFillSelection->setChecked(m_configGroup.readEntry("fillSelection", false)); + m_sldPatternRotate->setValue(m_configGroup.readEntry("patternRotate", 0.0)); + m_sldPatternScale->setValue(m_configGroup.readEntry("patternScale", 100.0)); + activateConnectionsToImage(); m_widgetsInitialized = true; return widget; } void KisToolFill::updateGUI() { bool useAdvancedMode = !m_useFastMode->isChecked(); bool selectionOnly = m_checkFillSelection->isChecked(); m_useFastMode->setEnabled(!selectionOnly); m_slThreshold->setEnabled(!selectionOnly); m_sizemodWidget->setEnabled(!selectionOnly && useAdvancedMode); m_featherWidget->setEnabled(!selectionOnly && useAdvancedMode); m_checkUsePattern->setEnabled(useAdvancedMode); + m_sldPatternRotate->setEnabled((m_checkUsePattern->isChecked() && useAdvancedMode)); + m_sldPatternScale->setEnabled((m_checkUsePattern->isChecked() && useAdvancedMode)); m_cmbSampleLayersMode->setEnabled(!selectionOnly && useAdvancedMode); bool sampleLayersModeIsColorLabeledLayers = m_cmbSampleLayersMode->currentData().toString() == SAMPLE_LAYERS_MODE_COLOR_LABELED; m_cmbSelectedLabels->setEnabled(!selectionOnly && useAdvancedMode && sampleLayersModeIsColorLabeledLayers); } QString KisToolFill::sampleLayerModeToUserString(QString sampleLayersModeId) { QString currentLayer = i18nc("Option in fill tool: take only the current layer into account when calculating the area to fill", "Current Layer"); if (sampleLayersModeId == SAMPLE_LAYERS_MODE_CURRENT) { return currentLayer; } else if (sampleLayersModeId == SAMPLE_LAYERS_MODE_ALL) { return i18nc("Option in fill tool: take all layers (merged) into account when calculating the area to fill", "All Layers"); } else if (sampleLayersModeId == SAMPLE_LAYERS_MODE_COLOR_LABELED) { return i18nc("Option in fill tool: take all layers that were labeled with a color label (more precisely: all those layers merged)" " into account when calculating the area to fill", "Color Labeled Layers"); } KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(false, currentLayer); return currentLayer; } void KisToolFill::setCmbSampleLayersMode(QString sampleLayersModeId) { for (int i = 0; i < m_cmbSampleLayersMode->count(); i++) { if (m_cmbSampleLayersMode->itemData(i).toString() == sampleLayersModeId) { m_cmbSampleLayersMode->setCurrentIndex(i); break; } } m_sampleLayersMode = sampleLayersModeId; updateGUI(); } void KisToolFill::activateConnectionsToImage() { auto *kisCanvas = dynamic_cast(canvas()); KIS_SAFE_ASSERT_RECOVER_RETURN(kisCanvas); KisDocument *doc = kisCanvas->imageView()->document(); KisShapeController *kritaShapeController = dynamic_cast(doc->shapeController()); m_dummiesFacade = static_cast(kritaShapeController); if (m_dummiesFacade) { m_imageConnections.addConnection(m_dummiesFacade, SIGNAL(sigEndInsertDummy(KisNodeDummy*)), &m_colorLabelCompressor, SLOT(start())); m_imageConnections.addConnection(m_dummiesFacade, SIGNAL(sigEndRemoveDummy()), &m_colorLabelCompressor, SLOT(start())); m_imageConnections.addConnection(m_dummiesFacade, SIGNAL(sigDummyChanged(KisNodeDummy*)), &m_colorLabelCompressor, SLOT(start())); } } void KisToolFill::deactivateConnectionsToImage() { m_imageConnections.clear(); } void KisToolFill::slotSetUseFastMode(bool value) { updateGUI(); m_configGroup.writeEntry("useFastMode", value); } void KisToolFill::slotSetThreshold(int threshold) { m_threshold = threshold; m_configGroup.writeEntry("thresholdAmount", threshold); } void KisToolFill::slotSetUsePattern(bool state) { m_usePattern = state; + m_sldPatternScale->setEnabled(state); + m_sldPatternRotate->setEnabled(state); m_configGroup.writeEntry("usePattern", state); } void KisToolFill::slotSetSampleLayers(int index) { Q_UNUSED(index); m_sampleLayersMode = m_cmbSampleLayersMode->currentData(Qt::UserRole).toString(); updateGUI(); m_configGroup.writeEntry("sampleLayersMode", m_sampleLayersMode); } void KisToolFill::slotSetSelectedColorLabels() { m_selectedColors = m_cmbSelectedLabels->selectedColors(); } +void KisToolFill::slotSetPatternScale(qreal scale) +{ + m_patternScale = scale*0.01; + m_configGroup.writeEntry("patternScale", scale); +} + +void KisToolFill::slotSetPatternRotation(qreal rotate) +{ + m_patternRotation = rotate; + m_configGroup.writeEntry("patternRotate", rotate); +} + void KisToolFill::slotSetFillSelection(bool state) { m_fillOnlySelection = state; m_configGroup.writeEntry("fillSelection", state); updateGUI(); } void KisToolFill::slotSetSizemod(int sizemod) { m_sizemod = sizemod; m_configGroup.writeEntry("growSelection", sizemod); } void KisToolFill::slotSetFeather(int feather) { m_feather = feather; m_configGroup.writeEntry("featherAmount", feather); } diff --git a/plugins/tools/basictools/kis_tool_fill.h b/plugins/tools/basictools/kis_tool_fill.h index 64bd54cdd2..3370614637 100644 --- a/plugins/tools/basictools/kis_tool_fill.h +++ b/plugins/tools/basictools/kis_tool_fill.h @@ -1,147 +1,154 @@ /* * kis_tool_fill.h - part of Krayon^Krita * * Copyright (c) 2004 Bart Coppens * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_TOOL_FILL_H_ #define KIS_TOOL_FILL_H_ #include "kis_tool_paint.h" #include #include #include #include #include #include #include #include #include class QWidget; class QCheckBox; class KisSliderSpinBox; +class KisDoubleSliderSpinBox; class KoCanvasBase; class KisColorFilterCombo; class KisDummiesFacadeBase; class KisToolFill : public KisToolPaint { Q_OBJECT public: KisToolFill(KoCanvasBase * canvas); ~KisToolFill() override; void beginPrimaryAction(KoPointerEvent *event) override; void endPrimaryAction(KoPointerEvent *event) override; QWidget * createOptionWidget() override; public Q_SLOTS: void activate(ToolActivation toolActivation, const QSet &shapes) override; void deactivate() override; void slotSetUseFastMode(bool); void slotSetThreshold(int); void slotSetUsePattern(bool); void slotSetFillSelection(bool); void slotSetSizemod(int); void slotSetFeather(int); void slotSetSampleLayers(int index); void slotSetSelectedColorLabels(); + void slotSetPatternScale(qreal scale); + void slotSetPatternRotation(qreal rotate); protected Q_SLOTS: void resetCursorStyle() override; void slotUpdateAvailableColorLabels(); protected: bool wantsAutoScroll() const override { return false; } private: void updateGUI(); QString sampleLayerModeToUserString(QString sampleLayersModeId); void setCmbSampleLayersMode(QString sampleLayersModeId); void activateConnectionsToImage(); void deactivateConnectionsToImage(); private: QString SAMPLE_LAYERS_MODE_CURRENT = {"currentLayer"}; QString SAMPLE_LAYERS_MODE_ALL = {"allLayers"}; QString SAMPLE_LAYERS_MODE_COLOR_LABELED = {"colorLabeledLayers"}; private: Qt::KeyboardModifiers keysAtStart; int m_feather; int m_sizemod; QPoint m_startPos; int m_threshold; bool m_usePattern; bool m_fillOnlySelection; QString m_sampleLayersMode; QList m_selectedColors; + qreal m_patternRotation; + qreal m_patternScale; bool m_widgetsInitialized {false}; QCheckBox *m_useFastMode; KisSliderSpinBox *m_slThreshold; KisSliderSpinBox *m_sizemodWidget; KisSliderSpinBox *m_featherWidget; + KisDoubleSliderSpinBox *m_sldPatternRotate; + KisDoubleSliderSpinBox *m_sldPatternScale; QCheckBox *m_checkUsePattern; QCheckBox *m_checkFillSelection; QComboBox *m_cmbSampleLayersMode; KisColorFilterCombo *m_cmbSelectedLabels; KisSignalCompressor m_colorLabelCompressor; KisDummiesFacadeBase* m_dummiesFacade; KisSignalAutoConnectionsStore m_imageConnections; KConfigGroup m_configGroup; }; #include "KisToolPaintFactoryBase.h" class KisToolFillFactory : public KisToolPaintFactoryBase { public: KisToolFillFactory() : KisToolPaintFactoryBase("KritaFill/KisToolFill") { setToolTip(i18n("Fill Tool")); setSection(TOOL_TYPE_FILL); setPriority(0); setActivationShapeId(KRITA_TOOL_ACTIVATION_ID); setIconName(koIconNameCStr("krita_tool_color_fill")); setShortcut( QKeySequence( Qt::Key_F ) ); setPriority(14); } ~KisToolFillFactory() override {} KoToolBase * createTool(KoCanvasBase *canvas) override { return new KisToolFill(canvas); } }; #endif //__filltool_h__ diff --git a/plugins/tools/basictools/kis_tool_move.cc b/plugins/tools/basictools/kis_tool_move.cc index b1da864a00..9bb8cb2c2b 100644 --- a/plugins/tools/basictools/kis_tool_move.cc +++ b/plugins/tools/basictools/kis_tool_move.cc @@ -1,780 +1,808 @@ /* * Copyright (c) 1999 Matthias Elter * 1999 Michael Koch * 2002 Patrick Julien * 2004 Boudewijn Rempt * 2016 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. */ #include "kis_tool_move.h" #include #include "kis_cursor.h" #include "kis_selection.h" #include "kis_canvas2.h" #include "kis_image.h" #include "kis_tool_utils.h" #include "kis_paint_layer.h" #include "strokes/move_stroke_strategy.h" #include "kis_tool_movetooloptionswidget.h" #include "strokes/move_selection_stroke_strategy.h" #include "kis_resources_snapshot.h" #include "kis_action_registry.h" #include "krita_utils.h" #include #include #include "kis_node_manager.h" #include "kis_selection_manager.h" #include "kis_signals_blocker.h" #include #include "KisMoveBoundsCalculationJob.h" struct KisToolMoveState : KisToolChangesTrackerData, boost::equality_comparable { KisToolMoveState(QPoint _accumulatedOffset) : accumulatedOffset(_accumulatedOffset) {} KisToolChangesTrackerData* clone() const override { return new KisToolMoveState(*this); } bool operator ==(const KisToolMoveState &rhs) { return accumulatedOffset == rhs.accumulatedOffset; } QPoint accumulatedOffset; }; KisToolMove::KisToolMove(KoCanvasBase *canvas) : KisTool(canvas, KisCursor::moveCursor()) , m_updateCursorCompressor(100, KisSignalCompressor::FIRST_ACTIVE) { setObjectName("tool_move"); m_showCoordinatesAction = action("movetool-show-coordinates"); m_showCoordinatesAction = action("movetool-show-coordinates"); connect(&m_updateCursorCompressor, SIGNAL(timeout()), this, SLOT(resetCursorStyle())); m_optionsWidget = new MoveToolOptionsWidget(0, currentImage()->xRes(), toolId()); // See https://bugs.kde.org/show_bug.cgi?id=316896 QWidget *specialSpacer = new QWidget(m_optionsWidget); specialSpacer->setObjectName("SpecialSpacer"); specialSpacer->setFixedSize(0, 0); m_optionsWidget->layout()->addWidget(specialSpacer); m_optionsWidget->setFixedHeight(m_optionsWidget->sizeHint().height()); m_showCoordinatesAction->setChecked(m_optionsWidget->showCoordinates()); m_optionsWidget->slotSetTranslate(m_handlesRect.topLeft() + currentOffset()); connect(m_optionsWidget, SIGNAL(sigSetTranslateX(int)), SLOT(moveBySpinX(int)), Qt::UniqueConnection); connect(m_optionsWidget, SIGNAL(sigSetTranslateY(int)), SLOT(moveBySpinY(int)), Qt::UniqueConnection); connect(m_optionsWidget, SIGNAL(sigRequestCommitOffsetChanges()), this, SLOT(commitChanges()), Qt::UniqueConnection); connect(this, SIGNAL(moveInNewPosition(QPoint)), m_optionsWidget, SLOT(slotSetTranslate(QPoint)), Qt::UniqueConnection); } KisToolMove::~KisToolMove() { endStroke(); } void KisToolMove::resetCursorStyle() { if (!isActive()) return; bool canMove = true; if (m_strokeId && m_currentlyUsingSelection) { /// noop; whatever the cursor position, we always show move /// cursor, because we don't use 'layer under cursor' mode /// for moving selections } else if (m_strokeId && !m_currentlyUsingSelection) { /// we cannot pick layer's pixel data while the stroke is running, /// because it may run in lodN mode; therefore, we delegate this /// work to the stroke itself if (m_currentMode != MoveSelectedLayer && (m_handlesRect.isEmpty() || !m_handlesRect.translated(currentOffset()).contains(m_lastCursorPos))) { image()->addJob(m_strokeId, new MoveStrokeStrategy::PickLayerData(m_lastCursorPos)); return; } } else { KisResourcesSnapshotSP resources = new KisResourcesSnapshot(this->image(), currentNode(), canvas()->resourceManager()); KisSelectionSP selection = resources->activeSelection(); KisPaintLayerSP paintLayer = dynamic_cast(this->currentNode().data()); const bool canUseSelectionMode = paintLayer && selection && !selection->selectedRect().isEmpty() && !selection->selectedExactRect().isEmpty(); if (!canUseSelectionMode) { KisNodeSelectionRecipe nodeSelection = KisNodeSelectionRecipe( this->selectedNodes(), (KisNodeSelectionRecipe::SelectionMode)moveToolMode(), m_lastCursorPos); if (nodeSelection.selectNodesToProcess().isEmpty()) { canMove = false; } } } if (canMove) { KisTool::resetCursorStyle(); } else { useCursor(Qt::ForbiddenCursor); } } bool KisToolMove::startStrokeImpl(MoveToolMode mode, const QPoint *pos) { KisNodeSP node; KisImageSP image = this->image(); KisResourcesSnapshotSP resources = new KisResourcesSnapshot(image, currentNode(), canvas()->resourceManager()); KisSelectionSP selection = resources->activeSelection(); KisPaintLayerSP paintLayer = dynamic_cast(this->currentNode().data()); const bool canUseSelectionMode = paintLayer && selection && !selection->selectedRect().isEmpty() && !selection->selectedExactRect().isEmpty(); if (pos) { // finish stroke by clicking outside image bounds if (m_strokeId && !image->bounds().contains(*pos)) { endStroke(); return false; } // restart stroke when the mode has changed or the user tried to // pick another layer in "layer under cursor" mode. if (m_strokeId && (m_currentMode != mode || m_currentlyUsingSelection != canUseSelectionMode || (!m_currentlyUsingSelection && mode != MoveSelectedLayer && !m_handlesRect.translated(currentOffset()).contains(*pos)))) { endStroke(); } } if (m_strokeId) return true; KisNodeList nodes; KisStrokeStrategy *strategy; bool isMoveSelection = false; if (canUseSelectionMode) { KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(selection, false); MoveSelectionStrokeStrategy *moveStrategy = new MoveSelectionStrokeStrategy(paintLayer, selection, image.data(), image.data()); connect(moveStrategy, SIGNAL(sigHandlesRectCalculated(const QRect&)), SLOT(slotHandlesRectCalculated(const QRect&))); + connect(moveStrategy, + SIGNAL(sigStrokeStartedEmpty()), + SLOT(slotStrokeStartedEmpty())); strategy = moveStrategy; isMoveSelection = true; nodes = {paintLayer}; } else { KisNodeSelectionRecipe nodeSelection = pos ? KisNodeSelectionRecipe( this->selectedNodes(), (KisNodeSelectionRecipe::SelectionMode)mode, *pos) : KisNodeSelectionRecipe(this->selectedNodes()); MoveStrokeStrategy *moveStrategy = new MoveStrokeStrategy(nodeSelection, image.data(), image.data()); connect(moveStrategy, SIGNAL(sigHandlesRectCalculated(const QRect&)), SLOT(slotHandlesRectCalculated(const QRect&))); connect(moveStrategy, SIGNAL(sigStrokeStartedEmpty()), SLOT(slotStrokeStartedEmpty())); connect(moveStrategy, SIGNAL(sigLayersPicked(const KisNodeList&)), SLOT(slotStrokePickedLayers(const KisNodeList&))); strategy = moveStrategy; nodes = nodeSelection.selectedNodes; } // disable outline feedback until the stroke calcualtes // correct bounding rect m_handlesRect = QRect(); m_strokeId = image->startStroke(strategy); m_currentlyProcessingNodes = nodes; m_currentlyUsingSelection = isMoveSelection; m_currentMode = mode; m_accumulatedOffset = QPoint(); if (!isMoveSelection) { m_asyncUpdateHelper.startUpdateStream(image.data(), m_strokeId); } KIS_SAFE_ASSERT_RECOVER(m_changesTracker.isEmpty()) { m_changesTracker.reset(); } commitChanges(); return true; } QPoint KisToolMove::currentOffset() const { return m_accumulatedOffset + m_dragPos - m_dragStart; } void KisToolMove::notifyGuiAfterMove(bool showFloatingMessage) { if (!m_optionsWidget) return; if (m_handlesRect.isEmpty()) return; const QPoint currentTopLeft = m_handlesRect.topLeft() + currentOffset(); KisSignalsBlocker b(m_optionsWidget); emit moveInNewPosition(currentTopLeft); // TODO: fetch this info not from options widget, but from config const bool showCoordinates = m_optionsWidget->showCoordinates(); if (showCoordinates && showFloatingMessage) { KisCanvas2 *kisCanvas = static_cast(canvas()); kisCanvas->viewManager()-> showFloatingMessage( i18nc("floating message in move tool", "X: %1 px, Y: %2 px", QLocale().toString(currentTopLeft.x()), QLocale().toString(currentTopLeft.y())), QIcon(), 1000, KisFloatingMessage::High); } } bool KisToolMove::tryEndPreviousStroke(const KisNodeList &nodes) { if (!m_strokeId) return false; bool strokeEnded = false; if (!KritaUtils::compareListsUnordered(nodes, m_currentlyProcessingNodes)) { endStroke(); strokeEnded = true; } return strokeEnded; } void KisToolMove::commitChanges() { KIS_SAFE_ASSERT_RECOVER_RETURN(m_strokeId); QSharedPointer newState(new KisToolMoveState(m_accumulatedOffset)); KisToolMoveState *lastState = dynamic_cast(m_changesTracker.lastState().data()); if (lastState && *lastState == *newState) return; m_changesTracker.commitConfig(newState); } void KisToolMove::slotHandlesRectCalculated(const QRect &handlesRect) { m_handlesRect = handlesRect; notifyGuiAfterMove(false); } void KisToolMove::slotStrokeStartedEmpty() { + /** + * Notify that move-selection stroke ended unexpectedly + */ + if (m_currentlyUsingSelection) { + KisCanvas2 *kisCanvas = static_cast(canvas()); + kisCanvas->viewManager()-> + showFloatingMessage( + i18nc("floating message in move tool", + "Selected area has no pixels"), + QIcon(), 1000, KisFloatingMessage::High); + } + /** * Since the choice of nodes for the operation happens in the * stroke itself, it may happen that there are no nodes at all. * In such a case, we should just cancel already started stroke. */ cancelStroke(); } void KisToolMove::slotStrokePickedLayers(const KisNodeList &nodes) { if (nodes.isEmpty()) { useCursor(Qt::ForbiddenCursor); } else { KisTool::resetCursorStyle(); } } void KisToolMove::moveDiscrete(MoveDirection direction, bool big) { if (mode() == KisTool::PAINT_MODE) return; // Don't interact with dragging if (!currentNode()) return; if (!image()) return; if (!currentNode()->isEditable()) return; // Don't move invisible nodes if (startStrokeImpl(MoveSelectedLayer, 0)) { setMode(KisTool::PAINT_MODE); } // Larger movement if "shift" key is pressed. qreal scale = big ? m_optionsWidget->moveScale() : 1.0; qreal moveStep = m_optionsWidget->moveStep() * scale; const QPoint offset = direction == Up ? QPoint( 0, -moveStep) : direction == Down ? QPoint( 0, moveStep) : direction == Left ? QPoint(-moveStep, 0) : QPoint( moveStep, 0) ; m_accumulatedOffset += offset; image()->addJob(m_strokeId, new MoveStrokeStrategy::Data(m_accumulatedOffset)); notifyGuiAfterMove(); commitChanges(); setMode(KisTool::HOVER_MODE); } void KisToolMove::activate(ToolActivation toolActivation, const QSet &shapes) { KisTool::activate(toolActivation, shapes); m_actionConnections.addConnection(action("movetool-move-up"), SIGNAL(triggered(bool)), this, SLOT(slotMoveDiscreteUp())); m_actionConnections.addConnection(action("movetool-move-down"), SIGNAL(triggered(bool)), this, SLOT(slotMoveDiscreteDown())); m_actionConnections.addConnection(action("movetool-move-left"), SIGNAL(triggered(bool)), this, SLOT(slotMoveDiscreteLeft())); m_actionConnections.addConnection(action("movetool-move-right"), SIGNAL(triggered(bool)), this, SLOT(slotMoveDiscreteRight())); m_actionConnections.addConnection(action("movetool-move-up-more"), SIGNAL(triggered(bool)), this, SLOT(slotMoveDiscreteUpMore())); m_actionConnections.addConnection(action("movetool-move-down-more"), SIGNAL(triggered(bool)), this, SLOT(slotMoveDiscreteDownMore())); m_actionConnections.addConnection(action("movetool-move-left-more"), SIGNAL(triggered(bool)), this, SLOT(slotMoveDiscreteLeftMore())); m_actionConnections.addConnection(action("movetool-move-right-more"), SIGNAL(triggered(bool)), this, SLOT(slotMoveDiscreteRightMore())); m_canvasConnections.addUniqueConnection(qobject_cast(canvas())->viewManager()->nodeManager(), SIGNAL(sigUiNeedChangeSelectedNodes(KisNodeList)), this, SLOT(slotNodeChanged(KisNodeList))); m_canvasConnections.addUniqueConnection(qobject_cast(canvas())->viewManager()->selectionManager(), SIGNAL(currentSelectionChanged()), this, SLOT(slotSelectionChanged())); connect(m_showCoordinatesAction, SIGNAL(triggered(bool)), m_optionsWidget, SLOT(setShowCoordinates(bool)), Qt::UniqueConnection); connect(m_optionsWidget, SIGNAL(showCoordinatesChanged(bool)), m_showCoordinatesAction, SLOT(setChecked(bool)), Qt::UniqueConnection); connect(&m_changesTracker, SIGNAL(sigConfigChanged(KisToolChangesTrackerDataSP)), SLOT(slotTrackerChangedConfig(KisToolChangesTrackerDataSP))); slotNodeChanged(this->selectedNodes()); } void KisToolMove::paint(QPainter& gc, const KoViewConverter &converter) { Q_UNUSED(converter); - if (m_strokeId && !m_handlesRect.isEmpty()) { + if (m_strokeId && !m_handlesRect.isEmpty() && !m_currentlyUsingSelection) { QPainterPath handles; handles.addRect(m_handlesRect.translated(currentOffset())); QPainterPath path = pixelToView(handles); paintToolOutline(&gc, path); } } void KisToolMove::deactivate() { m_actionConnections.clear(); m_canvasConnections.clear(); disconnect(m_showCoordinatesAction, 0, this, 0); disconnect(m_optionsWidget, 0, this, 0); endStroke(); KisTool::deactivate(); } void KisToolMove::requestStrokeEnd() { endStroke(); } void KisToolMove::requestStrokeCancellation() { cancelStroke(); } void KisToolMove::requestUndoDuringStroke() { if (!m_strokeId) return; if (m_changesTracker.isEmpty()) { cancelStroke(); } else { m_changesTracker.requestUndo(); } } void KisToolMove::beginPrimaryAction(KoPointerEvent *event) { startAction(event, moveToolMode()); } void KisToolMove::continuePrimaryAction(KoPointerEvent *event) { continueAction(event); } void KisToolMove::endPrimaryAction(KoPointerEvent *event) { endAction(event); } void KisToolMove::beginAlternateAction(KoPointerEvent *event, AlternateAction action) { // Ctrl+Right click toggles between moving current layer and moving layer w/ content if (action == PickFgNode || action == PickBgImage) { MoveToolMode mode = moveToolMode(); if (mode == MoveSelectedLayer) { mode = MoveFirstLayer; } else if (mode == MoveFirstLayer) { mode = MoveSelectedLayer; } startAction(event, mode); } else { startAction(event, MoveGroup); } } void KisToolMove::continueAlternateAction(KoPointerEvent *event, AlternateAction action) { Q_UNUSED(action) continueAction(event); } void KisToolMove::endAlternateAction(KoPointerEvent *event, AlternateAction action) { Q_UNUSED(action) endAction(event); } void KisToolMove::mouseMoveEvent(KoPointerEvent *event) { m_lastCursorPos = convertToPixelCoord(event).toPoint(); KisTool::mouseMoveEvent(event); if (moveToolMode() != MoveSelectedLayer || (m_strokeId && m_currentMode != MoveSelectedLayer)) { m_updateCursorCompressor.start(); } } void KisToolMove::startAction(KoPointerEvent *event, MoveToolMode mode) { QPoint pos = convertToPixelCoordAndSnap(event).toPoint(); m_dragStart = pos; m_dragPos = pos; if (startStrokeImpl(mode, &pos)) { setMode(KisTool::PAINT_MODE); + + if (m_currentlyUsingSelection) { + KisImageSP image = currentImage(); + image->addJob(m_strokeId, + new MoveSelectionStrokeStrategy::ShowSelectionData(false)); + } + } else { event->ignore(); m_dragPos = QPoint(); m_dragStart = QPoint(); } qobject_cast(canvas())->updateCanvas(); } void KisToolMove::continueAction(KoPointerEvent *event) { CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE); if (!m_strokeId) return; QPoint pos = convertToPixelCoordAndSnap(event).toPoint(); pos = applyModifiers(event->modifiers(), pos); m_dragPos = pos; drag(pos); notifyGuiAfterMove(); qobject_cast(canvas())->updateCanvas(); } void KisToolMove::endAction(KoPointerEvent *event) { CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE); setMode(KisTool::HOVER_MODE); if (!m_strokeId) return; QPoint pos = convertToPixelCoordAndSnap(event).toPoint(); pos = applyModifiers(event->modifiers(), pos); drag(pos); m_accumulatedOffset += pos - m_dragStart; m_dragStart = QPoint(); m_dragPos = QPoint(); commitChanges(); + if (m_currentlyUsingSelection) { + KisImageSP image = currentImage(); + image->addJob(m_strokeId, + new MoveSelectionStrokeStrategy::ShowSelectionData(true)); + } + notifyGuiAfterMove(); qobject_cast(canvas())->updateCanvas(); } void KisToolMove::drag(const QPoint& newPos) { - KisImageWSP image = currentImage(); + KisImageSP image = currentImage(); QPoint offset = m_accumulatedOffset + newPos - m_dragStart; image->addJob(m_strokeId, new MoveStrokeStrategy::Data(offset)); } void KisToolMove::endStroke() { if (!m_strokeId) return; if (m_asyncUpdateHelper.isActive()) { m_asyncUpdateHelper.endUpdateStream(); } KisImageSP image = currentImage(); image->endStroke(m_strokeId); m_strokeId.clear(); m_changesTracker.reset(); m_currentlyProcessingNodes.clear(); m_currentlyUsingSelection = false; m_currentMode = MoveSelectedLayer; m_accumulatedOffset = QPoint(); qobject_cast(canvas())->updateCanvas(); } void KisToolMove::slotTrackerChangedConfig(KisToolChangesTrackerDataSP state) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_strokeId); KisToolMoveState *newState = dynamic_cast(state.data()); KIS_SAFE_ASSERT_RECOVER_RETURN(newState); if (mode() == KisTool::PAINT_MODE) return; // Don't interact with dragging m_accumulatedOffset = newState->accumulatedOffset; image()->addJob(m_strokeId, new MoveStrokeStrategy::Data(m_accumulatedOffset)); notifyGuiAfterMove(); } void KisToolMove::slotMoveDiscreteLeft() { moveDiscrete(MoveDirection::Left, false); } void KisToolMove::slotMoveDiscreteRight() { moveDiscrete(MoveDirection::Right, false); } void KisToolMove::slotMoveDiscreteUp() { moveDiscrete(MoveDirection::Up, false); } void KisToolMove::slotMoveDiscreteDown() { moveDiscrete(MoveDirection::Down, false); } void KisToolMove::slotMoveDiscreteLeftMore() { moveDiscrete(MoveDirection::Left, true); } void KisToolMove::slotMoveDiscreteRightMore() { moveDiscrete(MoveDirection::Right, true); } void KisToolMove::slotMoveDiscreteUpMore() { moveDiscrete(MoveDirection::Up, true); } void KisToolMove::slotMoveDiscreteDownMore() { moveDiscrete(MoveDirection::Down, true); } void KisToolMove::cancelStroke() { if (!m_strokeId) return; if (m_asyncUpdateHelper.isActive()) { m_asyncUpdateHelper.cancelUpdateStream(); } KisImageSP image = currentImage(); image->cancelStroke(m_strokeId); m_strokeId.clear(); m_changesTracker.reset(); m_currentlyProcessingNodes.clear(); m_currentlyUsingSelection = false; m_currentMode = MoveSelectedLayer; m_accumulatedOffset = QPoint(); notifyGuiAfterMove(); qobject_cast(canvas())->updateCanvas(); } QWidget* KisToolMove::createOptionWidget() { return m_optionsWidget; } KisToolMove::MoveToolMode KisToolMove::moveToolMode() const { if (m_optionsWidget) return m_optionsWidget->mode(); return MoveSelectedLayer; } QPoint KisToolMove::applyModifiers(Qt::KeyboardModifiers modifiers, QPoint pos) { QPoint move = pos - m_dragStart; // Snap to axis if (modifiers & Qt::ShiftModifier) { move = snapToClosestAxis(move); } // "Precision mode" - scale down movement by 1/5 if (modifiers & Qt::AltModifier) { const qreal SCALE_FACTOR = .2; move = SCALE_FACTOR * move; } return m_dragStart + move; } void KisToolMove::moveBySpinX(int newX) { if (mode() == KisTool::PAINT_MODE) return; // Don't interact with dragging if (!currentNode()->isEditable()) return; // Don't move invisible nodes if (startStrokeImpl(MoveSelectedLayer, 0)) { setMode(KisTool::PAINT_MODE); } m_accumulatedOffset.rx() = newX - m_handlesRect.x(); image()->addJob(m_strokeId, new MoveStrokeStrategy::Data(m_accumulatedOffset)); notifyGuiAfterMove(false); setMode(KisTool::HOVER_MODE); } void KisToolMove::moveBySpinY(int newY) { if (mode() == KisTool::PAINT_MODE) return; // Don't interact with dragging if (!currentNode()->isEditable()) return; // Don't move invisible nodes if (startStrokeImpl(MoveSelectedLayer, 0)) { setMode(KisTool::PAINT_MODE); } m_accumulatedOffset.ry() = newY - m_handlesRect.y(); image()->addJob(m_strokeId, new MoveStrokeStrategy::Data(m_accumulatedOffset)); notifyGuiAfterMove(false); setMode(KisTool::HOVER_MODE); } void KisToolMove::requestHandlesRectUpdate() { KisResourcesSnapshotSP resources = new KisResourcesSnapshot(image(), currentNode(), canvas()->resourceManager()); KisSelectionSP selection = resources->activeSelection(); KisMoveBoundsCalculationJob *job = new KisMoveBoundsCalculationJob(this->selectedNodes(), selection, this); connect(job, SIGNAL(sigCalcualtionFinished(const QRect&)), SLOT(slotHandlesRectCalculated(const QRect &))); KisImageSP image = this->image(); image->addSpontaneousJob(job); notifyGuiAfterMove(false); } void KisToolMove::slotNodeChanged(const KisNodeList &nodes) { if (m_strokeId && !tryEndPreviousStroke(nodes)) { return; } requestHandlesRectUpdate(); } void KisToolMove::slotSelectionChanged() { if (m_strokeId) return; requestHandlesRectUpdate(); } QList KisToolMoveFactory::createActionsImpl() { KisActionRegistry *actionRegistry = KisActionRegistry::instance(); QList actions = KisToolPaintFactoryBase::createActionsImpl(); actions << actionRegistry->makeQAction("movetool-move-up"); actions << actionRegistry->makeQAction("movetool-move-down"); actions << actionRegistry->makeQAction("movetool-move-left"); actions << actionRegistry->makeQAction("movetool-move-right"); actions << actionRegistry->makeQAction("movetool-move-up-more"); actions << actionRegistry->makeQAction("movetool-move-down-more"); actions << actionRegistry->makeQAction("movetool-move-left-more"); actions << actionRegistry->makeQAction("movetool-move-right-more"); actions << actionRegistry->makeQAction("movetool-show-coordinates"); return actions; } diff --git a/plugins/tools/basictools/kis_tool_rectangle.cc b/plugins/tools/basictools/kis_tool_rectangle.cc index cfc11aa60f..1c98cb9040 100644 --- a/plugins/tools/basictools/kis_tool_rectangle.cc +++ b/plugins/tools/basictools/kis_tool_rectangle.cc @@ -1,102 +1,103 @@ /* * kis_tool_rectangle.cc - part of Krita * * Copyright (c) 2000 John Califf * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2004 Clarence Dang * Copyright (c) 2009 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_tool_rectangle.h" #include #include #include "KoCanvasBase.h" #include "kis_shape_tool_helper.h" #include "kis_figure_painting_tool_helper.h" #include #include KisToolRectangle::KisToolRectangle(KoCanvasBase * canvas) : KisToolRectangleBase(canvas, KisToolRectangleBase::PAINT, KisCursor::load("tool_rectangle_cursor.png", 6, 6)) { setSupportOutline(true); setObjectName("tool_rectangle"); } KisToolRectangle::~KisToolRectangle() { } void KisToolRectangle::resetCursorStyle() { KisToolRectangleBase::resetCursorStyle(); overrideCursorIfNotEditable(); } void KisToolRectangle::finishRect(const QRectF &rect, qreal roundCornersX, qreal roundCornersY) { if (rect.isNull()) return; const KisToolShape::ShapeAddInfo info = shouldAddShape(currentNode()); if (!info.shouldAddShape) { KisFigurePaintingToolHelper helper(kundo2_i18n("Draw Rectangle"), image(), currentNode(), canvas()->resourceManager(), strokeStyle(), - fillStyle()); + fillStyle(), + fillTransform()); QPainterPath path; if (roundCornersX > 0 || roundCornersY > 0) { path.addRoundedRect(rect, roundCornersX, roundCornersY); } else { path.addRect(rect); } helper.paintPainterPath(path); } else { const QRectF r = convertToPt(rect); const qreal docRoundCornersX = convertToPt(roundCornersX); const qreal docRoundCornersY = convertToPt(roundCornersY); KoShape* shape = KisShapeToolHelper::createRectangleShape(r, docRoundCornersX, docRoundCornersY); KoShapeStrokeSP border; if (strokeStyle() != KisToolShapeUtils::StrokeStyleNone) { const QColor color = strokeStyle() == KisToolShapeUtils::StrokeStyleForeground ? canvas()->resourceManager()->foregroundColor().toQColor() : canvas()->resourceManager()->backgroundColor().toQColor(); border = toQShared(new KoShapeStroke(currentStrokeWidth(), color)); } shape->setStroke(border); info.markAsSelectionShapeIfNeeded(shape); addShape(shape); } } diff --git a/plugins/tools/basictools/strokes/move_selection_stroke_strategy.cpp b/plugins/tools/basictools/strokes/move_selection_stroke_strategy.cpp index a49da8a3da..629c687759 100644 --- a/plugins/tools/basictools/strokes/move_selection_stroke_strategy.cpp +++ b/plugins/tools/basictools/strokes/move_selection_stroke_strategy.cpp @@ -1,191 +1,211 @@ /* * Copyright (c) 2012 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "move_selection_stroke_strategy.h" #include #include #include #include "kis_image.h" #include "kis_paint_layer.h" #include "kis_painter.h" #include "kis_transaction.h" #include #include "kis_lod_transform.h" MoveSelectionStrokeStrategy::MoveSelectionStrokeStrategy(KisPaintLayerSP paintLayer, KisSelectionSP selection, KisUpdatesFacade *updatesFacade, KisStrokeUndoFacade *undoFacade) : KisStrokeStrategyUndoCommandBased(kundo2_i18n("Move Selection"), false, undoFacade), m_paintLayer(paintLayer), m_selection(selection), m_updatesFacade(updatesFacade) { /** * Selection might have some update projection jobs pending, so we should ensure * all of them are completed before we start our stroke. */ enableJob(KisSimpleStrokeStrategy::JOB_INIT, true, KisStrokeJobData::BARRIER); enableJob(KisSimpleStrokeStrategy::JOB_FINISH); enableJob(KisSimpleStrokeStrategy::JOB_CANCEL); } MoveSelectionStrokeStrategy::MoveSelectionStrokeStrategy(const MoveSelectionStrokeStrategy &rhs) : QObject(), KisStrokeStrategyUndoCommandBased(rhs), m_paintLayer(rhs.m_paintLayer), m_selection(rhs.m_selection), m_updatesFacade(rhs.m_updatesFacade) { } void MoveSelectionStrokeStrategy::initStrokeCallback() { KisStrokeStrategyUndoCommandBased::initStrokeCallback(); KisPaintDeviceSP paintDevice = m_paintLayer->paintDevice(); KisPaintDeviceSP movedDevice = new KisPaintDevice(m_paintLayer.data(), paintDevice->colorSpace()); QRect copyRect = m_selection->selectedRect(); KisPainter gc(movedDevice); gc.setSelection(m_selection); gc.bitBlt(copyRect.topLeft(), paintDevice, copyRect); gc.end(); KisTransaction cutTransaction(name(), paintDevice); paintDevice->clearSelection(m_selection); runAndSaveCommand(KUndo2CommandSP(cutTransaction.endAndTake()), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::NORMAL); KisIndirectPaintingSupport *indirect = static_cast(m_paintLayer.data()); indirect->setTemporaryTarget(movedDevice); indirect->setTemporaryCompositeOp(COMPOSITE_OVER); indirect->setTemporaryOpacity(OPACITY_OPAQUE_U8); + indirect->setTemporarySelection(0); + indirect->setTemporaryChannelFlags(QBitArray()); m_initialDeviceOffset = QPoint(movedDevice->x(), movedDevice->y()); - - m_selection->setVisible(false); + m_initialSelectionOffset = QPoint(m_selection->x(), m_selection->y()); { QRect handlesRect = movedDevice->exactBounds(); KisLodTransform t(paintDevice); handlesRect = t.mapInverted(handlesRect); - emit this->sigHandlesRectCalculated(handlesRect); + if (!handlesRect.isEmpty()) { + emit this->sigHandlesRectCalculated(handlesRect); + } else { + emit this->sigStrokeStartedEmpty(); + } + } } void MoveSelectionStrokeStrategy::finishStrokeCallback() { KisIndirectPaintingSupport *indirect = static_cast(m_paintLayer.data()); KisTransaction transaction(name(), m_paintLayer->paintDevice()); indirect->mergeToLayer(m_paintLayer, (KisPostExecutionUndoAdapter*)0, KUndo2MagicString()); runAndSaveCommand(KUndo2CommandSP(transaction.endAndTake()), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::NORMAL); indirect->setTemporaryTarget(0); - QPoint selectionOffset(m_selection->x(), m_selection->y()); - m_updatesFacade->blockUpdates(); KUndo2CommandSP moveSelectionCommand( - new KisSelectionMoveCommand2(m_selection, selectionOffset, selectionOffset + m_finalOffset)); + new KisSelectionMoveCommand2(m_selection, + m_initialSelectionOffset, + m_initialSelectionOffset + m_finalOffset)); runAndSaveCommand( moveSelectionCommand, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); m_updatesFacade->unblockUpdates(); m_selection->setVisible(true); KisStrokeStrategyUndoCommandBased::finishStrokeCallback(); } void MoveSelectionStrokeStrategy::cancelStrokeCallback() { KisIndirectPaintingSupport *indirect = static_cast(m_paintLayer.data()); if (indirect) { KisPaintDeviceSP t = indirect->temporaryTarget(); if (t) { KisRegion dirtyRegion = t->region(); indirect->setTemporaryTarget(0); - m_selection->setVisible(true); - m_paintLayer->setDirty(dirtyRegion); + + m_selection->setX(m_initialSelectionOffset.x()); + m_selection->setY(m_initialSelectionOffset.y()); + m_selection->setVisible(true); + m_selection->notifySelectionChanged(); } } KisStrokeStrategyUndoCommandBased::cancelStrokeCallback(); } #include "tool/strokes/move_stroke_strategy.h" void MoveSelectionStrokeStrategy::doStrokeCallback(KisStrokeJobData *data) { MoveStrokeStrategy::Data *d = dynamic_cast(data); + ShowSelectionData *ssd = dynamic_cast(data); if (d) { KisIndirectPaintingSupport *indirect = static_cast(m_paintLayer.data()); KisPaintDeviceSP movedDevice = indirect->temporaryTarget(); QRegion dirtyRegion = movedDevice->region().toQRegion(); QPoint currentDeviceOffset(movedDevice->x(), movedDevice->y()); QPoint newDeviceOffset(m_initialDeviceOffset + d->offset); dirtyRegion |= dirtyRegion.translated(newDeviceOffset - currentDeviceOffset); movedDevice->setX(newDeviceOffset.x()); movedDevice->setY(newDeviceOffset.y()); m_finalOffset = d->offset; m_paintLayer->setDirty(KisRegion::fromQRegion(dirtyRegion)); + + m_selection->setX((m_initialSelectionOffset + d->offset).x()); + m_selection->setY((m_initialSelectionOffset + d->offset).y()); + + if (m_selection->isVisible()) { + m_selection->notifySelectionChanged(); + } + + } else if (ssd) { + m_selection->setVisible(ssd->showSelection); } else { KisStrokeStrategyUndoCommandBased::doStrokeCallback(data); } } KisStrokeStrategy* MoveSelectionStrokeStrategy::createLodClone(int levelOfDetail) { Q_UNUSED(levelOfDetail); // Vector selections don't support lod-moves if (m_selection->hasShapeSelection()) return 0; MoveSelectionStrokeStrategy *clone = new MoveSelectionStrokeStrategy(*this); connect(clone, SIGNAL(sigHandlesRectCalculated(QRect)), this, SIGNAL(sigHandlesRectCalculated(QRect))); return clone; } diff --git a/plugins/tools/basictools/strokes/move_selection_stroke_strategy.h b/plugins/tools/basictools/strokes/move_selection_stroke_strategy.h index 638b0ff385..2276b21b66 100644 --- a/plugins/tools/basictools/strokes/move_selection_stroke_strategy.h +++ b/plugins/tools/basictools/strokes/move_selection_stroke_strategy.h @@ -1,62 +1,75 @@ /* * Copyright (c) 2012 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __MOVE_SELECTION_STROKE_STRATEGY_H #define __MOVE_SELECTION_STROKE_STRATEGY_H #include "kis_stroke_strategy_undo_command_based.h" #include "kis_types.h" #include "kis_selection.h" #include "kis_paint_layer.h" class KisPostExecutionUndoAdapter; class KisUpdatesFacade; class MoveSelectionStrokeStrategy : public QObject, public KisStrokeStrategyUndoCommandBased { Q_OBJECT +public: + struct ShowSelectionData : public KisStrokeJobData + { + ShowSelectionData(bool _showSelection) + : KisStrokeJobData(), + showSelection(_showSelection) + {} + + bool showSelection = false; + }; + public: MoveSelectionStrokeStrategy(KisPaintLayerSP paintLayer, KisSelectionSP selection, KisUpdatesFacade *updatesFacade, KisStrokeUndoFacade *undoFacade); void initStrokeCallback() override; void finishStrokeCallback() override; void cancelStrokeCallback() override; void doStrokeCallback(KisStrokeJobData *data) override; Q_SIGNALS: void sigHandlesRectCalculated(const QRect &handlesRect); + void sigStrokeStartedEmpty(); private: MoveSelectionStrokeStrategy(const MoveSelectionStrokeStrategy &rhs); KisStrokeStrategy* createLodClone(int levelOfDetail) override; private: KisPaintLayerSP m_paintLayer; KisSelectionSP m_selection; KisUpdatesFacade *m_updatesFacade; QPoint m_finalOffset; QPoint m_initialDeviceOffset; + QPoint m_initialSelectionOffset; }; #endif /* __MOVE_SELECTION_STROKE_STRATEGY_H */ diff --git a/plugins/tools/tool_polygon/kis_tool_polygon.cc b/plugins/tools/tool_polygon/kis_tool_polygon.cc index c886ab38fc..3d4830cada 100644 --- a/plugins/tools/tool_polygon/kis_tool_polygon.cc +++ b/plugins/tools/tool_polygon/kis_tool_polygon.cc @@ -1,86 +1,87 @@ /* * kis_tool_polygon.cc -- part of Krita * * Copyright (c) 2004 Michael Thaler * Copyright (c) 2009 Lukáš Tvrdý * Copyright (c) 2010 Cyrille Berger * 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. */ #include "kis_tool_polygon.h" #include #include #include #include #include #include "kis_figure_painting_tool_helper.h" KisToolPolygon::KisToolPolygon(KoCanvasBase *canvas) : KisToolPolylineBase(canvas, KisToolPolylineBase::PAINT, KisCursor::load("tool_polygon_cursor.png", 6, 6)) { setObjectName("tool_polygon"); setSupportOutline(true); } KisToolPolygon::~KisToolPolygon() { } void KisToolPolygon::resetCursorStyle() { KisToolPolylineBase::resetCursorStyle(); overrideCursorIfNotEditable(); } void KisToolPolygon::finishPolyline(const QVector& points) { const KisToolShape::ShapeAddInfo info = shouldAddShape(currentNode()); if (!info.shouldAddShape) { KisFigurePaintingToolHelper helper(kundo2_i18n("Draw Polygon"), image(), currentNode(), canvas()->resourceManager(), strokeStyle(), - fillStyle()); + fillStyle(), + fillTransform()); helper.paintPolygon(points); } else { // remove the last point if it overlaps with the first QVector newPoints = points; if (newPoints.size() > 1 && newPoints.first() == newPoints.last()) { newPoints.removeLast(); } KoPathShape* path = new KoPathShape(); path->setShapeId(KoPathShapeId); QTransform resolutionMatrix; resolutionMatrix.scale(1 / currentImage()->xRes(), 1 / currentImage()->yRes()); path->moveTo(resolutionMatrix.map(newPoints[0])); for (int i = 1; i < newPoints.size(); i++) path->lineTo(resolutionMatrix.map(newPoints[i])); path->close(); path->normalize(); info.markAsSelectionShapeIfNeeded(path); addShape(path); } } diff --git a/plugins/tools/tool_polyline/kis_tool_polyline.cc b/plugins/tools/tool_polyline/kis_tool_polyline.cc index 274dae1250..49b0115906 100644 --- a/plugins/tools/tool_polyline/kis_tool_polyline.cc +++ b/plugins/tools/tool_polyline/kis_tool_polyline.cc @@ -1,82 +1,83 @@ /* * Copyright (c) 2004 Michael Thaler * Copyright (c) 2008 Boudewijn Rempt * Copyright (c) 2009 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_tool_polyline.h" #include #include #include #include #include #include "kis_figure_painting_tool_helper.h" KisToolPolyline::KisToolPolyline(KoCanvasBase * canvas) : KisToolPolylineBase(canvas, KisToolPolylineBase::PAINT, KisCursor::load("tool_polyline_cursor.png", 6, 6)) { setObjectName("tool_polyline"); setSupportOutline(true); } KisToolPolyline::~KisToolPolyline() { } void KisToolPolyline::resetCursorStyle() { KisToolPolylineBase::resetCursorStyle(); overrideCursorIfNotEditable(); } QWidget* KisToolPolyline::createOptionWidget() { return KisToolPolylineBase::createOptionWidget(); } void KisToolPolyline::finishPolyline(const QVector& points) { const KisToolShape::ShapeAddInfo info = shouldAddShape(currentNode()); if (!info.shouldAddShape || info.shouldAddSelectionShape) { KisFigurePaintingToolHelper helper(kundo2_i18n("Draw Polyline"), image(), currentNode(), canvas()->resourceManager(), strokeStyle(), - fillStyle()); + fillStyle(), + fillTransform()); helper.paintPolyline(points); } else { KoPathShape* path = new KoPathShape(); path->setShapeId(KoPathShapeId); QTransform resolutionMatrix; resolutionMatrix.scale(1 / currentImage()->xRes(), 1 / currentImage()->yRes()); path->moveTo(resolutionMatrix.map(points[0])); for (int i = 1; i < points.count(); i++) path->lineTo(resolutionMatrix.map(points[i])); path->normalize(); addShape(path); } } diff --git a/plugins/tools/tool_transform2/kis_tool_transform_config_widget.cpp b/plugins/tools/tool_transform2/kis_tool_transform_config_widget.cpp index bfbc5e2546..628debde9a 100644 --- a/plugins/tools/tool_transform2/kis_tool_transform_config_widget.cpp +++ b/plugins/tools/tool_transform2/kis_tool_transform_config_widget.cpp @@ -1,1288 +1,1288 @@ /* * 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_tool_transform_config_widget.h" #include #include "rotation_icons.h" #include "kis_canvas2.h" #include #include "kis_liquify_properties.h" #include "KisMainWindow.h" #include "KisViewManager.h" #include "kis_transform_utils.h" template inline T sign(T x) { return x > 0 ? 1 : x == (T)0 ? 0 : -1; } const int KisToolTransformConfigWidget::DEFAULT_POINTS_PER_LINE = 3; KisToolTransformConfigWidget::KisToolTransformConfigWidget(TransformTransactionProperties *transaction, KisCanvas2 *canvas, bool workRecursively, QWidget *parent) : QWidget(parent), m_transaction(transaction), m_notificationsBlocked(0), m_uiSlotsBlocked(0), m_configChanged(false) { setupUi(this); chkWorkRecursively->setIcon(KisIconUtils::loadIcon("krita_tool_transform_recursive")); flipXButton->setIcon(KisIconUtils::loadIcon("transform_icons_mirror_x")); flipYButton->setIcon(KisIconUtils::loadIcon("transform_icons_mirror_y")); rotateCWButton->setIcon(KisIconUtils::loadIcon("transform_icons_rotate_cw")); rotateCCWButton->setIcon(KisIconUtils::loadIcon("transform_icons_rotate_ccw")); chkWorkRecursively->setChecked(workRecursively); connect(chkWorkRecursively, SIGNAL(toggled(bool)), this, SIGNAL(sigRestartTransform())); // Granularity can only be specified in the power of 2's QStringList granularityValues{"4","8","16","32"}; changeGranularity->addItems(granularityValues); changeGranularity->setCurrentIndex(1); granularityPreview->addItems(granularityValues); granularityPreview->setCurrentIndex(2); connect(changeGranularity,SIGNAL(currentIndexChanged(QString)), this,SLOT(slotGranularityChanged(QString))); connect(granularityPreview, SIGNAL(currentIndexChanged(QString)), this,SLOT(slotPreviewGranularityChanged(QString))); // Init Filter combo cmbFilter->setIDList(KisFilterStrategyRegistry::instance()->listKeys()); cmbFilter->setCurrent("Bicubic"); cmbFilter->setToolTip(i18nc("@info:tooltip", "

Select filtering mode:\n" "

    " "
  • Bilinear for areas with uniform color to avoid artifacts
  • " "
  • Bicubic for smoother results
  • " "
  • Lanczos3 for sharp results. May produce aerials.
  • " "

")); connect(cmbFilter, SIGNAL(activated(KoID)), this, SLOT(slotFilterChanged(KoID))); // Init Warp Type combo cmbWarpType->insertItem(KisWarpTransformWorker::AFFINE_TRANSFORM,i18n("Default (Affine)")); cmbWarpType->insertItem(KisWarpTransformWorker::RIGID_TRANSFORM,i18n("Strong (Rigid)")); cmbWarpType->insertItem(KisWarpTransformWorker::SIMILITUDE_TRANSFORM,i18n("Strongest (Similitude)")); cmbWarpType->setCurrentIndex(KisWarpTransformWorker::AFFINE_TRANSFORM); connect(cmbWarpType, SIGNAL(currentIndexChanged(int)), this, SLOT(slotWarpTypeChanged(int))); // Init Rotation Center buttons m_handleDir[0] = QPointF(1, 0); m_handleDir[1] = QPointF(1, -1); m_handleDir[2] = QPointF(0, -1); m_handleDir[3] = QPointF(-1, -1); m_handleDir[4] = QPointF(-1, 0); m_handleDir[5] = QPointF(-1, 1); m_handleDir[6] = QPointF(0, 1); m_handleDir[7] = QPointF(1, 1); m_handleDir[8] = QPointF(0, 0); // also add the center m_rotationCenterButtons = new QButtonGroup(0); // we set the ids to match m_handleDir m_rotationCenterButtons->addButton(middleRightButton, 0); m_rotationCenterButtons->addButton(topRightButton, 1); m_rotationCenterButtons->addButton(middleTopButton, 2); m_rotationCenterButtons->addButton(topLeftButton, 3); m_rotationCenterButtons->addButton(middleLeftButton, 4); m_rotationCenterButtons->addButton(bottomLeftButton, 5); m_rotationCenterButtons->addButton(middleBottomButton, 6); m_rotationCenterButtons->addButton(bottomRightButton, 7); m_rotationCenterButtons->addButton(centerButton, 8); QToolButton *nothingSelected = new QToolButton(0); nothingSelected->setCheckable(true); nothingSelected->setAutoExclusive(true); nothingSelected->hide(); // a convenient button for when no button is checked in the group m_rotationCenterButtons->addButton(nothingSelected, 9); // initialize values for free transform sliders - shearXBox->setSuffix(i18n(" px")); - shearYBox->setSuffix(i18n(" px")); - shearXBox->setRange(-5.0, 5.0, 2); - shearYBox->setRange(-5.0, 5.0, 2); - shearXBox->setSingleStep(0.01); - shearYBox->setSingleStep(0.01); + shearXBox->setSuffix(QChar(Qt::Key_Percent)); + shearYBox->setSuffix(QChar(Qt::Key_Percent)); + shearXBox->setRange(-500, 500, 2); + shearYBox->setRange(-500, 500, 2); + shearXBox->setSingleStep(1); + shearYBox->setSingleStep(1); shearXBox->setValue(0.0); shearYBox->setValue(0.0); translateXBox->setSuffix(i18n(" px")); translateYBox->setSuffix(i18n(" px")); translateXBox->setRange(-10000, 10000); translateYBox->setRange(-10000, 10000); scaleXBox->setRange(-10000, 10000); scaleYBox->setRange(-10000, 10000); scaleXBox->setValue(100.0); scaleYBox->setValue(100.0); m_scaleRatio = 1.0; aXBox->setSuffix(QChar(Qt::Key_degree)); aYBox->setSuffix(QChar(Qt::Key_degree)); aZBox->setSuffix(QChar(Qt::Key_degree)); aXBox->setRange(0.0, 360.0, 2); aYBox->setRange(0.0, 360.0, 2); aZBox->setRange(0.0, 360.0, 2); aXBox->setValue(0.0); aYBox->setValue(0.0); aZBox->setValue(0.0); aXBox->setSingleStep(1.0); aYBox->setSingleStep(1.0); aZBox->setSingleStep(1.0); connect(m_rotationCenterButtons, SIGNAL(buttonPressed(int)), this, SLOT(slotRotationCenterChanged(int))); connect(btnTransformAroundPivotPoint, SIGNAL(clicked(bool)), this, SLOT(slotTransformAroundRotationCenter(bool))); // Init Free Transform Values connect(scaleXBox, SIGNAL(valueChanged(int)), this, SLOT(slotSetScaleX(int))); connect(scaleYBox, SIGNAL(valueChanged(int)), this, SLOT(slotSetScaleY(int))); connect(shearXBox, SIGNAL(valueChanged(qreal)), this, SLOT(slotSetShearX(qreal))); connect(shearYBox, SIGNAL(valueChanged(qreal)), this, SLOT(slotSetShearY(qreal))); connect(translateXBox, SIGNAL(valueChanged(int)), this, SLOT(slotSetTranslateX(int))); connect(translateYBox, SIGNAL(valueChanged(int)), this, SLOT(slotSetTranslateY(int))); connect(aXBox, SIGNAL(valueChanged(qreal)), this, SLOT(slotSetAX(qreal))); connect(aYBox, SIGNAL(valueChanged(qreal)), this, SLOT(slotSetAY(qreal))); connect(aZBox, SIGNAL(valueChanged(qreal)), this, SLOT(slotSetAZ(qreal))); connect(aspectButton, SIGNAL(keepAspectRatioChanged(bool)), this, SLOT(slotSetKeepAspectRatio(bool))); connect(flipXButton, SIGNAL(clicked(bool)), this, SLOT(slotFlipX())); connect(flipYButton, SIGNAL(clicked(bool)), this, SLOT(slotFlipY())); connect(rotateCWButton, SIGNAL(clicked(bool)), this, SLOT(slotRotateCW())); connect(rotateCCWButton, SIGNAL(clicked(bool)), this, SLOT(slotRotateCCW())); // toggle visibility of different free buttons connect(freeMoveRadioButton, SIGNAL(clicked(bool)), SLOT(slotTransformAreaVisible(bool))); connect(freeRotationRadioButton, SIGNAL(clicked(bool)), SLOT(slotTransformAreaVisible(bool))); connect(freeScaleRadioButton, SIGNAL(clicked(bool)), SLOT(slotTransformAreaVisible(bool))); connect(freeShearRadioButton, SIGNAL(clicked(bool)), SLOT(slotTransformAreaVisible(bool))); // only first group for free transform rotationGroup->hide(); moveGroup->show(); scaleGroup->hide(); shearGroup->hide(); // Init Warp Transform Values alphaBox->setSingleStep(0.1); alphaBox->setRange(0, 10, 1); connect(alphaBox, SIGNAL(valueChanged(qreal)), this, SLOT(slotSetWarpAlpha(qreal))); connect(densityBox, SIGNAL(valueChanged(int)), this, SLOT(slotSetWarpDensity(int))); connect(defaultRadioButton, SIGNAL(clicked(bool)), this, SLOT(slotWarpDefaultPointsButtonClicked(bool))); connect(customRadioButton, SIGNAL(clicked(bool)), this, SLOT(slotWarpCustomPointsButtonClicked(bool))); connect(lockUnlockPointsButton, SIGNAL(clicked()), this, SLOT(slotWarpLockPointsButtonClicked())); connect(resetPointsButton, SIGNAL(clicked()), this, SLOT(slotWarpResetPointsButtonClicked())); // Init Cage Transform Values cageTransformButtonGroup->setId(cageAddEditRadio, 0); // we need to set manually since Qt Designer generates negative by default cageTransformButtonGroup->setId(cageDeformRadio, 1); connect(cageTransformButtonGroup, SIGNAL(buttonClicked(int)), this, SLOT(slotCageOptionsChanged(int))); // Init Liquify Transform Values liquifySizeSlider->setRange(KisLiquifyProperties::minSize(), KisLiquifyProperties::maxSize(), 2); liquifySizeSlider->setExponentRatio(4); liquifySizeSlider->setValue(60.0); connect(liquifySizeSlider, SIGNAL(valueChanged(qreal)), this, SLOT(liquifySizeChanged(qreal))); liquifySizeSlider->setToolTip(i18nc("@info:tooltip", "Size of the deformation brush")); liquifyAmountSlider->setRange(0.0, 1.0, 2); liquifyAmountSlider->setValue(0.05); connect(liquifyAmountSlider, SIGNAL(valueChanged(qreal)), this, SLOT(liquifyAmountChanged(qreal))); liquifyAmountSlider->setToolTip(i18nc("@info:tooltip", "Amount of the deformation you get")); liquifyFlowSlider->setRange(0.0, 1.0, 2); liquifyFlowSlider->setValue(1.0); connect(liquifyFlowSlider, SIGNAL(valueChanged(qreal)), this, SLOT(liquifyFlowChanged(qreal))); liquifyFlowSlider->setToolTip(i18nc("@info:tooltip", "When in non-buildup mode, shows how fast the deformation limit is reached.")); buidupModeComboBox->setCurrentIndex(0); // set to build-up mode by default connect(buidupModeComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(liquifyBuildUpChanged(int))); buidupModeComboBox->setToolTip("

" + i18nc("@info:tooltip", "Switch between Build Up and Wash mode of painting. Build Up mode adds deformations one on top of the other without any limits. Wash mode gradually deforms the piece to the selected deformation level.") + "

"); liquifySpacingSlider->setRange(0.0, 3.0, 2); liquifySizeSlider->setExponentRatio(3); liquifySpacingSlider->setSingleStep(0.01); liquifySpacingSlider->setValue(0.2); connect(liquifySpacingSlider, SIGNAL(valueChanged(qreal)), this, SLOT(liquifySpacingChanged(qreal))); liquifySpacingSlider->setToolTip(i18nc("@info:tooltip", "Space between two sequential applications of the deformation")); liquifySizePressureBox->setChecked(true); connect(liquifySizePressureBox, SIGNAL(toggled(bool)), this, SLOT(liquifySizePressureChanged(bool))); liquifySizePressureBox->setToolTip(i18nc("@info:tooltip", "Scale Size value according to current stylus pressure")); liquifyAmountPressureBox->setChecked(true); connect(liquifyAmountPressureBox, SIGNAL(toggled(bool)), this, SLOT(liquifyAmountPressureChanged(bool))); liquifyAmountPressureBox->setToolTip(i18nc("@info:tooltip", "Scale Amount value according to current stylus pressure")); liquifyReverseDirectionChk->setChecked(false); connect(liquifyReverseDirectionChk, SIGNAL(toggled(bool)), this, SLOT(liquifyReverseDirectionChanged(bool))); liquifyReverseDirectionChk->setToolTip(i18nc("@info:tooltip", "Reverse direction of the current deformation tool")); KisSignalMapper *liquifyModeMapper = new KisSignalMapper(this); connect(liquifyMove, SIGNAL(toggled(bool)), liquifyModeMapper, SLOT(map())); connect(liquifyScale, SIGNAL(toggled(bool)), liquifyModeMapper, SLOT(map())); connect(liquifyRotate, SIGNAL(toggled(bool)), liquifyModeMapper, SLOT(map())); connect(liquifyOffset, SIGNAL(toggled(bool)), liquifyModeMapper, SLOT(map())); connect(liquifyUndo, SIGNAL(toggled(bool)), liquifyModeMapper, SLOT(map())); liquifyModeMapper->setMapping(liquifyMove, (int)KisLiquifyProperties::MOVE); liquifyModeMapper->setMapping(liquifyScale, (int)KisLiquifyProperties::SCALE); liquifyModeMapper->setMapping(liquifyRotate, (int)KisLiquifyProperties::ROTATE); liquifyModeMapper->setMapping(liquifyOffset, (int)KisLiquifyProperties::OFFSET); liquifyModeMapper->setMapping(liquifyUndo, (int)KisLiquifyProperties::UNDO); connect(liquifyModeMapper, SIGNAL(mapped(int)), SLOT(slotLiquifyModeChanged(int))); liquifyMove->setToolTip(i18nc("@info:tooltip", "Move: drag the image along the brush stroke")); liquifyScale->setToolTip(i18nc("@info:tooltip", "Scale: grow/shrink image under cursor")); liquifyRotate->setToolTip(i18nc("@info:tooltip", "Rotate: twirl image under cursor")); liquifyOffset->setToolTip(i18nc("@info:tooltip", "Offset: shift the image to the right of the stroke direction")); liquifyUndo->setToolTip(i18nc("@info:tooltip", "Undo: erase actions of other tools")); // Connect all edit boxes to the Editing Finished signal connect(densityBox, SIGNAL(editingFinished()), this, SLOT(notifyEditingFinished())); // Connect other widget (not having editingFinished signal) to // the same slot. From Qt 4.6 onwards the sequence of the signal // delivery is definite. connect(cmbFilter, SIGNAL(activated(KoID)), this, SLOT(notifyEditingFinished())); connect(cmbWarpType, SIGNAL(currentIndexChanged(int)), this, SLOT(notifyEditingFinished())); connect(m_rotationCenterButtons, SIGNAL(buttonPressed(int)), this, SLOT(notifyEditingFinished())); connect(aspectButton, SIGNAL(keepAspectRatioChanged(bool)), this, SLOT(notifyEditingFinished())); connect(lockUnlockPointsButton, SIGNAL(clicked()), this, SLOT(notifyEditingFinished())); connect(resetPointsButton, SIGNAL(clicked()), this, SLOT(notifyEditingFinished())); connect(defaultRadioButton, SIGNAL(clicked(bool)), this, SLOT(notifyEditingFinished())); connect(customRadioButton, SIGNAL(clicked(bool)), this, SLOT(notifyEditingFinished())); // Liquify // // liquify brush options do not affect the image directly and are not // saved to undo, so we don't emit notifyEditingFinished() for them // Connect Apply/Reset buttons connect(buttonBox, SIGNAL(clicked(QAbstractButton*)), this, SLOT(slotButtonBoxClicked(QAbstractButton*))); // Mode switch buttons connect(freeTransformButton, SIGNAL(clicked(bool)), this, SLOT(slotSetFreeTransformModeButtonClicked(bool))); connect(warpButton, SIGNAL(clicked(bool)), this, SLOT(slotSetWarpModeButtonClicked(bool))); connect(cageButton, SIGNAL(clicked(bool)), this, SLOT(slotSetCageModeButtonClicked(bool))); connect(perspectiveTransformButton, SIGNAL(clicked(bool)), this, SLOT(slotSetPerspectiveModeButtonClicked(bool))); connect(liquifyButton, SIGNAL(clicked(bool)), this, SLOT(slotSetLiquifyModeButtonClicked(bool))); tooBigLabelWidget->hide(); connect(canvas->viewManager()->mainWindow(), SIGNAL(themeChanged()), SLOT(slotUpdateIcons()), Qt::UniqueConnection); slotUpdateIcons(); } void KisToolTransformConfigWidget::slotUpdateIcons() { freeTransformButton->setIcon(KisIconUtils::loadIcon("transform_icons_main")); warpButton->setIcon(KisIconUtils::loadIcon("transform_icons_warp")); cageButton->setIcon(KisIconUtils::loadIcon("transform_icons_cage")); perspectiveTransformButton->setIcon(KisIconUtils::loadIcon("transform_icons_perspective")); liquifyButton->setIcon(KisIconUtils::loadIcon("transform_icons_liquify_main")); liquifyMove->setIcon(KisIconUtils::loadIcon("transform_icons_liquify_move")); liquifyScale->setIcon(KisIconUtils::loadIcon("transform_icons_liquify_resize")); liquifyRotate->setIcon(KisIconUtils::loadIcon("transform_icons_liquify_rotate")); liquifyOffset->setIcon(KisIconUtils::loadIcon("transform_icons_liquify_offset")); liquifyUndo->setIcon(KisIconUtils::loadIcon("transform_icons_liquify_erase")); middleRightButton->setIcon(KisIconUtils::loadIcon("arrow-right")); topRightButton->setIcon(KisIconUtils::loadIcon("arrow-topright")); middleTopButton->setIcon(KisIconUtils::loadIcon("arrow-up")); topLeftButton->setIcon(KisIconUtils::loadIcon("arrow-topleft")); middleLeftButton->setIcon(KisIconUtils::loadIcon("arrow-left")); bottomLeftButton->setIcon(KisIconUtils::loadIcon("arrow-downleft")); middleBottomButton->setIcon(KisIconUtils::loadIcon("arrow-down")); bottomRightButton->setIcon(KisIconUtils::loadIcon("arrow-downright")); btnTransformAroundPivotPoint->setIcon(KisIconUtils::loadIcon("pivot-point")); // pressure icons liquifySizePressureBox->setIcon(KisIconUtils::loadIcon("transform_icons_penPressure")); liquifyAmountPressureBox->setIcon(KisIconUtils::loadIcon("transform_icons_penPressure")); } double KisToolTransformConfigWidget::radianToDegree(double rad) { double piX2 = 2 * M_PI; if (rad < 0 || rad >= piX2) { rad = fmod(rad, piX2); if (rad < 0) { rad += piX2; } } return (rad * 360. / piX2); } double KisToolTransformConfigWidget::degreeToRadian(double degree) { if (degree < 0. || degree >= 360.) { degree = fmod(degree, 360.); if (degree < 0) degree += 360.; } return (degree * M_PI / 180.); } void KisToolTransformConfigWidget::updateLiquifyControls() { blockUiSlots(); ToolTransformArgs *config = m_transaction->currentConfig(); KisLiquifyProperties *props = config->liquifyProperties(); const bool useWashMode = props->useWashMode(); liquifySizeSlider->setValue(props->size()); liquifyAmountSlider->setValue(props->amount()); liquifyFlowSlider->setValue(props->flow()); buidupModeComboBox->setCurrentIndex(useWashMode); liquifySpacingSlider->setValue(props->spacing()); liquifySizePressureBox->setChecked(props->sizeHasPressure()); liquifyAmountPressureBox->setChecked(props->amountHasPressure()); liquifyReverseDirectionChk->setChecked(props->reverseDirection()); KisLiquifyProperties::LiquifyMode mode = static_cast(props->mode()); bool canInverseDirection = mode != KisLiquifyProperties::UNDO; bool canUseWashMode = mode != KisLiquifyProperties::UNDO; liquifyReverseDirectionChk->setEnabled(canInverseDirection); liquifyFlowSlider->setEnabled(canUseWashMode && useWashMode); buidupModeComboBox->setEnabled(canUseWashMode); const qreal maxAmount = canUseWashMode ? 5.0 : 1.0; liquifyAmountSlider->setRange(0.0, maxAmount, 2); unblockUiSlots(); } void KisToolTransformConfigWidget::slotLiquifyModeChanged(int value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); KisLiquifyProperties *props = config->liquifyProperties(); KisLiquifyProperties::LiquifyMode mode = static_cast(value); if (mode == props->mode()) return; props->setMode(mode); props->loadMode(); updateLiquifyControls(); notifyConfigChanged(); } void KisToolTransformConfigWidget::liquifySizeChanged(qreal value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); KisLiquifyProperties *props = config->liquifyProperties(); props->setSize(value); notifyConfigChanged(); } void KisToolTransformConfigWidget::liquifyAmountChanged(qreal value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); KisLiquifyProperties *props = config->liquifyProperties(); props->setAmount(value); notifyConfigChanged(); } void KisToolTransformConfigWidget::liquifyFlowChanged(qreal value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); KisLiquifyProperties *props = config->liquifyProperties(); props->setFlow(value); notifyConfigChanged(); } void KisToolTransformConfigWidget::liquifyBuildUpChanged(int value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); KisLiquifyProperties *props = config->liquifyProperties(); props->setUseWashMode(value); // 0 == build up mode / 1 == wash mode notifyConfigChanged(); // we need to enable/disable flow slider updateLiquifyControls(); } void KisToolTransformConfigWidget::liquifySpacingChanged(qreal value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); KisLiquifyProperties *props = config->liquifyProperties(); props->setSpacing(value); notifyConfigChanged(); } void KisToolTransformConfigWidget::liquifySizePressureChanged(bool value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); KisLiquifyProperties *props = config->liquifyProperties(); props->setSizeHasPressure(value); notifyConfigChanged(); } void KisToolTransformConfigWidget::liquifyAmountPressureChanged(bool value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); KisLiquifyProperties *props = config->liquifyProperties(); props->setAmountHasPressure(value); notifyConfigChanged(); } void KisToolTransformConfigWidget::liquifyReverseDirectionChanged(bool value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); KisLiquifyProperties *props = config->liquifyProperties(); props->setReverseDirection(value); notifyConfigChanged(); } void KisToolTransformConfigWidget::updateConfig(const ToolTransformArgs &config) { blockUiSlots(); if (config.mode() == ToolTransformArgs::FREE_TRANSFORM || config.mode() == ToolTransformArgs::PERSPECTIVE_4POINT) { quickTransformGroup->setEnabled(config.mode() == ToolTransformArgs::FREE_TRANSFORM); stackedWidget->setCurrentIndex(0); bool freeTransformIsActive = config.mode() == ToolTransformArgs::FREE_TRANSFORM; if (freeTransformIsActive) { freeTransformButton->setChecked(true); } else { perspectiveTransformButton->setChecked(true); } aXBox->setEnabled(freeTransformIsActive); aYBox->setEnabled(freeTransformIsActive); aZBox->setEnabled(freeTransformIsActive); freeRotationRadioButton->setEnabled(freeTransformIsActive); scaleXBox->setValue(config.scaleX() * 100.); scaleYBox->setValue(config.scaleY() * 100.); - shearXBox->setValue(config.shearX()); - shearYBox->setValue(config.shearY()); + shearXBox->setValue(config.shearX() * 100.); + shearYBox->setValue(config.shearY() * 100.); const QPointF anchorPoint = config.originalCenter() + config.rotationCenterOffset(); const KisTransformUtils::MatricesPack m(config); const QPointF anchorPointView = m.finalTransform().map(anchorPoint); translateXBox->setValue(anchorPointView.x()); translateYBox->setValue(anchorPointView.y()); aXBox->setValue(radianToDegree(config.aX())); aYBox->setValue(radianToDegree(config.aY())); aZBox->setValue(radianToDegree(config.aZ())); aspectButton->setKeepAspectRatio(config.keepAspectRatio()); cmbFilter->setCurrent(config.filterId()); QPointF pt = m_transaction->currentConfig()->rotationCenterOffset(); pt.rx() /= m_transaction->originalHalfWidth(); pt.ry() /= m_transaction->originalHalfHeight(); for (int i = 0; i < 9; i++) { if (qFuzzyCompare(m_handleDir[i].x(), pt.x()) && qFuzzyCompare(m_handleDir[i].y(), pt.y())) { m_rotationCenterButtons->button(i)->setChecked(true); break; } } btnTransformAroundPivotPoint->setChecked(config.transformAroundRotationCenter()); } else if (config.mode() == ToolTransformArgs::WARP) { stackedWidget->setCurrentIndex(1); warpButton->setChecked(true); if (config.defaultPoints()) { densityBox->setValue(std::sqrt(config.numPoints())); } cmbWarpType->setCurrentIndex((int)config.warpType()); defaultRadioButton->setChecked(config.defaultPoints()); customRadioButton->setChecked(!config.defaultPoints()); densityBox->setEnabled(config.defaultPoints()); customWarpWidget->setEnabled(!config.defaultPoints()); updateLockPointsButtonCaption(); } else if (config.mode() == ToolTransformArgs::CAGE) { // default UI options resetUIOptions(); // we need at least 3 point before we can start actively deforming if (config.origPoints().size() >= 3) { cageTransformDirections->setText(i18n("Switch between editing and deforming cage")); cageAddEditRadio->setVisible(true); cageDeformRadio->setVisible(true); // update to correct radio button if (config.isEditingTransformPoints()) cageAddEditRadio->setChecked(true); else cageDeformRadio->setChecked(true); changeGranularity->setCurrentIndex(log2(config.pixelPrecision()) - 2); granularityPreview->setCurrentIndex(log2(config.previewPixelPrecision()) - 2); } } else if (config.mode() == ToolTransformArgs::LIQUIFY) { stackedWidget->setCurrentIndex(3); liquifyButton->setChecked(true); const KisLiquifyProperties *props = config.liquifyProperties(); switch (props->mode()) { case KisLiquifyProperties::MOVE: liquifyMove->setChecked(true); break; case KisLiquifyProperties::SCALE: liquifyScale->setChecked(true); break; case KisLiquifyProperties::ROTATE: liquifyRotate->setChecked(true); break; case KisLiquifyProperties::OFFSET: liquifyOffset->setChecked(true); break; case KisLiquifyProperties::UNDO: liquifyUndo->setChecked(true); break; case KisLiquifyProperties::N_MODES: qFatal("Unsupported mode"); } updateLiquifyControls(); } unblockUiSlots(); } void KisToolTransformConfigWidget::updateLockPointsButtonCaption() { ToolTransformArgs *config = m_transaction->currentConfig(); if (config->isEditingTransformPoints()) { lockUnlockPointsButton->setText(i18n("Lock Points")); } else { lockUnlockPointsButton->setText(i18n("Unlock Points")); } } void KisToolTransformConfigWidget::setApplyResetDisabled(bool disabled) { QAbstractButton *applyButton = buttonBox->button(QDialogButtonBox::Apply); QAbstractButton *resetButton = buttonBox->button(QDialogButtonBox::Reset); Q_ASSERT(applyButton); Q_ASSERT(resetButton); applyButton->setDisabled(disabled); resetButton->setDisabled(disabled); } void KisToolTransformConfigWidget::resetRotationCenterButtons() { int checkedId = m_rotationCenterButtons->checkedId(); if (checkedId >= 0 && checkedId <= 8) { // uncheck the current checked button m_rotationCenterButtons->button(9)->setChecked(true); } } bool KisToolTransformConfigWidget::workRecursively() const { return chkWorkRecursively->isChecked(); } void KisToolTransformConfigWidget::setTooBigLabelVisible(bool value) { tooBigLabelWidget->setVisible(value); } void KisToolTransformConfigWidget::blockNotifications() { m_notificationsBlocked++; } void KisToolTransformConfigWidget::unblockNotifications() { m_notificationsBlocked--; } void KisToolTransformConfigWidget::notifyConfigChanged() { if (!m_notificationsBlocked) { emit sigConfigChanged(); } m_configChanged = true; } void KisToolTransformConfigWidget::blockUiSlots() { m_uiSlotsBlocked++; } void KisToolTransformConfigWidget::unblockUiSlots() { m_uiSlotsBlocked--; } void KisToolTransformConfigWidget::notifyEditingFinished() { if (m_uiSlotsBlocked || m_notificationsBlocked || !m_configChanged) return; emit sigEditingFinished(); m_configChanged = false; } void KisToolTransformConfigWidget::resetUIOptions() { // reset tool states since we are done (probably can encapsulate this later) ToolTransformArgs *config = m_transaction->currentConfig(); if (config->mode() == ToolTransformArgs::CAGE) { cageAddEditRadio->setVisible(false); cageAddEditRadio->setChecked(true); cageDeformRadio->setVisible(false); cageTransformDirections->setText(i18n("Create 3 points on the canvas to begin")); // ensure we are on the right options view stackedWidget->setCurrentIndex(2); } } void KisToolTransformConfigWidget::slotButtonBoxClicked(QAbstractButton *button) { QAbstractButton *applyButton = buttonBox->button(QDialogButtonBox::Apply); QAbstractButton *resetButton = buttonBox->button(QDialogButtonBox::Reset); resetUIOptions(); if (button == applyButton) { emit sigApplyTransform(); } else if (button == resetButton) { emit sigResetTransform(); } } void KisToolTransformConfigWidget::slotSetFreeTransformModeButtonClicked(bool value) { if (!value) return; lblTransformType->setText(freeTransformButton->toolTip()); ToolTransformArgs *config = m_transaction->currentConfig(); config->setMode(ToolTransformArgs::FREE_TRANSFORM); emit sigResetTransform(); } void KisToolTransformConfigWidget::slotSetWarpModeButtonClicked(bool value) { if (!value) return; lblTransformType->setText(warpButton->toolTip()); ToolTransformArgs *config = m_transaction->currentConfig(); config->setMode(ToolTransformArgs::WARP); config->setWarpCalculation(KisWarpTransformWorker::WarpCalculation::GRID); emit sigResetTransform(); } void KisToolTransformConfigWidget::slotSetCageModeButtonClicked(bool value) { if (!value) return; lblTransformType->setText(cageButton->toolTip()); ToolTransformArgs *config = m_transaction->currentConfig(); config->setMode(ToolTransformArgs::CAGE); emit sigResetTransform(); } void KisToolTransformConfigWidget::slotSetLiquifyModeButtonClicked(bool value) { if (!value) return; lblTransformType->setText(liquifyButton->toolTip()); ToolTransformArgs *config = m_transaction->currentConfig(); config->setMode(ToolTransformArgs::LIQUIFY); emit sigResetTransform(); } void KisToolTransformConfigWidget::slotSetPerspectiveModeButtonClicked(bool value) { if (!value) return; lblTransformType->setText(perspectiveTransformButton->toolTip()); ToolTransformArgs *config = m_transaction->currentConfig(); config->setMode(ToolTransformArgs::PERSPECTIVE_4POINT); emit sigResetTransform(); } void KisToolTransformConfigWidget::slotFilterChanged(const KoID &filterId) { ToolTransformArgs *config = m_transaction->currentConfig(); config->setFilterId(filterId.id()); notifyConfigChanged(); } void KisToolTransformConfigWidget::slotRotationCenterChanged(int index) { if (m_uiSlotsBlocked) return; if (index >= 0 && index <= 8) { ToolTransformArgs *config = m_transaction->currentConfig(); double i = m_handleDir[index].x(); double j = m_handleDir[index].y(); config->setRotationCenterOffset(QPointF(i * m_transaction->originalHalfWidth(), j * m_transaction->originalHalfHeight())); notifyConfigChanged(); updateConfig(*config); } } void KisToolTransformConfigWidget::slotTransformAroundRotationCenter(bool value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); config->setTransformAroundRotationCenter(value); notifyConfigChanged(); notifyEditingFinished(); } void KisToolTransformConfigWidget::slotSetScaleX(int value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); { KisTransformUtils::AnchorHolder keeper(config->transformAroundRotationCenter(), config); config->setScaleX(value / 100.); } if (config->keepAspectRatio()) { blockNotifications(); int calculatedValue = int( value/ m_scaleRatio ); scaleYBox->blockSignals(true); scaleYBox->setValue(calculatedValue); { KisTransformUtils::AnchorHolder keeper(config->transformAroundRotationCenter(), config); config->setScaleY(calculatedValue / 100.); } scaleYBox->blockSignals(false); unblockNotifications(); } notifyConfigChanged(); notifyEditingFinished(); } void KisToolTransformConfigWidget::slotSetScaleY(int value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); { KisTransformUtils::AnchorHolder keeper(config->transformAroundRotationCenter(), config); config->setScaleY(value / 100.); } if (config->keepAspectRatio()) { blockNotifications(); int calculatedValue = int(m_scaleRatio * value); scaleXBox->blockSignals(true); scaleXBox->setValue(calculatedValue); { KisTransformUtils::AnchorHolder keeper(config->transformAroundRotationCenter(), config); config->setScaleX(calculatedValue / 100.); } scaleXBox->blockSignals(false); unblockNotifications(); } notifyConfigChanged(); notifyEditingFinished(); } void KisToolTransformConfigWidget::slotSetShearX(qreal value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); { KisTransformUtils::AnchorHolder keeper(config->transformAroundRotationCenter(), config); - config->setShearX((double)value); + config->setShearX((double)value / 100.); } notifyConfigChanged(); notifyEditingFinished(); } void KisToolTransformConfigWidget::slotSetShearY(qreal value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); { KisTransformUtils::AnchorHolder keeper(config->transformAroundRotationCenter(), config); - config->setShearY((double)value); + config->setShearY((double)value / 100.); } notifyConfigChanged(); notifyEditingFinished(); } void KisToolTransformConfigWidget::slotSetTranslateX(int value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); const QPointF anchorPoint = config->originalCenter() + config->rotationCenterOffset(); const KisTransformUtils::MatricesPack m(*config); const QPointF anchorPointView = m.finalTransform().map(anchorPoint); const QPointF newAnchorPointView(value, anchorPointView.y()); config->setTransformedCenter(config->transformedCenter() + newAnchorPointView - anchorPointView); translateXBox->setValue(value); notifyConfigChanged(); } void KisToolTransformConfigWidget::slotSetTranslateY(int value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); const QPointF anchorPoint = config->originalCenter() + config->rotationCenterOffset(); const KisTransformUtils::MatricesPack m(*config); const QPointF anchorPointView = m.finalTransform().map(anchorPoint); const QPointF newAnchorPointView(anchorPointView.x(), value); config->setTransformedCenter(config->transformedCenter() + newAnchorPointView - anchorPointView); translateYBox->setValue(value); notifyConfigChanged(); } void KisToolTransformConfigWidget::slotSetAX(qreal value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); { KisTransformUtils::AnchorHolder keeper(config->transformAroundRotationCenter(), config); config->setAX(degreeToRadian((double)value)); } notifyConfigChanged(); notifyEditingFinished(); } void KisToolTransformConfigWidget::slotSetAY(qreal value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); { KisTransformUtils::AnchorHolder keeper(config->transformAroundRotationCenter(), config); config->setAY(degreeToRadian((double)value)); } notifyConfigChanged(); notifyEditingFinished(); } void KisToolTransformConfigWidget::slotSetAZ(qreal value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); { KisTransformUtils::AnchorHolder keeper(config->transformAroundRotationCenter(), config); config->setAZ(degreeToRadian((double)value)); } notifyConfigChanged(); notifyEditingFinished(); } void KisToolTransformConfigWidget::slotFlipX() { ToolTransformArgs *config = m_transaction->currentConfig(); { KisTransformUtils::AnchorHolder keeper(config->transformAroundRotationCenter(), config); config->setScaleX(config->scaleX() * -1); } notifyConfigChanged(); notifyEditingFinished(); } void KisToolTransformConfigWidget::slotFlipY() { ToolTransformArgs *config = m_transaction->currentConfig(); { KisTransformUtils::AnchorHolder keeper(config->transformAroundRotationCenter(), config); config->setScaleY(config->scaleY() * -1); } notifyConfigChanged(); notifyEditingFinished(); } void KisToolTransformConfigWidget::slotRotateCW() { ToolTransformArgs *config = m_transaction->currentConfig(); { KisTransformUtils::AnchorHolder keeper(config->transformAroundRotationCenter(), config); config->setAZ(normalizeAngle(config->aZ() + M_PI_2)); } notifyConfigChanged(); notifyEditingFinished(); } void KisToolTransformConfigWidget::slotRotateCCW() { ToolTransformArgs *config = m_transaction->currentConfig(); { KisTransformUtils::AnchorHolder keeper(config->transformAroundRotationCenter(), config); config->setAZ(normalizeAngle(config->aZ() - M_PI_2)); } notifyConfigChanged(); notifyEditingFinished(); } // change free transform setting we want to alter (all radio buttons call this) void KisToolTransformConfigWidget::slotTransformAreaVisible(bool value) { Q_UNUSED(value); //QCheckBox sender = (QCheckBox)(*)(QObject::sender()); QString senderName = QObject::sender()->objectName(); // only show setting with what we have selected rotationGroup->hide(); shearGroup->hide(); scaleGroup->hide(); moveGroup->hide(); if ("freeMoveRadioButton" == senderName) { moveGroup->show(); } else if ("freeShearRadioButton" == senderName) { shearGroup->show(); } else if ("freeScaleRadioButton" == senderName) { scaleGroup->show(); } else { rotationGroup->show(); } } void KisToolTransformConfigWidget::slotSetKeepAspectRatio(bool value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); config->setKeepAspectRatio(value); if (value) { blockNotifications(); int tmpXScaleBox = scaleXBox->value(); int tmpYScaleBox = scaleYBox->value(); m_scaleRatio = (tmpXScaleBox / (double) tmpYScaleBox); unblockNotifications(); } notifyConfigChanged(); } void KisToolTransformConfigWidget::slotSetWarpAlpha(qreal value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); config->setAlpha((double)value); notifyConfigChanged(); notifyEditingFinished(); } void KisToolTransformConfigWidget::slotSetWarpDensity(int value) { if (m_uiSlotsBlocked) return; setDefaultWarpPoints(value); } void KisToolTransformConfigWidget::setDefaultWarpPoints(int pointsPerLine) { ToolTransformArgs *config = m_transaction->currentConfig(); KisTransformUtils::setDefaultWarpPoints(pointsPerLine, m_transaction, config); notifyConfigChanged(); } void KisToolTransformConfigWidget::activateCustomWarpPoints(bool enabled) { ToolTransformArgs *config = m_transaction->currentConfig(); densityBox->setEnabled(!enabled); customWarpWidget->setEnabled(enabled); if (!enabled) { config->setEditingTransformPoints(false); setDefaultWarpPoints(densityBox->value()); config->setWarpCalculation(KisWarpTransformWorker::WarpCalculation::GRID); } else { config->setEditingTransformPoints(true); config->setWarpCalculation(KisWarpTransformWorker::WarpCalculation::DRAW); setDefaultWarpPoints(0); } updateLockPointsButtonCaption(); } void KisToolTransformConfigWidget::slotWarpDefaultPointsButtonClicked(bool value) { if (m_uiSlotsBlocked) return; activateCustomWarpPoints(!value); } void KisToolTransformConfigWidget::slotWarpCustomPointsButtonClicked(bool value) { if (m_uiSlotsBlocked) return; activateCustomWarpPoints(value); } void KisToolTransformConfigWidget::slotWarpResetPointsButtonClicked() { if (m_uiSlotsBlocked) return; activateCustomWarpPoints(true); } void KisToolTransformConfigWidget::slotWarpLockPointsButtonClicked() { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); config->setEditingTransformPoints(!config->isEditingTransformPoints()); if (config->isEditingTransformPoints()) { // reinit the transf points to their original value ToolTransformArgs *config = m_transaction->currentConfig(); int nbPoints = config->origPoints().size(); for (int i = 0; i < nbPoints; ++i) { config->transfPoint(i) = config->origPoint(i); } } updateLockPointsButtonCaption(); notifyConfigChanged(); } void KisToolTransformConfigWidget::slotWarpTypeChanged(int index) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); switch (index) { case KisWarpTransformWorker::AFFINE_TRANSFORM: case KisWarpTransformWorker::SIMILITUDE_TRANSFORM: case KisWarpTransformWorker::RIGID_TRANSFORM: config->setWarpType((KisWarpTransformWorker::WarpType)index); break; default: config->setWarpType(KisWarpTransformWorker::RIGID_TRANSFORM); break; } notifyConfigChanged(); } void KisToolTransformConfigWidget::slotCageOptionsChanged(int value) { if ( value == 0) { slotEditCagePoints(true); } else { slotEditCagePoints(false); } notifyEditingFinished(); } void KisToolTransformConfigWidget::slotEditCagePoints(bool value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); config->refTransformedPoints() = config->origPoints(); config->setEditingTransformPoints(value); notifyConfigChanged(); } void KisToolTransformConfigWidget::slotGranularityChanged(QString value) { if (m_uiSlotsBlocked) return; KIS_SAFE_ASSERT_RECOVER_RETURN(value.toInt() > 1); ToolTransformArgs *config = m_transaction->currentConfig(); config->setPixelPrecision(value.toInt()); notifyConfigChanged(); } void KisToolTransformConfigWidget::slotPreviewGranularityChanged(QString value) { if (m_uiSlotsBlocked) return; KIS_SAFE_ASSERT_RECOVER_RETURN(value.toInt() > 1); ToolTransformArgs *config = m_transaction->currentConfig(); config->setPreviewPixelPrecision(value.toInt()); notifyConfigChanged(); }